Skip to content

Commit 9dcad59

Browse files
Merge pull request #1717 from glimmerjs/check-for-tilde-on-comment-2
Fix source slicing for whitespace-stripping comments
2 parents 789ebcd + 5256314 commit 9dcad59

File tree

5 files changed

+142
-19
lines changed

5 files changed

+142
-19
lines changed

packages/@glimmer/compiler/test/compiler-test.ts

+82
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,88 @@ test('top-level comments', `<!-- {{foo}} -->`, c` {{foo}} `);
314314

315315
test('handlebars comments', `<div>{{! Better not break! }}content</div>`, ['<div>', [s`content`]]);
316316

317+
test('handlebars comments with whitespace removal', '<div> {{~! some comment ~}} content</div>', [
318+
'<div>',
319+
[s`content`],
320+
]);
321+
322+
// Strange handlebars comments
323+
//
324+
// https://github.com/handlebars-lang/handlebars-parser/blob/a095229e292e814ed9d113d88c827f3509534d1a/lib/helpers.js#L44C26-L44C45
325+
//
326+
// These are all the ways I could find to write a comment with a single character 's':
327+
//
328+
// {{!s}}
329+
// {{!s-}}
330+
// {{!s--}}
331+
// {{!-s}}
332+
// {{!-s-}}
333+
// {{!-s--}}
334+
// {{!--s--}}
335+
//
336+
// Unclear if they are meant to work but the parser accepts them, and the compiler shouldn't break.
337+
//
338+
// This is really testing @glimmer/syntax's ASTv2 loc info, but that is currently only exercised by
339+
// the compiler and does not have its own dedicated test suite.
340+
const StrangeComments = [
341+
// empty comments
342+
'!',
343+
'!-',
344+
'!--',
345+
'!---',
346+
'!----',
347+
// comment consisting of '-'
348+
'!-----',
349+
// comment consisting of '--'
350+
'!------',
351+
// comment consisting of '!'
352+
'!!',
353+
'!-!',
354+
'!!-',
355+
'!-!-',
356+
'!-!--',
357+
'!--!--',
358+
// comment consisting of '!-'
359+
'!!---',
360+
'!-!---',
361+
'!--!---',
362+
// comment consisting of '!--'
363+
'!!----',
364+
'!-!----',
365+
'!--!----',
366+
// comment consisting of 's'
367+
'!s',
368+
'!s-',
369+
'!s--',
370+
'!-s',
371+
'!-s-',
372+
'!-s--',
373+
'!--s--',
374+
// notably does not work, probably a bug:
375+
// '!--!',
376+
// '!--!-',
377+
// '!--s',
378+
// '!--s-',
379+
];
380+
for (const c of StrangeComments) {
381+
test(`strange handlebars comments {{${c}}}`, `<div>{{${c}}}content</div>`, [
382+
'<div>',
383+
[s`content`],
384+
]);
385+
test(`strange handlebars comments {{~${c}}}`, `<div> {{~${c}}}content</div>`, [
386+
'<div>',
387+
[s`content`],
388+
]);
389+
test(`strange handlebars comments {{${c}~}}`, `<div>{{${c}~}} content</div>`, [
390+
'<div>',
391+
[s`content`],
392+
]);
393+
test(`strange handlebars comments {{~${c}~}}`, `<div> {{~${c}~}} content</div>`, [
394+
'<div>',
395+
[s`content`],
396+
]);
397+
}
398+
317399
test('namespaced attribute', `<svg xlink:title='svg-title'>content</svg>`, [
318400
'<svg>',
319401
{ 'xlink:title': s`svg-title` },

packages/@glimmer/syntax/lib/source/loc/span.ts

+7-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { localAssert } from '@glimmer/debug-util';
12
import { LOCAL_DEBUG } from '@glimmer/local-debug-flags';
23
import { assertNever } from '@glimmer/util';
34

@@ -206,23 +207,16 @@ export class SourceSpan implements SourceLocation {
206207
}
207208

208209
/**
209-
* Convert this `SourceSpan` into a `SourceSlice`. In debug mode, this method optionally checks
210-
* that the byte offsets represented by this `SourceSpan` actually correspond to the expected
211-
* string.
210+
* Convert this `SourceSpan` into a `SourceSlice`.
212211
*/
213212
toSlice(expected?: string): SourceSlice {
214213
const chars = this.data.asString();
215214

216-
if (import.meta.env.DEV) {
217-
if (expected !== undefined && chars !== expected) {
218-
// eslint-disable-next-line no-console
219-
console.warn(
220-
`unexpectedly found ${JSON.stringify(
221-
chars
222-
)} when slicing source, but expected ${JSON.stringify(expected)}`
223-
);
224-
}
225-
}
215+
localAssert(
216+
expected === undefined || expected === chars,
217+
`unexpectedly found ${JSON.stringify(chars)} when slicing source, ` +
218+
`but expected ${JSON.stringify(expected)}`
219+
);
226220

227221
return new SourceSlice({
228222
loc: this,

packages/@glimmer/syntax/lib/v2/normalize.ts

+43-5
Original file line numberDiff line numberDiff line change
@@ -390,17 +390,55 @@ class StatementNormalizer {
390390

391391
MustacheCommentStatement(node: ASTv1.MustacheCommentStatement): ASTv2.GlimmerComment {
392392
let loc = this.block.loc(node.loc);
393-
let textLoc: SourceSpan;
394393

395-
if (loc.asString().slice(0, 5) === '{{!--') {
396-
textLoc = loc.slice({ skipStart: 5, skipEnd: 4 });
394+
// If someone cares for these cases to have the right loc, feel free to attempt:
395+
// {{!}} {{~!}} {{!~}} {{~!~}}
396+
// {{!-}} {{~!-}} {{!-~}} {{~!-~}}
397+
// {{!--}} {{~!--}} {{!--~}} {{~!--~}}
398+
// {{!---}} {{~!---}} {{!---~}} {{~!---~}}
399+
// {{!----}} {{~!----}} {{!----~}} {{~!----~}}
400+
if (node.value === '') {
401+
return new ASTv2.GlimmerComment({
402+
loc,
403+
text: SourceSlice.synthetic(''),
404+
});
405+
}
406+
407+
let source = loc.asString();
408+
let span = loc;
409+
410+
if (node.value.startsWith('-')) {
411+
localAssert(
412+
/^\{\{~?!---/u.test(source),
413+
`to start a comment's content with a '-', it must have started with {{!--`
414+
);
415+
span = span.sliceStartChars({
416+
skipStart: source.startsWith('{{~') ? 6 : 5,
417+
chars: node.value.length,
418+
});
419+
} else if (node.value.endsWith('-')) {
420+
localAssert(
421+
/--~?\}\}/u.test(source),
422+
`to end a comment's content with a '-', it must have ended with --}}`
423+
);
424+
425+
const skipEnd = source.endsWith('~}}') ? 5 : 4;
426+
const skipStart = source.length - node.value.length - skipEnd;
427+
428+
span = span.slice({
429+
skipStart,
430+
skipEnd,
431+
});
397432
} else {
398-
textLoc = loc.slice({ skipStart: 3, skipEnd: 2 });
433+
span = span.sliceStartChars({
434+
skipStart: source.lastIndexOf(node.value),
435+
chars: node.value.length,
436+
});
399437
}
400438

401439
return new ASTv2.GlimmerComment({
402440
loc,
403-
text: textLoc.toSlice(node.value),
441+
text: span.toSlice(node.value),
404442
});
405443
}
406444

packages/@glimmer/syntax/test/loc-node-test.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,12 @@ test('handlebars comment', () => {
585585
{{!-- derp herky --}}<div></div>
586586
</div>
587587
<span {{! derpy }}></span>
588+
589+
{{~!~}}
590+
588591
`);
589592

590-
let [, div, , span] = ast.body;
593+
let [, div, , span, standaloneComment] = ast.body;
591594
if (assertNodeType(div, 'ElementNode')) {
592595
let [comment1, , comment2, trailingDiv] = div.children;
593596
locEqual(comment1, 2, 9, 2, 39);
@@ -598,6 +601,7 @@ test('handlebars comment', () => {
598601
locEqual(span, 5, 4, 5, 30);
599602
locEqual(comment3, 5, 10, 5, 22);
600603
}
604+
locEqual(standaloneComment, 7, 6, 7, 13);
601605
}
602606
});
603607

packages/@glimmer/syntax/test/parser-node-test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,11 @@ test('a Handlebars comment', () => {
794794
);
795795
});
796796

797+
test('a Handlebars comment with whitespace removal', function () {
798+
let t = 'before {{~! some comment ~}} after';
799+
astEqual(t, b.program([b.text('before'), b.mustacheComment(' some comment '), b.text('after')]));
800+
});
801+
797802
test('a Handlebars comment in proper element space', () => {
798803
let t = 'before <div {{! some comment }} data-foo="bar" {{! other comment }}></div> after';
799804
astEqual(

0 commit comments

Comments
 (0)