Skip to content

Commit 6433671

Browse files
crisbetoAndrewKushnir
authored andcommitted
refactor(compiler): track span information about defer trigger parameters (angular#64130)
Currently we produce the string value of a defer `on` trigger by concatenating the string values of all of its tokens. This ends up ignoring whitespaces which in turn throws off source spans. These changes switch to producing the string by taking the text between the start and end tokens, as well as tracking the start index. PR Close angular#64130
1 parent 8654020 commit 6433671

File tree

1 file changed

+41
-29
lines changed

1 file changed

+41
-29
lines changed

packages/compiler/src/render3/r3_deferred_triggers.ts

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@ enum OnTriggerType {
3939
}
4040

4141
/** Function that validates the structure of a reference-based trigger. */
42-
type ReferenceTriggerValidator = (type: OnTriggerType, parameters: string[]) => void;
42+
type ReferenceTriggerValidator = (type: OnTriggerType, parameters: ParsedParameter[]) => void;
43+
44+
/** Parsed information about a defer trigger parameter. */
45+
interface ParsedParameter {
46+
/** Expression of the parameter. */
47+
expression: string;
48+
49+
/** Index within the trigger at which the parameter starts. */
50+
start: number;
51+
}
4352

4453
/** Parses a `when` deferred trigger. */
4554
export function parseNeverTrigger(
@@ -224,7 +233,7 @@ class OnTriggerParser {
224233
return this.tokens[Math.min(this.index, this.tokens.length - 1)];
225234
}
226235

227-
private consumeTrigger(identifier: Token, parameters: string[]) {
236+
private consumeTrigger(identifier: Token, parameters: ParsedParameter[]) {
228237
const triggerNameStartSpan = this.span.start.moveBy(
229238
this.start + identifier.index - this.tokens[0].index,
230239
);
@@ -315,7 +324,6 @@ class OnTriggerParser {
315324
this.prefetchSpan,
316325
this.onSourceSpan,
317326
this.hydrateSpan,
318-
this.placeholder,
319327
this.validator,
320328
),
321329
);
@@ -344,8 +352,8 @@ class OnTriggerParser {
344352
}
345353
}
346354

347-
private consumeParameters(): string[] {
348-
const parameters: string[] = [];
355+
private consumeParameters(): ParsedParameter[] {
356+
const parameters: ParsedParameter[] = [];
349357

350358
if (!this.token().isCharacter(chars.$LPAREN)) {
351359
this.unexpectedToken(this.token());
@@ -355,7 +363,7 @@ class OnTriggerParser {
355363
this.advance();
356364

357365
const commaDelimStack: number[] = [];
358-
let current = '';
366+
let tokens: Token[] = [];
359367

360368
while (this.index < this.tokens.length) {
361369
const token = this.token();
@@ -364,8 +372,8 @@ class OnTriggerParser {
364372
// Note that we don't need to account for strings here since the lexer already parsed them
365373
// into string tokens.
366374
if (token.isCharacter(chars.$RPAREN) && commaDelimStack.length === 0) {
367-
if (current.length) {
368-
parameters.push(current);
375+
if (tokens.length) {
376+
parameters.push({expression: this.tokenRangeText(tokens), start: tokens[0].index});
369377
}
370378
break;
371379
}
@@ -389,15 +397,15 @@ class OnTriggerParser {
389397

390398
// If we hit a comma outside of a comma-delimited syntax, it means
391399
// that we're at the top level and we're starting a new parameter.
392-
if (commaDelimStack.length === 0 && token.isCharacter(chars.$COMMA) && current.length > 0) {
393-
parameters.push(current);
394-
current = '';
400+
if (commaDelimStack.length === 0 && token.isCharacter(chars.$COMMA) && tokens.length > 0) {
401+
parameters.push({expression: this.tokenRangeText(tokens), start: tokens[0].index});
395402
this.advance();
403+
tokens = [];
396404
continue;
397405
}
398406

399407
// Otherwise treat the token as a plain text character in the current parameter.
400-
current += this.tokenText();
408+
tokens.push(token);
401409
this.advance();
402410
}
403411

@@ -415,10 +423,15 @@ class OnTriggerParser {
415423
return parameters;
416424
}
417425

418-
private tokenText(): string {
419-
// Tokens have a toString already which we could use, but for string tokens it omits the quotes.
420-
// Eventually we could expose this information on the token directly.
421-
return this.expression.slice(this.start + this.token().index, this.start + this.token().end);
426+
private tokenRangeText(tokens: Token[]): string {
427+
if (tokens.length === 0) {
428+
return '';
429+
}
430+
431+
return this.expression.slice(
432+
this.start + tokens[0].index,
433+
this.start + tokens[tokens.length - 1].end,
434+
);
422435
}
423436

424437
private trackTrigger(name: keyof t.DeferredBlockTriggers, trigger: t.DeferredTrigger): void {
@@ -451,7 +464,7 @@ function trackTrigger(
451464
}
452465

453466
function createIdleTrigger(
454-
parameters: string[],
467+
parameters: ParsedParameter[],
455468
nameSpan: ParseSourceSpan,
456469
sourceSpan: ParseSourceSpan,
457470
prefetchSpan: ParseSourceSpan | null,
@@ -466,7 +479,7 @@ function createIdleTrigger(
466479
}
467480

468481
function createTimerTrigger(
469-
parameters: string[],
482+
parameters: ParsedParameter[],
470483
nameSpan: ParseSourceSpan,
471484
sourceSpan: ParseSourceSpan,
472485
prefetchSpan: ParseSourceSpan | null,
@@ -477,7 +490,7 @@ function createTimerTrigger(
477490
throw new Error(`"${OnTriggerType.TIMER}" trigger must have exactly one parameter`);
478491
}
479492

480-
const delay = parseDeferredTime(parameters[0]);
493+
const delay = parseDeferredTime(parameters[0].expression);
481494

482495
if (delay === null) {
483496
throw new Error(`Could not parse time value of trigger "${OnTriggerType.TIMER}"`);
@@ -494,7 +507,7 @@ function createTimerTrigger(
494507
}
495508

496509
function createImmediateTrigger(
497-
parameters: string[],
510+
parameters: ParsedParameter[],
498511
nameSpan: ParseSourceSpan,
499512
sourceSpan: ParseSourceSpan,
500513
prefetchSpan: ParseSourceSpan | null,
@@ -515,18 +528,17 @@ function createImmediateTrigger(
515528
}
516529

517530
function createHoverTrigger(
518-
parameters: string[],
531+
parameters: ParsedParameter[],
519532
nameSpan: ParseSourceSpan,
520533
sourceSpan: ParseSourceSpan,
521534
prefetchSpan: ParseSourceSpan | null,
522535
onSourceSpan: ParseSourceSpan | null,
523536
hydrateSpan: ParseSourceSpan | null,
524-
placeholder: t.DeferredBlockPlaceholder | null,
525537
validator: ReferenceTriggerValidator,
526538
): t.HoverDeferredTrigger {
527539
validator(OnTriggerType.HOVER, parameters);
528540
return new t.HoverDeferredTrigger(
529-
parameters[0] ?? null,
541+
parameters[0]?.expression ?? null,
530542
nameSpan,
531543
sourceSpan,
532544
prefetchSpan,
@@ -536,7 +548,7 @@ function createHoverTrigger(
536548
}
537549

538550
function createInteractionTrigger(
539-
parameters: string[],
551+
parameters: ParsedParameter[],
540552
nameSpan: ParseSourceSpan,
541553
sourceSpan: ParseSourceSpan,
542554
prefetchSpan: ParseSourceSpan | null,
@@ -546,7 +558,7 @@ function createInteractionTrigger(
546558
): t.InteractionDeferredTrigger {
547559
validator(OnTriggerType.INTERACTION, parameters);
548560
return new t.InteractionDeferredTrigger(
549-
parameters[0] ?? null,
561+
parameters[0]?.expression ?? null,
550562
nameSpan,
551563
sourceSpan,
552564
prefetchSpan,
@@ -556,7 +568,7 @@ function createInteractionTrigger(
556568
}
557569

558570
function createViewportTrigger(
559-
parameters: string[],
571+
parameters: ParsedParameter[],
560572
nameSpan: ParseSourceSpan,
561573
sourceSpan: ParseSourceSpan,
562574
prefetchSpan: ParseSourceSpan | null,
@@ -566,7 +578,7 @@ function createViewportTrigger(
566578
): t.ViewportDeferredTrigger {
567579
validator(OnTriggerType.VIEWPORT, parameters);
568580
return new t.ViewportDeferredTrigger(
569-
parameters[0] ?? null,
581+
parameters[0]?.expression ?? null,
570582
nameSpan,
571583
sourceSpan,
572584
prefetchSpan,
@@ -580,7 +592,7 @@ function createViewportTrigger(
580592
* @param type Type of the trigger being validated.
581593
* @param parameters Parameters of the trigger.
582594
*/
583-
function validatePlainReferenceBasedTrigger(type: OnTriggerType, parameters: string[]) {
595+
function validatePlainReferenceBasedTrigger(type: OnTriggerType, parameters: ParsedParameter[]) {
584596
if (parameters.length > 1) {
585597
throw new Error(`"${type}" trigger can only have zero or one parameters`);
586598
}
@@ -591,7 +603,7 @@ function validatePlainReferenceBasedTrigger(type: OnTriggerType, parameters: str
591603
* @param type Type of the trigger being validated.
592604
* @param parameters Parameters of the trigger.
593605
*/
594-
function validateHydrateReferenceBasedTrigger(type: OnTriggerType, parameters: string[]) {
606+
function validateHydrateReferenceBasedTrigger(type: OnTriggerType, parameters: ParsedParameter[]) {
595607
if (parameters.length > 0) {
596608
throw new Error(`Hydration trigger "${type}" cannot have parameters`);
597609
}

0 commit comments

Comments
 (0)