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

fix(no-input-rename): allow input aliases that match the directive na… #1207

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
94 changes: 94 additions & 0 deletions packages/eslint-plugin/docs/rules/no-input-rename.md
Expand Up @@ -365,6 +365,38 @@ class Test {
}
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/no-input-rename": [
"error"
]
}
}
```

<br>

#### ❌ Invalid Code

```ts
@Directive({
selector: 'img[fooDirective]',
})
class Test {
@Input('notFooDirective') foo: Foo;
~~~~~~~~~~~~~~~~~
}
```

</details>

<br>
Expand Down Expand Up @@ -887,6 +919,68 @@ class Test {
}
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/no-input-rename": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```ts
@Directive({
selector: 'img[fooDirective]'
})
class Test {
@Input foo: Foo;
}
```

<br>

---

<br>

#### Default Config

```json
{
"rules": {
"@angular-eslint/no-input-rename": [
"error"
]
}
}
```

<br>

#### ✅ Valid Code

```ts
@Directive({
selector: 'img[fooDirective]'
})
class Test {
@Input('fooDirective') foo: Foo;
}
```

</details>

<br>
30 changes: 27 additions & 3 deletions packages/eslint-plugin/src/rules/no-input-rename.ts
Expand Up @@ -55,13 +55,21 @@ export default createESLintRule<Options, MessageIds>({
create(context, [{ allowedNames = [] }]) {
let selectors: ReadonlySet<string> = new Set();
const ariaAttributeKeys = getAriaAttributeKeys();
let selectorDirectiveName: string;

return {
[Selectors.COMPONENT_OR_DIRECTIVE_SELECTOR_LITERAL](
node: TSESTree.Literal | TSESTree.TemplateElement,
) {
const nodeRawText = ASTUtils.getRawText(node);
const bracketMatchResults = nodeRawText.match(/\[(.*?)\]/);

if (bracketMatchResults) {
selectorDirectiveName = bracketMatchResults[1];
}

selectors = new Set(
withoutBracketsAndWhitespaces(ASTUtils.getRawText(node)).split(','),
withoutBracketsAndWhitespaces(nodeRawText).split(','),
);
},
[Selectors.INPUT_ALIAS](
Expand Down Expand Up @@ -98,7 +106,14 @@ export default createESLintRule<Options, MessageIds>({
messageId: 'noInputRename',
fix: (fixer) => fixer.remove(node),
});
} else if (!isAliasNameAllowed(selectors, propertyName, aliasName)) {
} else if (
!isAliasNameAllowed(
selectors,
propertyName,
aliasName,
selectorDirectiveName,
)
) {
context.report({
node,
messageId: 'noInputRename',
Expand Down Expand Up @@ -147,7 +162,14 @@ export default createESLintRule<Options, MessageIds>({
ASTUtils.getReplacementText(node, propertyName),
),
});
} else if (!isAliasNameAllowed(selectors, propertyName, aliasName)) {
} else if (
!isAliasNameAllowed(
selectors,
propertyName,
aliasName,
selectorDirectiveName,
)
) {
context.report({
node,
messageId: 'noInputRename',
Expand Down Expand Up @@ -182,10 +204,12 @@ function isAliasNameAllowed(
selectors: ReadonlySet<string>,
propertyName: string,
aliasName: string,
selectorDirectiveName: string,
): boolean {
return [...selectors].some((selector) => {
return (
selector === aliasName ||
selectorDirectiveName === aliasName ||
composedName(selector, propertyName) === aliasName
);
});
Expand Down
47 changes: 47 additions & 0 deletions packages/eslint-plugin/tests/rules/no-input-rename/cases.ts
Expand Up @@ -146,6 +146,22 @@ export const valid = [
@Input('fooMyColor') myColor: string;
}
`,
`
@Directive({
selector: 'img[fooDirective]'
})
class Test {
@Input foo: Foo;
}
`,
`
@Directive({
selector: 'img[fooDirective]'
})
class Test {
@Input('fooDirective') foo: Foo;
}
`,
];

export const invalid = [
Expand Down Expand Up @@ -447,4 +463,35 @@ export const invalid = [
`,
})),
}),
convertAnnotatedSourceToFailureCase({
description:
'should fail if input property alias does not match the directive name when applied to an element in the selector',
annotatedSource: `
@Directive({
selector: 'img[fooDirective]',
})
class Test {
@Input('notFooDirective') foo: Foo;
~~~~~~~~~~~~~~~~~
}
`,
messageId,
suggestions: (
[
[suggestRemoveAliasName, 'foo'],
[suggestReplaceOriginalNameWithAliasName, 'notFooDirective'],
] as const
).map(([messageId, propertyName]) => ({
messageId,
output: `
@Directive({
selector: 'img[fooDirective]',
})
class Test {
@Input() ${propertyName}: Foo;

}
`,
})),
}),
];