From aff33442e0c35440827f144916e07d180965d0e9 Mon Sep 17 00:00:00 2001 From: Adrian Baran <93612066+adbaran1@users.noreply.github.com> Date: Sun, 20 Nov 2022 03:55:59 -0600 Subject: [PATCH] fix(no-input-rename): allow input aliases that match the directive name applied to an element (#1207) --- .../docs/rules/no-input-rename.md | 94 +++++++++++++++++++ .../src/rules/no-input-rename.ts | 30 +++++- .../tests/rules/no-input-rename/cases.ts | 47 ++++++++++ 3 files changed, 168 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-input-rename.md b/packages/eslint-plugin/docs/rules/no-input-rename.md index eaa56a41e..4bae04702 100644 --- a/packages/eslint-plugin/docs/rules/no-input-rename.md +++ b/packages/eslint-plugin/docs/rules/no-input-rename.md @@ -365,6 +365,38 @@ class Test { } ``` +
+ +--- + +
+ +#### Default Config + +```json +{ + "rules": { + "@angular-eslint/no-input-rename": [ + "error" + ] + } +} +``` + +
+ +#### ❌ Invalid Code + +```ts +@Directive({ + selector: 'img[fooDirective]', +}) +class Test { + @Input('notFooDirective') foo: Foo; + ~~~~~~~~~~~~~~~~~ +} +``` +
@@ -887,6 +919,68 @@ class Test { } ``` +
+ +--- + +
+ +#### Default Config + +```json +{ + "rules": { + "@angular-eslint/no-input-rename": [ + "error" + ] + } +} +``` + +
+ +#### ✅ Valid Code + +```ts +@Directive({ + selector: 'img[fooDirective]' +}) +class Test { + @Input foo: Foo; +} +``` + +
+ +--- + +
+ +#### Default Config + +```json +{ + "rules": { + "@angular-eslint/no-input-rename": [ + "error" + ] + } +} +``` + +
+ +#### ✅ Valid Code + +```ts +@Directive({ + selector: 'img[fooDirective]' +}) +class Test { + @Input('fooDirective') foo: Foo; +} +``` +
diff --git a/packages/eslint-plugin/src/rules/no-input-rename.ts b/packages/eslint-plugin/src/rules/no-input-rename.ts index d1f216b19..aa81e9a55 100644 --- a/packages/eslint-plugin/src/rules/no-input-rename.ts +++ b/packages/eslint-plugin/src/rules/no-input-rename.ts @@ -55,13 +55,21 @@ export default createESLintRule({ create(context, [{ allowedNames = [] }]) { let selectors: ReadonlySet = 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]( @@ -98,7 +106,14 @@ export default createESLintRule({ 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', @@ -147,7 +162,14 @@ export default createESLintRule({ ASTUtils.getReplacementText(node, propertyName), ), }); - } else if (!isAliasNameAllowed(selectors, propertyName, aliasName)) { + } else if ( + !isAliasNameAllowed( + selectors, + propertyName, + aliasName, + selectorDirectiveName, + ) + ) { context.report({ node, messageId: 'noInputRename', @@ -182,10 +204,12 @@ function isAliasNameAllowed( selectors: ReadonlySet, propertyName: string, aliasName: string, + selectorDirectiveName: string, ): boolean { return [...selectors].some((selector) => { return ( selector === aliasName || + selectorDirectiveName === aliasName || composedName(selector, propertyName) === aliasName ); }); diff --git a/packages/eslint-plugin/tests/rules/no-input-rename/cases.ts b/packages/eslint-plugin/tests/rules/no-input-rename/cases.ts index 880201f69..9cfbd6e53 100644 --- a/packages/eslint-plugin/tests/rules/no-input-rename/cases.ts +++ b/packages/eslint-plugin/tests/rules/no-input-rename/cases.ts @@ -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 = [ @@ -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; + + } + `, + })), + }), ];