Skip to content

Commit 9794b47

Browse files
committed
fix a number of diagnostic source mapping issues
1 parent 12389ad commit 9794b47

File tree

2 files changed

+56
-53
lines changed

2 files changed

+56
-53
lines changed

packages/core/src/transform/template/template-to-typescript.ts

+34-21
Original file line numberDiff line numberDiff line change
@@ -1150,13 +1150,19 @@ export function templateToTypescript(
11501150
| AST.ElementModifierStatement;
11511151

11521152
function emitResolve(node: CurlyInvocationNode, resolveType: string): void {
1153-
emit.text('χ.');
1154-
emit.text(resolveType);
1155-
emit.text('(');
1156-
emitExpression(node.path);
1157-
emit.text(')(');
1158-
emitArgs(node.params, node.hash);
1159-
emit.text(')');
1153+
// We use forNode here to wrap the emitted resolve expression here so that when
1154+
// we convert to Volar mappings, we can create a boundary around
1155+
// e.g. "χ.resolveOrReturn(expectsAtLeastOneArg)()", which is required because
1156+
// this is where TS might generate a diagnostic error.
1157+
emit.forNode(node, () => {
1158+
emit.text('χ.');
1159+
emit.text(resolveType);
1160+
emit.text('(');
1161+
emitExpression(node.path);
1162+
emit.text(')(');
1163+
emitArgs(node.params, node.hash);
1164+
emit.text(')');
1165+
});
11601166
}
11611167

11621168
function emitArgs(positional: Array<AST.Expression>, named: AST.Hash): void {
@@ -1171,24 +1177,31 @@ export function templateToTypescript(
11711177

11721178
// Emit named args
11731179
if (named.pairs.length) {
1174-
emit.text(positional.length ? ', { ' : '{ ');
1180+
if (positional.length) {
1181+
emit.text(', ');
1182+
}
11751183

1176-
let { start } = rangeForNode(named);
1177-
for (let [index, pair] of named.pairs.entries()) {
1178-
start = template.indexOf(pair.key, start);
1179-
emitHashKey(pair.key, start);
1180-
emit.text(': ');
1181-
emitExpression(pair.value);
1184+
// TS diagnostic error boundary
1185+
emit.forNode(named, () => {
1186+
emit.text('{ ');
11821187

1183-
if (index === named.pairs.length - 1) {
1184-
emit.text(' ');
1185-
}
1188+
let { start } = rangeForNode(named);
1189+
for (let [index, pair] of named.pairs.entries()) {
1190+
start = template.indexOf(pair.key, start);
1191+
emitHashKey(pair.key, start);
1192+
emit.text(': ');
1193+
emitExpression(pair.value);
11861194

1187-
start = rangeForNode(pair.value).end;
1188-
emit.text(', ');
1189-
}
1195+
if (index === named.pairs.length - 1) {
1196+
emit.text(' ');
1197+
}
11901198

1191-
emit.text('...χ.NamedArgsMarker }');
1199+
start = rangeForNode(pair.value).end;
1200+
emit.text(', ');
1201+
}
1202+
1203+
emit.text('...χ.NamedArgsMarker }');
1204+
});
11921205
}
11931206
}
11941207

packages/core/src/transform/template/transformed-module.ts

+22-32
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,16 @@ export default class TransformedModule {
219219

220220
/**
221221
* Converts the mappings in this transformed module to the format expected by Volar.
222-
*
222+
*
223223
* The main difference between the two formats is that while the classic Glint transformation
224224
* mappings support mapping a differently sized source region to a differently sized target region
225225
* (e.g. `{{expectsAtLeastOneArg}}` in an .hbs file to `χ.emitContent(χ.resolveOrReturn(expectsAtLeastOneArg)());`
226226
* in a generated TS file, in Volar you can only map regions of the same size.
227-
*
227+
*
228228
* In the case that you need to map regions of different sizes in Volar, you need to also using
229229
* zero-length mappings to delineate regions/boundaries that should map to each other, otherwise there will
230230
* be cases where TS diagnostics will fail to transform/map back to the original source. Example:
231-
*
231+
*
232232
* - `{{[[ZEROLEN-A]][[expectsAtLeastOneArg]][[ZEROLEN-B]]}}`
233233
* - to
234234
* - `[[ZEROLEN-A]]χ.emitContent(χ.resolveOrReturn([[expectsAtLeastOneArg]])());[[ZEROLEN-B]]`
@@ -248,46 +248,37 @@ export default class TransformedModule {
248248

249249
if (children.length === 0) {
250250
// leaf node
251-
252-
const length = hbsEnd - hbsStart;
253-
254-
if (length === tsEnd - tsStart) {
251+
const hbsLength = hbsEnd - hbsStart;
252+
const tsLength = tsEnd - tsStart;
253+
if (hbsLength === tsLength) {
255254
// (Hacky?) assumption: because TS and HBS span lengths are equivalent,
256255
// then this is a simple leafmost mapping, e.g. `{{this.[foo]}}` -> `this.[foo]`
257256
sourceOffsets.push(hbsStart);
258257
generatedOffsets.push(tsStart);
259-
lengths.push(length);
258+
lengths.push(hbsLength);
260259
} else {
261-
// This isn't common but there are a few cases where we are not currently going as
262-
// "deep" as we good into the mapping tree to produce equal-size leaf nodes; here
263-
// is one example (using `toDebugString()`)
264-
//
265-
// | | | Mapping: MustacheStatement
266-
// | | | hbs(686:723): {{yield to="expectsAtLeastOneParam"}}
267-
// | | | ts(1025:1073):χ.yieldToBlock(𝚪, "expectsAtLeastOneParam")()
268-
269-
// It may make sense to rework the mappings for yields and other cases so that
270-
// the leaf nodes are equal-sized identifiers
271-
// (e.g. expectsAtLeastOneParam (hbs) -> expectsAtLeastOneParam(ts) ), but
272-
// in the mean time we will just produce zero-length boundary markers for Volar.
260+
// Disregard the "null zone" mappings, i.e. cases where TS code maps to empty HBS code
261+
if (hbsLength > 0 && tsLength > 0) {
262+
sourceOffsets.push(hbsStart);
263+
generatedOffsets.push(tsStart);
264+
lengths.push(0);
265+
sourceOffsets.push(hbsEnd);
266+
generatedOffsets.push(tsEnd);
267+
lengths.push(0);
268+
}
273269
}
274270
} else {
275-
// here we want to install zero-length mappings on the boundaries
276-
277-
// TODO: consider re-enabling these zero-length boundary mappings, but for now
278-
// they don't solve the problem of lack of granularity
279-
// sourceOffsets.push(hbsStart);
280-
// generatedOffsets.push(tsStart);
281-
// lengths.push(0);
271+
sourceOffsets.push(hbsStart);
272+
generatedOffsets.push(tsStart);
273+
lengths.push(0);
282274

283275
mapping.children.forEach((child) => {
284276
recurse(span, child);
285277
});
286278

287-
// TODO: see above
288-
// sourceOffsets.push(hbsEnd);
289-
// generatedOffsets.push(tsEnd);
290-
// lengths.push(0);
279+
sourceOffsets.push(hbsEnd);
280+
generatedOffsets.push(tsEnd);
281+
lengths.push(0);
291282
}
292283
};
293284

@@ -301,7 +292,6 @@ export default class TransformedModule {
301292
// transformation, we expect these to be the same length (in fact, they
302293
// should be the same string entirely)
303294

304-
305295
// This assertion seemed valid when parsing .gts files with extracted hbs in <template> tags,
306296
// but when parsing solo .hbs files in loose mode there were cases where, e.g.,
307297
// originalLength == 0 and transformLength == 1;

0 commit comments

Comments
 (0)