Skip to content

Commit b19fda0

Browse files
SkyZeroZxatscott
authored andcommitted
refactor(compiler-cli): Add diagnostics for idle prefetch triggers
Extends the extended diagnostic for `@defer` trigger misconfiguration to include `on idle` triggers
1 parent 11f047f commit b19fda0

2 files changed

Lines changed: 78 additions & 13 deletions

File tree

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/index.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TmplAstDeferredBlock,
1313
TmplAstDeferredTrigger,
1414
TmplAstHoverDeferredTrigger,
15+
TmplAstIdleDeferredTrigger,
1516
TmplAstImmediateDeferredTrigger,
1617
TmplAstInteractionDeferredTrigger,
1718
TmplAstTimerDeferredTrigger,
@@ -60,6 +61,26 @@ function areLiteralMapsEqual(a: LiteralMap | null, b: LiteralMap | null): boolea
6061
return true;
6162
}
6263

64+
function getTimedTriggerValue(
65+
trigger: TmplAstTimerDeferredTrigger | TmplAstIdleDeferredTrigger,
66+
): number | null {
67+
if (trigger instanceof TmplAstTimerDeferredTrigger) {
68+
return trigger.delay;
69+
}
70+
71+
return trigger.timeout;
72+
}
73+
74+
function formatTimedTrigger(
75+
trigger: TmplAstTimerDeferredTrigger | TmplAstIdleDeferredTrigger,
76+
): string {
77+
if (trigger instanceof TmplAstTimerDeferredTrigger) {
78+
return `timer(${trigger.delay}ms)`;
79+
}
80+
81+
return trigger.timeout === null ? 'idle' : `idle(${trigger.timeout}ms)`;
82+
}
83+
6384
/**
6485
* This check implements warnings for unreachable or redundant @defer triggers.
6586
* Emits ErrorCode.DEFER_TRIGGER_MISCONFIGURATION with messages matching the project's
@@ -119,21 +140,36 @@ class DeferTriggerMisconfiguration extends TemplateCheckWithVisitor<ErrorCode.DE
119140
const main = mains[0];
120141

121142
for (const pre of prefetches) {
122-
// Timer vs Timer: warn when prefetch delay >= main delay
123-
const isTimerTriggger =
143+
// Delay-based pairs (timer/idle): warn when prefetch fires no sooner than main.
144+
145+
const isTimerPair =
124146
main instanceof TmplAstTimerDeferredTrigger && pre instanceof TmplAstTimerDeferredTrigger;
125-
if (isTimerTriggger) {
126-
const mainDelay = main.delay;
127-
const preDelay = pre.delay;
128-
if (preDelay >= mainDelay) {
129-
const msg = `The Prefetch 'timer(${preDelay}ms)' is not scheduled before the main 'timer(${mainDelay}ms)', so it won’t run prior to rendering. Lower the prefetch delay or remove it.`;
130-
diags.push(
131-
ctx.makeTemplateDiagnostic(
132-
pre.sourceSpan ?? node.sourceSpan,
133-
formatExtendedError(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION, msg),
134-
),
135-
);
147+
148+
const isIdlePair =
149+
main instanceof TmplAstIdleDeferredTrigger && pre instanceof TmplAstIdleDeferredTrigger;
150+
151+
if (isTimerPair || isIdlePair) {
152+
const mainVal = getTimedTriggerValue(main);
153+
const preVal = getTimedTriggerValue(pre);
154+
const sameUntimedIdle = mainVal == null && preVal == null;
155+
const comparableTimedPair = mainVal != null && preVal != null && preVal >= mainVal;
156+
157+
if (!sameUntimedIdle && !comparableTimedPair) {
158+
continue;
136159
}
160+
161+
const msg =
162+
`The Prefetch '${formatTimedTrigger(pre)}' is not scheduled before the main '${formatTimedTrigger(main)}',` +
163+
` so it won't run prior to rendering. Lower the prefetch timing or remove it.`;
164+
165+
diags.push(
166+
ctx.makeTemplateDiagnostic(
167+
pre.sourceSpan ?? node.sourceSpan,
168+
formatExtendedError(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION, msg),
169+
),
170+
);
171+
172+
continue;
137173
}
138174

139175
// Reference-based triggers (hover/interaction/viewport): warn if both

packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/defer_trigger_misconfiguration_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,35 @@ runInEachFileSystem(() => {
6767
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
6868
});
6969

70+
it('should emit when prefetch idle timeout >= main idle timeout', () => {
71+
const diags = getDiags(`@defer (on idle(1s); prefetch on idle(2s)) { <div></div> }`);
72+
expect(diags.length).toBe(1);
73+
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
74+
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
75+
});
76+
77+
it('should not emit when prefetch idle timeout < main idle timeout', () => {
78+
const diags = getDiags(`@defer (on idle(2s); prefetch on idle(1s)) { <div></div> }`);
79+
expect(diags.length).toBe(0);
80+
});
81+
82+
it('should emit when prefetch idle matches main idle with no timeout', () => {
83+
const diags = getDiags(`@defer (on idle; prefetch on idle) { <div></div> }`);
84+
expect(diags.length).toBe(1);
85+
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
86+
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
87+
});
88+
89+
it('should not emit when only main idle has timeout', () => {
90+
const diags = getDiags(`@defer (on idle(1s); prefetch on idle) { <div></div> }`);
91+
expect(diags.length).toBe(0);
92+
});
93+
94+
it('should not emit when only prefetch idle has timeout', () => {
95+
const diags = getDiags(`@defer (on idle; prefetch on idle(1s)) { <div></div> }`);
96+
expect(diags.length).toBe(0);
97+
});
98+
7099
it('should emit when prefetch identical to main viewport/interaction/hover', () => {
71100
const diags = getDiags(
72101
`@defer (on viewport(ref); prefetch on viewport(ref)) { <div></div> }`,

0 commit comments

Comments
 (0)