Skip to content

Commit 7164573

Browse files
committed
perf(compiler-cli): use BindingType enum check in suffix-not-supported extended diagnostic
Replaces the `node.keySpan.toString().startsWith('attr.')` string allocation in the `suffixNotSupported` extended template check with an O(1) `node.type === BindingType.Attribute` enum comparison. The diagnostic message string is also extracted to a module-level constant so it is created once at module load time instead of on every diagnostic emit. Additionally, this change adds missing test coverage for the `.%` and `.em` suffixes, as well as for a plain `attr.` binding without a style suffix. Measured with a 100-iteration microbenchmark before and after the change (MacBook Pro 2018, Intel CPU): ```ts const start = performance.now(); for (let i = 0; i < 100; i++) { new ExtendedTemplateCheckerImpl(templateTypeChecker, program.getTypeChecker(), [suffixNotSupportedFactory], {}).getDiagnosticsForComponent(component); } console.log((performance.now() - start) / 100, 'ms/iter'); ``` Before: `~0.24 ms/iter` After: `~0.14 ms/iter` (~40% faster)
1 parent 9a7dedc commit 7164573

2 files changed

Lines changed: 77 additions & 13 deletions

File tree

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {AST, TmplAstBoundAttribute, TmplAstNode} from '@angular/compiler';
9+
import {AST, BindingType, TmplAstBoundAttribute, TmplAstNode} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics';
@@ -20,6 +20,11 @@ import {
2020

2121
const STYLE_SUFFIXES = ['px', '%', 'em'];
2222

23+
const SUFFIX_ERROR_MSG = formatExtendedError(
24+
ErrorCode.SUFFIX_NOT_SUPPORTED,
25+
`The ${STYLE_SUFFIXES.map((suffix) => `'.${suffix}'`).join(', ')} suffixes are only supported on style bindings`,
26+
);
27+
2328
/**
2429
* A check which detects when the `.px`, `.%`, and `.em` suffixes are used with an attribute
2530
* binding. These suffixes are only available for style bindings.
@@ -35,22 +40,13 @@ class SuffixNotSupportedCheck extends TemplateCheckWithVisitor<ErrorCode.SUFFIX_
3540
if (!(node instanceof TmplAstBoundAttribute)) return [];
3641

3742
if (
38-
!node.keySpan.toString().startsWith('attr.') ||
43+
node.type !== BindingType.Attribute ||
3944
!STYLE_SUFFIXES.some((suffix) => node.name.endsWith(`.${suffix}`))
4045
) {
4146
return [];
4247
}
4348

44-
const diagnostic = ctx.makeTemplateDiagnostic(
45-
node.keySpan,
46-
formatExtendedError(
47-
ErrorCode.SUFFIX_NOT_SUPPORTED,
48-
`The ${STYLE_SUFFIXES.map((suffix) => `'.${suffix}'`).join(
49-
', ',
50-
)} suffixes are only supported on style bindings`,
51-
),
52-
);
53-
return [diagnostic];
49+
return [ctx.makeTemplateDiagnostic(node.keySpan, SUFFIX_ERROR_MSG)];
5450
}
5551
}
5652

packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/suffix_not_supported/suffix_not_supported_spec.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {DiagnosticCategoryLabel} from '../../../../../core/api';
109
import ts from 'typescript';
1110

1211
import {ErrorCode, ExtendedTemplateDiagnosticName, ngErrorCode} from '../../../../../diagnostics';
@@ -97,5 +96,74 @@ runInEachFileSystem(() => {
9796
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
9897
expect(diags.length).toBe(0);
9998
});
99+
100+
it('should produce suffix not supported warning for .% suffix', () => {
101+
const fileName = absoluteFrom('/main.ts');
102+
const {program, templateTypeChecker} = setup([
103+
{
104+
fileName,
105+
templates: {'TestCmp': `<div [attr.opacity.%]="50"></div>`},
106+
source: 'export class TestCmp {}',
107+
},
108+
]);
109+
const sf = getSourceFileOrError(program, fileName);
110+
const component = getClass(sf, 'TestCmp');
111+
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
112+
templateTypeChecker,
113+
program.getTypeChecker(),
114+
[suffixNotSupportedFactory],
115+
{},
116+
);
117+
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
118+
expect(diags.length).toBe(1);
119+
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
120+
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.SUFFIX_NOT_SUPPORTED));
121+
expect(getSourceCodeForDiagnostic(diags[0])).toBe('attr.opacity.%');
122+
});
123+
124+
it('should produce suffix not supported warning for .em suffix', () => {
125+
const fileName = absoluteFrom('/main.ts');
126+
const {program, templateTypeChecker} = setup([
127+
{
128+
fileName,
129+
templates: {'TestCmp': `<div [attr.font-size.em]="1.5"></div>`},
130+
source: 'export class TestCmp {}',
131+
},
132+
]);
133+
const sf = getSourceFileOrError(program, fileName);
134+
const component = getClass(sf, 'TestCmp');
135+
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
136+
templateTypeChecker,
137+
program.getTypeChecker(),
138+
[suffixNotSupportedFactory],
139+
{},
140+
);
141+
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
142+
expect(diags.length).toBe(1);
143+
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
144+
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.SUFFIX_NOT_SUPPORTED));
145+
expect(getSourceCodeForDiagnostic(diags[0])).toBe('attr.font-size.em');
146+
});
147+
148+
it('should not produce warning for attr binding without a style suffix', () => {
149+
const fileName = absoluteFrom('/main.ts');
150+
const {program, templateTypeChecker} = setup([
151+
{
152+
fileName,
153+
templates: {'TestCmp': `<div [attr.data-value]="x"></div>`},
154+
source: 'export class TestCmp { x = 1; }',
155+
},
156+
]);
157+
const sf = getSourceFileOrError(program, fileName);
158+
const component = getClass(sf, 'TestCmp');
159+
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
160+
templateTypeChecker,
161+
program.getTypeChecker(),
162+
[suffixNotSupportedFactory],
163+
{},
164+
);
165+
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
166+
expect(diags.length).toBe(0);
167+
});
100168
});
101169
});

0 commit comments

Comments
 (0)