Skip to content

Commit a69c809

Browse files
authored
feat(eslint-plugin-template): [no-call-expression] add allowList option (#1217)
1 parent de2994c commit a69c809

File tree

3 files changed

+96
-6
lines changed

3 files changed

+96
-6
lines changed

packages/eslint-plugin-template/docs/rules/no-call-expression.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ Disallows calling expressions in templates, except for output handlers
2323

2424
## Rule Options
2525

26-
The rule does not have any configuration options.
26+
The rule accepts an options object with the following properties:
27+
28+
```ts
29+
interface Options {
30+
/**
31+
* Default: `[]`
32+
*/
33+
allowList?: string[];
34+
}
35+
36+
```
2737

2838
<br>
2939

@@ -391,6 +401,39 @@ The rule does not have any configuration options.
391401
<form [formGroup]="form" (ngSubmit)="id ? save() : edit()"></form>
392402
```
393403

404+
<br>
405+
406+
---
407+
408+
<br>
409+
410+
#### Custom Config
411+
412+
```json
413+
{
414+
"rules": {
415+
"@angular-eslint/template/no-call-expression": [
416+
"error",
417+
{
418+
"allowList": [
419+
"nested",
420+
"getHref"
421+
]
422+
}
423+
]
424+
}
425+
}
426+
```
427+
428+
<br>
429+
430+
#### ✅ Valid Code
431+
432+
```html
433+
{{ obj?.nested() }} {{ obj!.nested() }}
434+
<a [href]="getHref()">info</a>
435+
```
436+
394437
</details>
395438

396439
<br>

packages/eslint-plugin-template/src/rules/no-call-expression.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import type { Call } from '@angular-eslint/bundled-angular-compiler';
1+
import type { AST, Call } from '@angular-eslint/bundled-angular-compiler';
22
import { TmplAstBoundEvent } from '@angular-eslint/bundled-angular-compiler';
33
import { ensureTemplateParser } from '@angular-eslint/utils';
44
import { createESLintRule } from '../utils/create-eslint-rule';
55
import { getNearestNodeFrom } from '../utils/get-nearest-node-from';
66

7-
type Options = [];
7+
type Options = [
8+
{
9+
readonly allowList?: readonly string[];
10+
},
11+
];
812
export type MessageIds = 'noCallExpression';
913
export const RULE_NAME = 'no-call-expression';
1014

@@ -17,13 +21,25 @@ export default createESLintRule<Options, MessageIds>({
1721
'Disallows calling expressions in templates, except for output handlers',
1822
recommended: false,
1923
},
20-
schema: [],
24+
schema: [
25+
{
26+
additionalProperties: false,
27+
properties: {
28+
allowList: {
29+
items: { type: 'string' },
30+
type: 'array',
31+
uniqueItems: true,
32+
},
33+
},
34+
type: 'object',
35+
},
36+
],
2137
messages: {
2238
noCallExpression: 'Avoid calling expressions in templates',
2339
},
2440
},
25-
defaultOptions: [],
26-
create(context) {
41+
defaultOptions: [{ allowList: [] }],
42+
create(context, [{ allowList }]) {
2743
ensureTemplateParser(context);
2844
const sourceCode = context.getSourceCode();
2945

@@ -35,6 +51,8 @@ export default createESLintRule<Options, MessageIds>({
3551

3652
if (isChildOfBoundEvent) return;
3753

54+
if (isCallNameInAllowList(node.receiver, allowList)) return;
55+
3856
const {
3957
sourceSpan: { start, end },
4058
} = node;
@@ -53,3 +71,21 @@ export default createESLintRule<Options, MessageIds>({
5371
function isBoundEvent(node: unknown): node is TmplAstBoundEvent {
5472
return node instanceof TmplAstBoundEvent;
5573
}
74+
75+
function isASTWithName(
76+
ast: AST & { name?: string },
77+
): ast is AST & { name: string } {
78+
return !!ast.name;
79+
}
80+
81+
function isCallNameInAllowList(
82+
ast: AST & { name?: string },
83+
allowList?: readonly string[],
84+
): boolean | undefined {
85+
return (
86+
allowList &&
87+
allowList.length > 0 &&
88+
isASTWithName(ast) &&
89+
allowList.indexOf(ast.name) > -1
90+
);
91+
}

packages/eslint-plugin-template/tests/rules/no-call-expression/cases.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ export const valid = [
1111
'<form [formGroup]="form" (ngSubmit)="form.valid || save()"></form>',
1212
'<form [formGroup]="form" (ngSubmit)="form.valid && save()"></form>',
1313
'<form [formGroup]="form" (ngSubmit)="id ? save() : edit()"></form>',
14+
{
15+
code: `
16+
{{ obj?.nested() }} {{ obj!.nested() }}
17+
<a [href]="getHref()">info</a>
18+
`,
19+
options: [
20+
{
21+
allowList: ['nested', 'getHref'],
22+
},
23+
],
24+
},
1425
];
1526

1627
export const invalid = [

0 commit comments

Comments
 (0)