Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin-template): [no-interpolation-in-attributes] new rule #1242

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,228 @@
<!--

DO NOT EDIT.

This markdown file was autogenerated using a mixture of the following files as the source of truth for its data:
- ../../src/rules/no-interpolation-in-attributes.ts
- ../../tests/rules/no-interpolation-in-attributes/cases.ts

In order to update this file, it is therefore those files which need to be updated, as well as potentially the generator script:
- ../../../../tools/scripts/generate-rule-docs.ts

-->

<br>

# `@angular-eslint/template/no-interpolation-in-attributes`

Ensures that property-binding is used instead of interpolation in attributes.

- Type: suggestion

<br>

## Rule Options

The rule does not have any configuration options.

<br>

## Usage Examples

> The following examples are generated automatically from the actual unit tests within the plugin, so you can be assured that their behavior is accurate based on the current commit.

<br>

<details>
<summary>❌ - Toggle examples of <strong>incorrect</strong> code for this rule</summary>

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<input type="text" name="{{ foo }}">
~~~~~~~~~
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<input type="text" name="{{ foo }}bar">
~~~~~~~~~~~~
```

</details>

<br>

---

<br>

<details>
<summary>✅ - Toggle examples of <strong>correct</strong> code for this rule</summary>

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```html
<input type="text" [name]="foo">
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```html
<input type="text" name="foo" [(ngModel)]="foo">
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```html
<input type="text" [name]="foo + 'bar'">
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```html
<input type="text" [name]="foo | bar">
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/template/no-interpolation-in-attributes": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```html
<div>{{ content }}</div>
```

</details>

<br>
1 change: 1 addition & 0 deletions packages/eslint-plugin-template/src/configs/all.json
Expand Up @@ -24,6 +24,7 @@
"@angular-eslint/template/no-distracting-elements": "error",
"@angular-eslint/template/no-duplicate-attributes": "error",
"@angular-eslint/template/no-inline-styles": "error",
"@angular-eslint/template/no-interpolation-in-attributes": "error",
"@angular-eslint/template/no-negated-async": "error",
"@angular-eslint/template/no-positive-tabindex": "error",
"@angular-eslint/template/use-track-by-function": "error"
Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin-template/src/index.ts
Expand Up @@ -75,6 +75,9 @@ import noPositiveTabindex, {
import useTrackByFunction, {
RULE_NAME as useTrackByFunctionRuleName,
} from './rules/use-track-by-function';
import noInterpolationInAttributes, {
RULE_NAME as noInterpolationInAttributesRuleName,
} from './rules/no-interpolation-in-attributes';

export default {
configs: {
Expand Down Expand Up @@ -114,5 +117,6 @@ export default {
[noNegatedAsyncRuleName]: noNegatedAsync,
[noPositiveTabindexRuleName]: noPositiveTabindex,
[useTrackByFunctionRuleName]: useTrackByFunction,
[noInterpolationInAttributesRuleName]: noInterpolationInAttributes,
},
};
@@ -0,0 +1,43 @@
import type { Interpolation } from '@angular-eslint/bundled-angular-compiler';
import { createESLintRule } from '../utils/create-eslint-rule';

type Options = [];
export const MESSAGE_ID = 'noInterpolationInAttributes';
export const RULE_NAME = 'no-interpolation-in-attributes';

export default createESLintRule<Options, typeof MESSAGE_ID>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description:
'Ensures that property-binding is used instead of interpolation in attributes.',
recommended: false,
},
schema: [],
messages: {
[MESSAGE_ID]:
'Use property binding [attribute]="value" instead of interpolation {{ value }} for an attribute.',
},
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();

return {
['BoundAttribute Interpolation'](interpolation: Interpolation) {
const {
sourceSpan: { start, end },
} = interpolation;

context.report({
loc: {
start: sourceCode.getLocFromIndex(start),
end: sourceCode.getLocFromIndex(end),
},
messageId: MESSAGE_ID,
});
},
};
},
});
@@ -0,0 +1,40 @@
import { convertAnnotatedSourceToFailureCase } from '@angular-eslint/utils';
import { MESSAGE_ID } from '../../../src/rules/no-interpolation-in-attributes';

const messageId = MESSAGE_ID;

export const valid = [
'<input type="text" [name]="foo">',
'<input type="text" name="foo" [(ngModel)]="foo">',
'<input type="text" [name]="foo + \'bar\'">',
'<input type="text" [name]="foo | bar">',
'<div>{{ content }}</div>',
];

export const invalid = [
convertAnnotatedSourceToFailureCase({
description: 'it should fail if interpolation is used as attribute value',
annotatedSource: `
<input type="text" name="{{ foo }}">
~~~~~~~~~
`,
messageId,
annotatedOutput: `
<input type="text" name="{{ foo }}">
~~~~~~~~~
`,
}),
convertAnnotatedSourceToFailureCase({
description:
'it should fail if interpolation is used as part of attribute value',
annotatedSource: `
<input type="text" name="{{ foo }}bar">
~~~~~~~~~~~~
`,
messageId,
annotatedOutput: `
<input type="text" name="{{ foo }}bar">
~~~~~~~~~~~~
`,
}),
];
@@ -0,0 +1,14 @@
import { RuleTester } from '@angular-eslint/utils';
import rule, {
RULE_NAME,
} from '../../../src/rules/no-interpolation-in-attributes';
import { invalid, valid } from './cases';

const ruleTester = new RuleTester({
parser: '@angular-eslint/template-parser',
});

ruleTester.run(RULE_NAME, rule, {
valid,
invalid,
});
2 changes: 1 addition & 1 deletion packages/template-parser/src/convert-source-span-to-loc.ts
Expand Up @@ -33,7 +33,7 @@ export function convertElementSourceSpanToLoc(
// We explicitly throw an exception since this function should not be used
// with non-element nodes, e.g. `TextAttribute` or `MethodDefinition`, etc.
throw new Error(
'convertElementSourceSpanToLoc is intented to be used only with elements.',
'convertElementSourceSpanToLoc is intended to be used only with elements.',
);
}

Expand Down