From a69c809cd31f142d2f5aff1c34afeb6e4a607a9c Mon Sep 17 00:00:00 2001
From: Sandi Barr <2250413+sandikbarr@users.noreply.github.com>
Date: Thu, 24 Nov 2022 03:08:35 -0600
Subject: [PATCH] feat(eslint-plugin-template): [no-call-expression] add
allowList option (#1217)
---
.../docs/rules/no-call-expression.md | 45 +++++++++++++++++-
.../src/rules/no-call-expression.ts | 46 +++++++++++++++++--
.../tests/rules/no-call-expression/cases.ts | 11 +++++
3 files changed, 96 insertions(+), 6 deletions(-)
diff --git a/packages/eslint-plugin-template/docs/rules/no-call-expression.md b/packages/eslint-plugin-template/docs/rules/no-call-expression.md
index 494af1678..643d7b045 100644
--- a/packages/eslint-plugin-template/docs/rules/no-call-expression.md
+++ b/packages/eslint-plugin-template/docs/rules/no-call-expression.md
@@ -23,7 +23,17 @@ Disallows calling expressions in templates, except for output handlers
## Rule Options
-The rule does not have any configuration options.
+The rule accepts an options object with the following properties:
+
+```ts
+interface Options {
+ /**
+ * Default: `[]`
+ */
+ allowList?: string[];
+}
+
+```
@@ -391,6 +401,39 @@ The rule does not have any configuration options.
```
+
+
+---
+
+
+
+#### Custom Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/template/no-call-expression": [
+ "error",
+ {
+ "allowList": [
+ "nested",
+ "getHref"
+ ]
+ }
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```html
+{{ obj?.nested() }} {{ obj!.nested() }}
+info
+```
+
diff --git a/packages/eslint-plugin-template/src/rules/no-call-expression.ts b/packages/eslint-plugin-template/src/rules/no-call-expression.ts
index 1910b76fe..53ff1a951 100644
--- a/packages/eslint-plugin-template/src/rules/no-call-expression.ts
+++ b/packages/eslint-plugin-template/src/rules/no-call-expression.ts
@@ -1,10 +1,14 @@
-import type { Call } from '@angular-eslint/bundled-angular-compiler';
+import type { AST, Call } from '@angular-eslint/bundled-angular-compiler';
import { TmplAstBoundEvent } from '@angular-eslint/bundled-angular-compiler';
import { ensureTemplateParser } from '@angular-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
import { getNearestNodeFrom } from '../utils/get-nearest-node-from';
-type Options = [];
+type Options = [
+ {
+ readonly allowList?: readonly string[];
+ },
+];
export type MessageIds = 'noCallExpression';
export const RULE_NAME = 'no-call-expression';
@@ -17,13 +21,25 @@ export default createESLintRule({
'Disallows calling expressions in templates, except for output handlers',
recommended: false,
},
- schema: [],
+ schema: [
+ {
+ additionalProperties: false,
+ properties: {
+ allowList: {
+ items: { type: 'string' },
+ type: 'array',
+ uniqueItems: true,
+ },
+ },
+ type: 'object',
+ },
+ ],
messages: {
noCallExpression: 'Avoid calling expressions in templates',
},
},
- defaultOptions: [],
- create(context) {
+ defaultOptions: [{ allowList: [] }],
+ create(context, [{ allowList }]) {
ensureTemplateParser(context);
const sourceCode = context.getSourceCode();
@@ -35,6 +51,8 @@ export default createESLintRule({
if (isChildOfBoundEvent) return;
+ if (isCallNameInAllowList(node.receiver, allowList)) return;
+
const {
sourceSpan: { start, end },
} = node;
@@ -53,3 +71,21 @@ export default createESLintRule({
function isBoundEvent(node: unknown): node is TmplAstBoundEvent {
return node instanceof TmplAstBoundEvent;
}
+
+function isASTWithName(
+ ast: AST & { name?: string },
+): ast is AST & { name: string } {
+ return !!ast.name;
+}
+
+function isCallNameInAllowList(
+ ast: AST & { name?: string },
+ allowList?: readonly string[],
+): boolean | undefined {
+ return (
+ allowList &&
+ allowList.length > 0 &&
+ isASTWithName(ast) &&
+ allowList.indexOf(ast.name) > -1
+ );
+}
diff --git a/packages/eslint-plugin-template/tests/rules/no-call-expression/cases.ts b/packages/eslint-plugin-template/tests/rules/no-call-expression/cases.ts
index 66cd0fb96..458a4cf5e 100644
--- a/packages/eslint-plugin-template/tests/rules/no-call-expression/cases.ts
+++ b/packages/eslint-plugin-template/tests/rules/no-call-expression/cases.ts
@@ -11,6 +11,17 @@ export const valid = [
'',
'',
'',
+ {
+ code: `
+ {{ obj?.nested() }} {{ obj!.nested() }}
+ info
+ `,
+ options: [
+ {
+ allowList: ['nested', 'getHref'],
+ },
+ ],
+ },
];
export const invalid = [