diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 9063affc6..803bffabd 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -70,7 +70,7 @@ | [`use-component-selector`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-component-selector.md) | Component selector must be declared | | | | | [`use-component-view-encapsulation`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-component-view-encapsulation.md) | Disallows using `ViewEncapsulation.None` | | | :bulb: | | [`use-injectable-provided-in`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-injectable-provided-in.md) | Using the `providedIn` property makes `Injectables` tree-shakable | | | :bulb: | -| [`use-lifecycle-interface`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md) | Ensures that classes implement lifecycle interfaces corresponding to the declared lifecycle methods. See more at https://angular.io/styleguide#style-09-01 | | | | +| [`use-lifecycle-interface`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md) | Ensures that classes implement lifecycle interfaces corresponding to the declared lifecycle methods. See more at https://angular.io/styleguide#style-09-01 | | :wrench: | | | [`use-pipe-transform-interface`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/use-pipe-transform-interface.md) | Ensures that `Pipes` implement `PipeTransform` interface | :white_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md b/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md index 819e205fd..c957f7250 100644 --- a/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md +++ b/packages/eslint-plugin/docs/rules/use-lifecycle-interface.md @@ -18,6 +18,7 @@ Ensures that classes implement lifecycle interfaces corresponding to the declared lifecycle methods. See more at https://angular.io/styleguide#style-09-01 - Type: suggestion +- 🔧 Supports autofix (`--fix`)
diff --git a/packages/eslint-plugin/src/rules/use-lifecycle-interface.ts b/packages/eslint-plugin/src/rules/use-lifecycle-interface.ts index 7b71b4215..27349449f 100644 --- a/packages/eslint-plugin/src/rules/use-lifecycle-interface.ts +++ b/packages/eslint-plugin/src/rules/use-lifecycle-interface.ts @@ -1,4 +1,9 @@ -import { ASTUtils, toPattern } from '@angular-eslint/utils'; +import { + ASTUtils, + toPattern, + RuleFixes, + isNotNullOrUndefined, +} from '@angular-eslint/utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { createESLintRule } from '../utils/create-eslint-rule'; @@ -18,6 +23,7 @@ export default createESLintRule({ messages: { useLifecycleInterface: `Lifecycle interface '{{interfaceName}}' should be implemented for method '{{methodName}}'. (${STYLE_GUIDE_LINK})`, }, + fixable: 'code', }, defaultOptions: [], create(context) { @@ -50,6 +56,16 @@ export default createESLintRule({ node: key, messageId: 'useLifecycleInterface', data: { interfaceName, methodName }, + fix: (fixer) => { + const { implementsNodeReplace, implementsTextReplace } = + RuleFixes.getImplementsSchemaFixer(parent, interfaceName); + return [ + fixer.insertTextAfter( + implementsNodeReplace, + implementsTextReplace, + ), + ].filter(isNotNullOrUndefined); + }, }); }, }; diff --git a/packages/eslint-plugin/tests/rules/use-lifecycle-interface/cases.ts b/packages/eslint-plugin/tests/rules/use-lifecycle-interface/cases.ts index 17c8b3ed9..8b5630d27 100644 --- a/packages/eslint-plugin/tests/rules/use-lifecycle-interface/cases.ts +++ b/packages/eslint-plugin/tests/rules/use-lifecycle-interface/cases.ts @@ -58,6 +58,14 @@ export const invalid = [ interfaceName: ASTUtils.AngularLifecycleInterfaces.OnInit, methodName: ASTUtils.AngularLifecycleMethods.ngOnInit, }, + annotatedOutput: ` + @Component() + class Test implements OnInit { + ngOnInit() { + + } + } + `, }), convertAnnotatedSourceToFailureCase({ description: @@ -77,6 +85,16 @@ export const invalid = [ interfaceName: ASTUtils.AngularLifecycleInterfaces.OnDestroy, methodName: ASTUtils.AngularLifecycleMethods.ngOnDestroy, }, + annotatedOutput: ` + @Directive() + class Test extends Component implements OnInit, OnDestroy { + ngOnInit() {} + + ngOnDestroy() { + + } + } + `, }), convertAnnotatedSourceToFailureCase({ description: @@ -120,6 +138,19 @@ export const invalid = [ }, }, ], + annotatedOutput: ` + @Injectable() + class Test implements DoBootstrap { + ngDoBootstrap() {} + + + ngOnInit() {} + + + ngOnDestroy() {} + + } + `, }), convertAnnotatedSourceToFailureCase({ description: @@ -139,5 +170,15 @@ export const invalid = [ interfaceName: ASTUtils.AngularLifecycleInterfaces.OnDestroy, methodName: ASTUtils.AngularLifecycleMethods.ngOnDestroy, }, + annotatedOutput: ` + @NgModule() + class Test extends Component implements ng.OnInit, OnDestroy { + ngOnInit() {} + + ngOnDestroy() { + + } + } + `, }), ];