@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,1095 @@
1
+ import { assert, unreachable } from '../util.js';
2
+ import { TextContent } from './glimmer-ast-mapping-tree.js';
3
+ import { mapTemplateContents } from './map-template-contents.js';
4
+ import ScopeStack from './scope-stack.js';
5
+ const SPLATTRIBUTES = '...attributes';
6
+ /**
7
+ * NOTE: this is tech debt. Tho solving it will require more work than polyfilling old behavior of path.parts
8
+ * @param node
9
+ * @returns
10
+ */
11
+ function getPathParts(node) {
12
+ // The original code which used old @glimmer/syntax used node.parts,
13
+ // which never included the @ of the path.
14
+ let atLess = node.head.original.replace(/^@/, '');
15
+ // The original path.parts array did not include "this" in the parts.
16
+ if (atLess === 'this')
17
+ return node.tail;
18
+ return [atLess, ...node.tail];
19
+ }
20
+ /**
21
+ * Given the text contents of a template, returns a TypeScript representation
22
+ * of that template's contents, as well as a mapping of offsets and ranges between
23
+ * the original and transformed contents.
24
+ */
25
+ export function templateToTypescript(originalTemplate, { typesModule, globals, meta, backingValue, preamble = [], embeddingSyntax = { prefix: '', suffix: '' }, specialForms = {}, useJsDoc = false, }) {
26
+ let { prefix, suffix } = embeddingSyntax;
27
+ let template = `${''.padEnd(prefix.length)}${originalTemplate}${''.padEnd(suffix.length)}`;
28
+ return mapTemplateContents(originalTemplate, { embeddingSyntax }, (ast, mapper) => {
29
+ let { rangeForNode } = mapper;
30
+ let scope = new ScopeStack([]);
31
+ let inHtmlContext = 'default';
32
+ emitTemplateBoilerplate(() => {
33
+ for (let statement of ast?.body ?? []) {
34
+ emitTopLevelStatement(statement);
35
+ }
36
+ });
37
+ return;
38
+ function emitTopLevelStatement(node) {
39
+ switch (node.type) {
40
+ case 'Block':
41
+ throw new Error(`Internal error: unexpected top-level ${node.type}`);
42
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
43
+ // @ts-expect-error
44
+ case 'PartialStatement':
45
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
46
+ // @ts-expect-error
47
+ throw new Error(`Internal error: unexpected top-level ${node.type}`);
48
+ case 'TextNode':
49
+ return emitTopLevelTextNode(node);
50
+ case 'CommentStatement':
51
+ case 'MustacheCommentStatement':
52
+ return emitComment(node);
53
+ case 'MustacheStatement':
54
+ return emitTopLevelMustacheStatement(node);
55
+ case 'BlockStatement':
56
+ return emitBlockStatement(node);
57
+ case 'ElementNode':
58
+ return emitElementNode(node);
59
+ default:
60
+ unreachable(node);
61
+ }
62
+ }
63
+ function emitTemplateBoilerplate(emitBody) {
64
+ if (meta?.prepend) {
65
+ mapper.text(meta.prepend);
66
+ }
67
+ if (useJsDoc) {
68
+ mapper.text(`(/** @type {typeof import(`);
69
+ if (ast) {
70
+ mapper.forNode(ast, () => {
71
+ mapper.text(`"${typesModule}"`);
72
+ });
73
+ }
74
+ else {
75
+ mapper.text(`"${typesModule}"`);
76
+ }
77
+ mapper.text(`)} */ ({}))`);
78
+ }
79
+ else {
80
+ mapper.text(`({} as typeof import(`);
81
+ if (ast) {
82
+ mapper.forNode(ast, () => {
83
+ mapper.text(`"${typesModule}"`);
84
+ });
85
+ }
86
+ else {
87
+ mapper.text(`"${typesModule}"`);
88
+ }
89
+ mapper.text(`))`);
90
+ }
91
+ if (backingValue) {
92
+ mapper.text(`.templateForBackingValue(${backingValue}, function(__glintRef__`);
93
+ }
94
+ else {
95
+ mapper.text(`.templateExpression(function(__glintRef__`);
96
+ }
97
+ if (useJsDoc) {
98
+ mapper.text(`, /** @type {typeof import("${typesModule}")} */ __glintDSL__) {`);
99
+ }
100
+ else {
101
+ mapper.text(`, __glintDSL__: typeof import("${typesModule}")) {`);
102
+ }
103
+ mapper.newline();
104
+ mapper.indent();
105
+ for (let line of preamble) {
106
+ mapper.text(line);
107
+ mapper.newline();
108
+ }
109
+ if (ast) {
110
+ mapper.forNode(ast, emitBody);
111
+ }
112
+ // Ensure the context and lib variables are always consumed to prevent
113
+ // an unused variable warning
114
+ mapper.text('__glintRef__; __glintDSL__;');
115
+ mapper.newline();
116
+ mapper.emitDirectivePlaceholders();
117
+ mapper.dedent();
118
+ mapper.text('})');
119
+ if (meta?.append) {
120
+ mapper.text(meta.append);
121
+ }
122
+ }
123
+ function emitTopLevelTextNode(node) {
124
+ // We don't need to emit any code for text nodes, but we want to track
125
+ // where they are so we know NOT to try and suggest global completions
126
+ // in "text space" where it wouldn't make sense.
127
+ mapper.nothing(node, new TextContent());
128
+ }
129
+ function emitComment(node) {
130
+ let text = node.value.trim();
131
+ const directiveRegex = /^@glint-([a-z-]+)/i;
132
+ let match = directiveRegex.exec(text);
133
+ if (!match) {
134
+ return mapper.nothing(node);
135
+ }
136
+ emitDirective(match, node);
137
+ }
138
+ function emitDirective(match, node) {
139
+ let kind = match[1];
140
+ let location = rangeForNode(node);
141
+ if (kind === 'ignore' || kind === 'expect-error') {
142
+ mapper.directive(kind, node, location, mapper.rangeForLine(node.loc.end.line + 1));
143
+ }
144
+ else if (kind === 'nocheck') {
145
+ mapper.directive('ignore', node, location, { start: 0, end: template.length - 1 });
146
+ }
147
+ else if (kind === 'in-svg') {
148
+ inHtmlContext = 'svg';
149
+ }
150
+ else if (kind === 'in-mathml') {
151
+ inHtmlContext = 'math';
152
+ }
153
+ else if (kind === 'out-svg' || kind === 'out-mathml') {
154
+ inHtmlContext = 'default';
155
+ }
156
+ else {
157
+ // Push an error on the record
158
+ mapper.error(`Unknown directive @glint-${kind}`, location);
159
+ }
160
+ }
161
+ function emitTopLevelMustacheStatement(node) {
162
+ emitMustacheStatement(node, 'top-level');
163
+ mapper.text(';');
164
+ mapper.newline();
165
+ }
166
+ function emitSpecialFormExpression(formInfo, node, position) {
167
+ if (formInfo.requiresConsumption) {
168
+ mapper.text('(__glintDSL__.noop(');
169
+ emitExpression(node.path);
170
+ mapper.text('), ');
171
+ }
172
+ switch (formInfo.form) {
173
+ case 'yield':
174
+ emitYieldExpression(formInfo, node, position);
175
+ break;
176
+ case 'if':
177
+ emitIfExpression(formInfo, node);
178
+ break;
179
+ case 'if-not':
180
+ emitIfNotExpression(formInfo, node);
181
+ break;
182
+ case 'object-literal':
183
+ emitObjectExpression(formInfo, node);
184
+ break;
185
+ case 'array-literal':
186
+ emitArrayExpression(formInfo, node);
187
+ break;
188
+ case 'bind-invokable':
189
+ emitBindInvokableExpression(formInfo, node, position);
190
+ break;
191
+ case '===':
192
+ case '!==':
193
+ emitBinaryOperatorExpression(formInfo, node);
194
+ break;
195
+ case '&&':
196
+ case '||':
197
+ emitLogicalExpression(formInfo, node);
198
+ break;
199
+ case '!':
200
+ emitUnaryOperatorExpression(formInfo, node);
201
+ break;
202
+ default:
203
+ mapper.error(`${formInfo.name} is not valid in inline form`, rangeForNode(node));
204
+ mapper.text('undefined');
205
+ }
206
+ if (formInfo.requiresConsumption) {
207
+ mapper.text(')');
208
+ }
209
+ }
210
+ function emitBindInvokableExpression(formInfo, node, position) {
211
+ mapper.forNode(node, () => {
212
+ assert(node.params.length >= 1, () => `{{${formInfo.name}}} requires at least one positional argument`);
213
+ assert(node.params.length === 1 || node.hash.pairs.length === 0, () => `Due to TypeScript inference limitations, {{${formInfo.name}}} can only pre-bind ` +
214
+ `either named or positional arguments in a single pass. You can instead break the ` +
215
+ `binding into two parts, e.g. ` +
216
+ `{{${formInfo.name} (${formInfo.name} ... posA posB) namedA=true namedB=true}}`);
217
+ if (position === 'top-level') {
218
+ mapper.text('__glintDSL__.emitContent(');
219
+ }
220
+ // Treat the first argument to a bind-invokable expression (`{{component}}`,
221
+ // `{{helper}}`, etc) as special: we wrap it in a `resolve` call so that the
222
+ // type machinery for those helpers can always operate against the resolved value.
223
+ // We wrap the `resolveForBind` call in an IIFE to prevent "backpressure" in
224
+ // type inference from the subsequent arguments that are being passed: the bound
225
+ // invokable is the source of record for its own type and we don't want inference
226
+ // from the `resolveForBind` call to be affected by other (potentially incorrect)
227
+ // parameter types.
228
+ mapper.text('__glintDSL__.resolve(');
229
+ emitExpression(node.path);
230
+ mapper.text(')((() => __glintDSL__.resolveForBind(');
231
+ emitExpression(node.params[0]);
232
+ mapper.text('))(), ');
233
+ emitArgs(node.params.slice(1), node.hash);
234
+ mapper.text(')');
235
+ if (position === 'top-level') {
236
+ mapper.text(')');
237
+ }
238
+ });
239
+ }
240
+ function emitObjectExpression(formInfo, node) {
241
+ mapper.forNode(node, () => {
242
+ assert(node.params.length === 0, () => `{{${formInfo.name}}} only accepts named parameters`);
243
+ if (!node.hash.pairs.length) {
244
+ mapper.text('{}');
245
+ return;
246
+ }
247
+ mapper.text('({');
248
+ mapper.indent();
249
+ mapper.newline();
250
+ let start = template.indexOf('hash', rangeForNode(node).start) + 4;
251
+ for (let pair of node.hash.pairs) {
252
+ start = template.indexOf(pair.key, start);
253
+ emitHashKey(pair.key, start);
254
+ mapper.text(': ');
255
+ emitExpression(pair.value);
256
+ mapper.text(',');
257
+ mapper.newline();
258
+ }
259
+ mapper.dedent();
260
+ mapper.text('})');
261
+ });
262
+ }
263
+ function emitArrayExpression(formInfo, node) {
264
+ mapper.forNode(node, () => {
265
+ assert(node.hash.pairs.length === 0, () => `{{${formInfo.name}}} only accepts positional parameters`);
266
+ mapper.text('[');
267
+ for (let [index, param] of node.params.entries()) {
268
+ emitExpression(param);
269
+ if (index < node.params.length - 1) {
270
+ mapper.text(', ');
271
+ }
272
+ }
273
+ mapper.text(']');
274
+ });
275
+ }
276
+ function emitIfExpression(formInfo, node) {
277
+ mapper.forNode(node, () => {
278
+ assert(node.params.length >= 2, () => `{{${formInfo.name}}} requires at least two parameters`);
279
+ mapper.text('(');
280
+ emitExpression(node.params[0]);
281
+ mapper.text(') ? (');
282
+ emitExpression(node.params[1]);
283
+ mapper.text(') : (');
284
+ if (node.params[2]) {
285
+ emitExpression(node.params[2]);
286
+ }
287
+ else {
288
+ mapper.text('undefined');
289
+ }
290
+ mapper.text(')');
291
+ });
292
+ }
293
+ function emitIfNotExpression(formInfo, node) {
294
+ mapper.forNode(node, () => {
295
+ assert(node.params.length >= 2, () => `{{${formInfo.name}}} requires at least two parameters`);
296
+ mapper.text('!(');
297
+ emitExpression(node.params[0]);
298
+ mapper.text(') ? (');
299
+ emitExpression(node.params[1]);
300
+ mapper.text(') : (');
301
+ if (node.params[2]) {
302
+ emitExpression(node.params[2]);
303
+ }
304
+ else {
305
+ mapper.text('undefined');
306
+ }
307
+ mapper.text(')');
308
+ });
309
+ }
310
+ function emitBinaryOperatorExpression(formInfo, node) {
311
+ mapper.forNode(node, () => {
312
+ assert(node.hash.pairs.length === 0, () => `{{${formInfo.name}}} only accepts positional parameters`);
313
+ assert(node.params.length === 2, () => `{{${formInfo.name}}} requires exactly two parameters`);
314
+ const [left, right] = node.params;
315
+ mapper.text('(');
316
+ emitExpression(left);
317
+ mapper.text(` ${formInfo.form} `);
318
+ emitExpression(right);
319
+ mapper.text(')');
320
+ });
321
+ }
322
+ function emitLogicalExpression(formInfo, node) {
323
+ mapper.forNode(node, () => {
324
+ assert(node.hash.pairs.length === 0, () => `{{${formInfo.name}}} only accepts positional parameters`);
325
+ assert(node.params.length >= 2, () => `{{${formInfo.name}}} requires at least two parameters`);
326
+ mapper.text('(');
327
+ for (const [index, param] of node.params.entries()) {
328
+ emitExpression(param);
329
+ if (index < node.params.length - 1) {
330
+ mapper.text(` ${formInfo.form} `);
331
+ }
332
+ }
333
+ mapper.text(')');
334
+ });
335
+ }
336
+ function emitUnaryOperatorExpression(formInfo, node) {
337
+ mapper.forNode(node, () => {
338
+ assert(node.hash.pairs.length === 0, () => `{{${formInfo.name}}} only accepts positional parameters`);
339
+ assert(node.params.length === 1, () => `{{${formInfo.name}}} requires exactly one parameter`);
340
+ const [param] = node.params;
341
+ mapper.text(formInfo.form);
342
+ emitExpression(param);
343
+ });
344
+ }
345
+ function checkSpecialForm(node) {
346
+ if (node.path.type === 'PathExpression' &&
347
+ node.path.head.type === 'VarHead' &&
348
+ !node.path.tail.length) {
349
+ let name = node.path.head.name;
350
+ if (typeof specialForms[name] === 'string' && !scope.hasBinding(name)) {
351
+ let isGlobal = globals ? globals.includes(name) : true;
352
+ let form = specialForms[name];
353
+ return { name, form, requiresConsumption: !isGlobal };
354
+ }
355
+ }
356
+ return null;
357
+ }
358
+ function emitExpression(node) {
359
+ switch (node.type) {
360
+ case 'PathExpression':
361
+ return emitPath(node);
362
+ case 'SubExpression':
363
+ return emitSubExpression(node);
364
+ case 'BooleanLiteral':
365
+ case 'NullLiteral':
366
+ case 'NumberLiteral':
367
+ case 'StringLiteral':
368
+ case 'UndefinedLiteral':
369
+ return emitLiteral(node);
370
+ default:
371
+ unreachable(node);
372
+ }
373
+ }
374
+ function emitElementNode(node) {
375
+ let firstCharacter = node.tag.charAt(0);
376
+ if (firstCharacter.toUpperCase() === firstCharacter ||
377
+ node.tag.includes('.') ||
378
+ scope.hasBinding(node.tag)) {
379
+ emitComponent(node);
380
+ }
381
+ else {
382
+ emitPlainElement(node);
383
+ }
384
+ }
385
+ function emitConcatStatement(node) {
386
+ mapper.forNode(node, () => {
387
+ mapper.text('`');
388
+ for (let part of node.parts) {
389
+ if (part.type === 'MustacheStatement') {
390
+ mapper.text('$' + '{');
391
+ emitMustacheStatement(part, 'concat');
392
+ mapper.text('}');
393
+ }
394
+ }
395
+ mapper.text('`');
396
+ });
397
+ }
398
+ function emitIdentifierReference(name, hbsOffset) {
399
+ if (treatAsGlobal(name)) {
400
+ mapper.text('__glintDSL__.Globals["');
401
+ mapper.identifier(JSON.stringify(name).slice(1, -1), hbsOffset, name.length);
402
+ mapper.text('"]');
403
+ }
404
+ else {
405
+ mapper.identifier(makeJSSafe(name), hbsOffset, name.length);
406
+ }
407
+ }
408
+ function treatAsGlobal(name) {
409
+ if (globals) {
410
+ // If we have a known set of global identifiers, we should only treat
411
+ // members of that set as global, unless the identifier is in scope,
412
+ // and assume everything else is local. This is typically true in
413
+ // environments that capture scope, like strict-mode Ember.
414
+ return globals.includes(name) && !scope.hasBinding(name);
415
+ }
416
+ else {
417
+ // Otherwise, we assume everything is global unless we can see it
418
+ // in scope as a block variable. This is the case in resolver-based
419
+ // environments like loose-mode Ember.
420
+ return !scope.hasBinding(name);
421
+ }
422
+ }
423
+ function tagNameToPathContents(node) {
424
+ let tagName = node.tag;
425
+ let start = template.indexOf(tagName, rangeForNode(node).start);
426
+ if (tagName.startsWith('@')) {
427
+ return {
428
+ start,
429
+ kind: 'arg',
430
+ path: tagName.slice(1).split('.'),
431
+ };
432
+ }
433
+ else if (tagName.startsWith('this.')) {
434
+ return {
435
+ start,
436
+ kind: 'this',
437
+ path: tagName.slice('this.'.length).split('.'),
438
+ };
439
+ }
440
+ else {
441
+ return {
442
+ start,
443
+ kind: 'free',
444
+ path: tagName.split('.'),
445
+ };
446
+ }
447
+ }
448
+ function emitComponent(node) {
449
+ mapper.forNode(node, () => {
450
+ let { start, path, kind } = tagNameToPathContents(node);
451
+ for (let comment of node.comments) {
452
+ emitComment(comment);
453
+ }
454
+ mapper.text('{');
455
+ mapper.newline();
456
+ mapper.indent();
457
+ // Resolve the component and stash into the `__glintY__` variable for later invocation.
458
+ mapper.text('const __glintY__ = __glintDSL__.emitComponent(');
459
+ // Error boundary: "Expected 1 arguments, but got 0." e.g. when invoking `<ComponentThatHasArgs />`
460
+ mapper.forNode(node.path, () => {
461
+ mapper.text('__glintDSL__.resolve(');
462
+ emitPathContents(path, start, kind);
463
+ mapper.text(')');
464
+ });
465
+ // "Call" the component, optionally passing args if they are provided in the template.
466
+ mapper.text('(');
467
+ let dataAttrs = node.attributes.filter(({ name }) => name.startsWith('@'));
468
+ if (dataAttrs.length) {
469
+ // Error boundary: "Expected 0 arguments, but got 1." e.g. when invoking `<ComponentThatHasNoArgs @foo={{bar}} />`
470
+ mapper.forNodeWithSpan(node, node.openTag, () => {
471
+ mapper.text('{ ');
472
+ for (let attr of dataAttrs) {
473
+ mapper.forNode(attr, () => {
474
+ mapper.newline();
475
+ const attrStartOffset = attr.loc.getStart().offset;
476
+ emitHashKey(attr.name.slice(1), attrStartOffset + prefix.length + 1);
477
+ mapper.text(': ');
478
+ switch (attr.value.type) {
479
+ case 'TextNode':
480
+ mapper.text(JSON.stringify(attr.value.chars));
481
+ break;
482
+ case 'ConcatStatement':
483
+ emitConcatStatement(attr.value);
484
+ break;
485
+ case 'MustacheStatement':
486
+ emitMustacheStatement(attr.value, 'arg');
487
+ break;
488
+ default:
489
+ unreachable(attr.value);
490
+ }
491
+ });
492
+ start = rangeForNode(attr.value).end;
493
+ mapper.text(', ');
494
+ }
495
+ mapper.text('...__glintDSL__.NamedArgsMarker }');
496
+ });
497
+ }
498
+ mapper.text('));');
499
+ mapper.newline();
500
+ emitAttributesAndModifiers(node);
501
+ if (!node.selfClosing) {
502
+ let blocks = determineBlockChildren(node);
503
+ if (blocks.type === 'named') {
504
+ for (const child of blocks.children) {
505
+ if (child.type === 'CommentStatement' || child.type === 'MustacheCommentStatement') {
506
+ emitComment(child);
507
+ continue;
508
+ }
509
+ let childStart = rangeForNode(child).start;
510
+ let nameStart = template.indexOf(child.tag, childStart) + ':'.length;
511
+ let blockParamsStart = template.indexOf('|', childStart);
512
+ let name = child.tag.slice(1);
513
+ mapper.forNode(child, () => emitBlockContents(name, nameStart, child.blockParams, blockParamsStart, child.children));
514
+ }
515
+ }
516
+ else {
517
+ let blockParamsStart = template.indexOf('|', rangeForNode(node).start);
518
+ emitBlockContents('default', undefined, node.blockParams, blockParamsStart, blocks.children);
519
+ }
520
+ }
521
+ mapper.dedent();
522
+ mapper.text('}');
523
+ mapper.newline();
524
+ });
525
+ }
526
+ function isAllowedAmongNamedBlocks(node) {
527
+ return ((node.type === 'TextNode' && node.chars.trim() === '') ||
528
+ node.type === 'CommentStatement' ||
529
+ node.type === 'MustacheCommentStatement');
530
+ }
531
+ function isNamedBlock(node) {
532
+ return node.type === 'ElementNode' && node.tag.startsWith(':');
533
+ }
534
+ function determineBlockChildren(node) {
535
+ let named = 0;
536
+ let other = 0;
537
+ for (let child of node.children) {
538
+ if (isAllowedAmongNamedBlocks(child)) {
539
+ continue;
540
+ }
541
+ if (isNamedBlock(child)) {
542
+ named += 1;
543
+ }
544
+ else {
545
+ other += 1;
546
+ }
547
+ }
548
+ if (named === 0) {
549
+ return { type: 'default', children: node.children };
550
+ }
551
+ else if (other === 0) {
552
+ return {
553
+ type: 'named',
554
+ children: node.children.filter(
555
+ // Filter out ignorable content between named blocks
556
+ (child) => child.type === 'ElementNode'),
557
+ };
558
+ }
559
+ else {
560
+ // If we get here, meaningful content was mixed with named blocks,
561
+ // so it's worth doing the additional work to produce errors for
562
+ // those nodes
563
+ for (let child of node.children) {
564
+ if (!isNamedBlock(child)) {
565
+ mapper.forNode(child, () => assert(isAllowedAmongNamedBlocks(child), 'Named blocks may not be mixed with other content'));
566
+ }
567
+ }
568
+ return { type: 'named', children: [] };
569
+ }
570
+ }
571
+ function emitPlainElement(node) {
572
+ mapper.forNode(node, () => {
573
+ if (node.tag === 'svg') {
574
+ inHtmlContext = 'svg';
575
+ }
576
+ if (node.tag === 'math') {
577
+ inHtmlContext = 'math';
578
+ }
579
+ for (let comment of node.comments) {
580
+ emitComment(comment);
581
+ }
582
+ mapper.text('{');
583
+ mapper.newline();
584
+ mapper.indent();
585
+ if (inHtmlContext === 'default') {
586
+ mapper.text('const __glintY__ = __glintDSL__.emitElement("');
587
+ }
588
+ else if (inHtmlContext === 'svg') {
589
+ mapper.text('const __glintY__ = __glintDSL__.emitSVGElement("');
590
+ }
591
+ else if (inHtmlContext === 'math') {
592
+ mapper.text('const __glintY__ = __glintDSL__.emitMathMlElement("');
593
+ }
594
+ mapper.forNode(node.path, () => {
595
+ mapper.text(node.tag);
596
+ });
597
+ mapper.text('");');
598
+ mapper.newline();
599
+ emitAttributesAndModifiers(node);
600
+ for (let child of node.children) {
601
+ emitTopLevelStatement(child);
602
+ }
603
+ if (node.tag === 'svg' || node.tag === 'math') {
604
+ inHtmlContext = 'default';
605
+ }
606
+ mapper.dedent();
607
+ mapper.text('}');
608
+ mapper.newline();
609
+ });
610
+ }
611
+ function emitAttributesAndModifiers(node) {
612
+ emitSplattributes(node);
613
+ emitPlainAttributes(node);
614
+ emitModifiers(node);
615
+ }
616
+ function emitPlainAttributes(node) {
617
+ let attributes = node.attributes.filter((attr) => !attr.name.startsWith('@') && attr.name !== SPLATTRIBUTES);
618
+ // Only emit `applyAttributes` if there are attributes to apply.
619
+ if (attributes.length === 0)
620
+ return;
621
+ const attrsSpan = node.openTag
622
+ .withStart(node.path.loc.getEnd())
623
+ .withEnd(node.openTag.getEnd().move(-1));
624
+ mapper.forNodeWithSpan(node, attrsSpan, () => {
625
+ let isFirstAttribute = true;
626
+ for (let attr of attributes) {
627
+ if (isFirstAttribute) {
628
+ isFirstAttribute = false;
629
+ mapper.text('__glintDSL__.applyAttributes(');
630
+ // We map the `__glintY__.element` arg to the first attribute node, which has the effect
631
+ // such that diagnostics due to passing attributes to invalid elements will show up
632
+ // on the attribute, rather than on the whole element.
633
+ mapper.forNode(attr, () => {
634
+ mapper.text('__glintY__.element');
635
+ });
636
+ mapper.text(', {');
637
+ }
638
+ mapper.newline();
639
+ mapper.indent();
640
+ mapper.forNode(attr, () => {
641
+ const attrStartOffset = attr.loc.getStart().offset;
642
+ emitHashKey(attr.name, attrStartOffset + prefix.length);
643
+ mapper.text(': ');
644
+ if (attr.value.type === 'MustacheStatement') {
645
+ emitMustacheStatement(attr.value, 'attr');
646
+ }
647
+ else if (attr.value.type === 'ConcatStatement') {
648
+ emitConcatStatement(attr.value);
649
+ }
650
+ else {
651
+ mapper.text(JSON.stringify(attr.value.chars));
652
+ }
653
+ mapper.text(',');
654
+ mapper.newline();
655
+ });
656
+ }
657
+ mapper.newline();
658
+ });
659
+ mapper.newline();
660
+ mapper.dedent();
661
+ mapper.text('});');
662
+ mapper.newline();
663
+ }
664
+ function emitSplattributes(node) {
665
+ let splattributes = node.attributes.find((attr) => attr.name === SPLATTRIBUTES);
666
+ if (!splattributes)
667
+ return;
668
+ assert(splattributes.value.type === 'TextNode' && splattributes.value.chars === '', '`...attributes` cannot accept a value');
669
+ mapper.forNode(splattributes, () => {
670
+ mapper.text('__glintDSL__.applySplattributes(__glintRef__.element, __glintY__.element);');
671
+ });
672
+ mapper.newline();
673
+ }
674
+ function emitModifiers(node) {
675
+ for (let modifier of node.modifiers) {
676
+ mapper.forNode(modifier, () => {
677
+ mapper.text('__glintDSL__.applyModifier(');
678
+ mapper.forNode(modifier, () => {
679
+ mapper.text('__glintDSL__.resolve(');
680
+ emitExpression(modifier.path);
681
+ mapper.text(')');
682
+ });
683
+ mapper.text('(__glintY__.element, ');
684
+ emitArgs(modifier.params, modifier.hash);
685
+ mapper.text('));');
686
+ mapper.newline();
687
+ });
688
+ }
689
+ }
690
+ function emitMustacheStatement(node, position) {
691
+ let specialFormInfo = checkSpecialForm(node);
692
+ if (specialFormInfo) {
693
+ emitSpecialFormExpression(specialFormInfo, node, position);
694
+ return;
695
+ }
696
+ else if (node.path.type !== 'PathExpression' && node.path.type !== 'SubExpression') {
697
+ // This assertion is currently meaningless, as @glimmer/syntax silently drops
698
+ // any named or positional parameters passed in a literal mustache
699
+ assert(node.params.length === 0 && node.hash.pairs.length === 0, 'Literals do not accept params');
700
+ emitLiteral(node.path);
701
+ return;
702
+ }
703
+ mapper.forNode(node, () => {
704
+ // If a mustache has parameters, we know it must be an invocation; if
705
+ // not, it depends on where it appears. In arg position, it's always
706
+ // passed directly as a value; otherwise it's invoked if it's a
707
+ // component/helper, and returned as a value otherwise.
708
+ let hasParams = Boolean(node.hash.pairs.length || node.params.length);
709
+ if (!hasParams && position === 'arg' && !isGlobal(node.path)) {
710
+ emitExpression(node.path);
711
+ }
712
+ else if (position === 'top-level') {
713
+ // e.g. top-level mustache `{{someValue}}`
714
+ mapper.text('__glintDSL__.emitContent(');
715
+ emitResolve(node, hasParams ? 'resolve' : 'resolveOrReturn');
716
+ mapper.text(')');
717
+ }
718
+ else {
719
+ emitResolve(node, hasParams ? 'resolve' : 'resolveOrReturn');
720
+ }
721
+ });
722
+ }
723
+ function isGlobal(path) {
724
+ return Boolean(path.type === 'PathExpression' &&
725
+ path.head.type === 'VarHead' &&
726
+ globals?.includes(path.head.name) &&
727
+ !scope.hasBinding(path.head.name));
728
+ }
729
+ function emitYieldExpression(formInfo, node, position) {
730
+ mapper.forNode(node, () => {
731
+ assert(position === 'top-level', () => `{{${formInfo.name}}} may only appear as a top-level statement`);
732
+ let to = 'default';
733
+ let toPair = node.hash.pairs.find((pair) => pair.key === 'to');
734
+ if (toPair) {
735
+ assert(toPair.value.type === 'StringLiteral', () => `Named block {{${formInfo.name}}}s must have a literal block name`);
736
+ to = toPair.value.value;
737
+ }
738
+ if (to === 'inverse') {
739
+ to = 'else';
740
+ }
741
+ mapper.text('__glintDSL__.yieldToBlock(__glintRef__, ');
742
+ mapper.text(JSON.stringify(to));
743
+ mapper.text(')(');
744
+ for (let [index, param] of node.params.entries()) {
745
+ if (index) {
746
+ mapper.text(', ');
747
+ }
748
+ emitExpression(param);
749
+ }
750
+ mapper.text(')');
751
+ });
752
+ }
753
+ function emitSpecialFormStatement(formInfo, node) {
754
+ if (formInfo.requiresConsumption) {
755
+ emitExpression(node.path);
756
+ mapper.text(';');
757
+ mapper.newline();
758
+ }
759
+ switch (formInfo.form) {
760
+ case 'if':
761
+ emitIfStatement(formInfo, node);
762
+ break;
763
+ case 'if-not':
764
+ emitUnlessStatement(formInfo, node);
765
+ break;
766
+ case 'bind-invokable':
767
+ mapper.error(`The {{${formInfo.name}}} helper can't be used directly in block form under Glint. ` +
768
+ `Consider first binding the result to a variable, e.g. '{{#let (${formInfo.name} ...) as |...|}}' ` +
769
+ `and then using the bound value.`, rangeForNode(node.path));
770
+ break;
771
+ default:
772
+ mapper.error(`${formInfo.name} is not valid in block form`, rangeForNode(node.path));
773
+ }
774
+ }
775
+ function emitIfStatement(formInfo, node) {
776
+ mapper.forNode(node, () => {
777
+ assert(node.params.length === 1, () => `{{#${formInfo.name}}} requires exactly one condition`);
778
+ mapper.text('if (');
779
+ emitExpression(node.params[0]);
780
+ mapper.text(') {');
781
+ mapper.newline();
782
+ mapper.indent();
783
+ for (let statement of node.program.body) {
784
+ emitTopLevelStatement(statement);
785
+ }
786
+ if (node.inverse) {
787
+ mapper.dedent();
788
+ mapper.text('} else {');
789
+ mapper.indent();
790
+ mapper.newline();
791
+ for (let statement of node.inverse.body) {
792
+ emitTopLevelStatement(statement);
793
+ }
794
+ }
795
+ mapper.dedent();
796
+ mapper.text('}');
797
+ mapper.newline();
798
+ });
799
+ }
800
+ function emitUnlessStatement(formInfo, node) {
801
+ mapper.forNode(node, () => {
802
+ assert(node.params.length === 1, () => `{{#${formInfo.name}}} requires exactly one condition`);
803
+ mapper.text('if (!(');
804
+ emitExpression(node.params[0]);
805
+ mapper.text(')) {');
806
+ mapper.newline();
807
+ mapper.indent();
808
+ for (let statement of node.program.body) {
809
+ emitTopLevelStatement(statement);
810
+ }
811
+ if (node.inverse) {
812
+ mapper.dedent();
813
+ mapper.text('} else {');
814
+ mapper.indent();
815
+ mapper.newline();
816
+ for (let statement of node.inverse.body) {
817
+ emitTopLevelStatement(statement);
818
+ }
819
+ }
820
+ mapper.dedent();
821
+ mapper.text('}');
822
+ mapper.newline();
823
+ });
824
+ }
825
+ function emitBlockStatement(node) {
826
+ let specialFormInfo = checkSpecialForm(node);
827
+ if (specialFormInfo) {
828
+ emitSpecialFormStatement(specialFormInfo, node);
829
+ return;
830
+ }
831
+ mapper.forNode(node, () => {
832
+ mapper.text('{');
833
+ mapper.newline();
834
+ mapper.indent();
835
+ mapper.text('const __glintY__ = __glintDSL__.emitComponent(');
836
+ emitResolve(node, 'resolve');
837
+ mapper.text(');');
838
+ mapper.newline();
839
+ emitBlock('default', node.program);
840
+ if (node.inverse) {
841
+ emitBlock('else', node.inverse);
842
+ }
843
+ // TODO: emit something corresponding to `{{/foo}}` like we do
844
+ // for angle bracket components, so that symbol renames propagate?
845
+ // A little hairier (ha) for mustaches, since they
846
+ if (node.path.type === 'PathExpression') {
847
+ let start = template.lastIndexOf(node.path.original, rangeForNode(node).end);
848
+ emitPathContents(getPathParts(node.path), start, determinePathKind(node.path));
849
+ mapper.text(';');
850
+ mapper.newline();
851
+ }
852
+ mapper.dedent();
853
+ mapper.text('}');
854
+ });
855
+ mapper.newline();
856
+ }
857
+ function emitBlock(name, node) {
858
+ let paramsStart = template.lastIndexOf('|', template.lastIndexOf('|', rangeForNode(node).end) - 1);
859
+ emitBlockContents(name, undefined, node.blockParams, paramsStart, node.body);
860
+ }
861
+ function emitBlockContents(name, nameOffset, blockParams, blockParamsOffset, children) {
862
+ assert(blockParams.every((name) => !name.includes('-')), 'Block params must be valid TypeScript identifiers');
863
+ scope.push(blockParams);
864
+ mapper.text('{');
865
+ mapper.newline();
866
+ mapper.indent();
867
+ mapper.text('const [');
868
+ let start = blockParamsOffset;
869
+ for (let [index, param] of blockParams.entries()) {
870
+ if (index)
871
+ mapper.text(', ');
872
+ start = template.indexOf(param, start);
873
+ mapper.identifier(makeJSSafe(param), start, param.length);
874
+ }
875
+ mapper.text('] = __glintY__.blockParams');
876
+ emitPropertyAccesss(name, { offset: nameOffset, synthetic: true });
877
+ mapper.text(';');
878
+ mapper.newline();
879
+ for (let statement of children) {
880
+ emitTopLevelStatement(statement);
881
+ }
882
+ mapper.dedent();
883
+ mapper.text('}');
884
+ mapper.newline();
885
+ scope.pop();
886
+ }
887
+ function emitSubExpression(node) {
888
+ let specialFormInfo = checkSpecialForm(node);
889
+ if (specialFormInfo) {
890
+ emitSpecialFormExpression(specialFormInfo, node, 'sexpr');
891
+ return;
892
+ }
893
+ mapper.forNode(node, () => {
894
+ emitResolve(node, 'resolve');
895
+ });
896
+ }
897
+ function emitResolve(node, resolveType) {
898
+ // We use forNode here to wrap the emitted resolve expression here so that when
899
+ // we convert to Volar mappings, we can create a boundary around
900
+ // e.g. "__glintDSL__.resolveOrReturn(expectsAtLeastOneArg)()", which is required because
901
+ // this is where TS might generate a diagnostic error.
902
+ mapper.forNode(node, () => {
903
+ mapper.text('__glintDSL__.');
904
+ mapper.text(resolveType);
905
+ mapper.text('(');
906
+ emitExpression(node.path);
907
+ mapper.text(')(');
908
+ emitArgs(node.params, node.hash);
909
+ mapper.text(')');
910
+ });
911
+ }
912
+ function emitArgs(positional, named) {
913
+ // Emit positional args
914
+ for (let [index, param] of positional.entries()) {
915
+ if (index) {
916
+ mapper.text(', ');
917
+ }
918
+ emitExpression(param);
919
+ }
920
+ // Emit named args
921
+ if (named.pairs.length) {
922
+ if (positional.length) {
923
+ mapper.text(', ');
924
+ }
925
+ // TS diagnostic error boundary
926
+ mapper.forNode(named, () => {
927
+ mapper.text('{ ');
928
+ let { start } = rangeForNode(named);
929
+ for (let [index, pair] of named.pairs.entries()) {
930
+ start = template.indexOf(pair.key, start);
931
+ emitHashKey(pair.key, start);
932
+ mapper.text(': ');
933
+ emitExpression(pair.value);
934
+ if (index === named.pairs.length - 1) {
935
+ mapper.text(' ');
936
+ }
937
+ start = rangeForNode(pair.value).end;
938
+ mapper.text(', ');
939
+ }
940
+ mapper.text('...__glintDSL__.NamedArgsMarker }');
941
+ });
942
+ }
943
+ }
944
+ function emitPath(node) {
945
+ mapper.forNode(node, () => {
946
+ let { start } = rangeForNode(node);
947
+ emitPathContents(getPathParts(node), start, determinePathKind(node));
948
+ });
949
+ }
950
+ function determinePathKind(node) {
951
+ switch (node.head.type) {
952
+ case 'AtHead':
953
+ return 'arg';
954
+ case 'ThisHead':
955
+ return 'this';
956
+ case 'VarHead':
957
+ return 'free';
958
+ }
959
+ }
960
+ function emitPathContents(parts, start, kind) {
961
+ if (kind === 'this') {
962
+ let thisStart = template.indexOf('this', start);
963
+ mapper.text('__glintRef__.');
964
+ mapper.identifier('this', thisStart);
965
+ start = template.indexOf('.', thisStart) + 1;
966
+ }
967
+ else if (kind === 'arg') {
968
+ mapper.text('__glintRef__.args');
969
+ start = template.indexOf('@', start) + 1;
970
+ }
971
+ let head = parts[0];
972
+ if (!head)
973
+ return;
974
+ start = template.indexOf(head, start);
975
+ // The first segment of a non-this, non-arg path must resolve
976
+ // to some in-scope identifier.
977
+ if (kind === 'free') {
978
+ emitIdentifierReference(head, start);
979
+ }
980
+ else {
981
+ emitPropertyAccesss(head, { offset: start, optional: false });
982
+ }
983
+ start += head.length;
984
+ for (let i = 1; i < parts.length; i++) {
985
+ let part = parts[i];
986
+ start = template.indexOf(part, start);
987
+ emitPropertyAccesss(part, { offset: start, optional: true });
988
+ start += part.length;
989
+ }
990
+ }
991
+ function emitPropertyAccesss(name, { offset, optional, synthetic } = {}) {
992
+ // Synthetic accesses should always use `[]` notation to avoid incidentally triggering
993
+ // `noPropertyAccessFromIndexSignature`. Emitting `{{foo.bar}}` property accesses, however,
994
+ // should use `.` notation for exactly the same reason.
995
+ if (!synthetic && isSafeKey(name)) {
996
+ mapper.text(optional ? '?.' : '.');
997
+ if (offset) {
998
+ mapper.identifier(name, offset);
999
+ }
1000
+ else {
1001
+ mapper.text(name);
1002
+ }
1003
+ }
1004
+ else {
1005
+ mapper.text(optional ? '?.[' : '[');
1006
+ if (offset) {
1007
+ emitIdentifierString(name, offset);
1008
+ }
1009
+ else {
1010
+ mapper.text(JSON.stringify(name));
1011
+ }
1012
+ mapper.text(']');
1013
+ }
1014
+ }
1015
+ function emitHashKey(name, start) {
1016
+ if (isSafeKey(name)) {
1017
+ mapper.identifier(name, start);
1018
+ }
1019
+ else {
1020
+ emitIdentifierString(name, start);
1021
+ }
1022
+ }
1023
+ function emitIdentifierString(name, start) {
1024
+ mapper.text('"');
1025
+ mapper.identifier(JSON.stringify(name).slice(1, -1), start, name.length);
1026
+ mapper.text('"');
1027
+ }
1028
+ function emitLiteral(node) {
1029
+ mapper.forNode(node, () => mapper.text(node.value === undefined ? 'undefined' : JSON.stringify(node.value)));
1030
+ }
1031
+ function isSafeKey(key) {
1032
+ return /^[a-z_$][a-z0-9_$]*$/i.test(key);
1033
+ }
1034
+ });
1035
+ }
1036
+ const JSKeywords = new Set([
1037
+ 'await',
1038
+ 'break',
1039
+ 'case',
1040
+ 'catch',
1041
+ 'class',
1042
+ 'const',
1043
+ 'continue',
1044
+ 'debugger',
1045
+ 'default',
1046
+ 'delete',
1047
+ 'do',
1048
+ 'else',
1049
+ 'enum',
1050
+ 'eval',
1051
+ 'export',
1052
+ 'extends',
1053
+ 'false',
1054
+ 'finally',
1055
+ 'for',
1056
+ 'function',
1057
+ 'if',
1058
+ 'implements',
1059
+ 'import',
1060
+ 'in',
1061
+ 'instanceof',
1062
+ 'interface',
1063
+ 'let',
1064
+ 'new',
1065
+ 'null',
1066
+ 'package',
1067
+ 'private',
1068
+ 'protected',
1069
+ 'public',
1070
+ 'return',
1071
+ 'static',
1072
+ 'super',
1073
+ 'switch',
1074
+ 'this',
1075
+ 'throw',
1076
+ 'true',
1077
+ 'try',
1078
+ 'typeof',
1079
+ 'undefined',
1080
+ 'var',
1081
+ 'void',
1082
+ 'while',
1083
+ 'with',
1084
+ 'yield',
1085
+ ]);
1086
+ function isJSKeyword(token) {
1087
+ return JSKeywords.has(token);
1088
+ }
1089
+ function makeJSSafe(identifier) {
1090
+ if (isJSKeyword(identifier) || identifier.startsWith('__')) {
1091
+ return `__${identifier}`;
1092
+ }
1093
+ return identifier;
1094
+ }
1095
+ //# sourceMappingURL=template-to-typescript.js.map