Skip to content

Commit 2ac4372

Browse files
authored
feat(eslint-plugin): add new rule require-super-ondestroy (#4611)
Closes #4505
1 parent 4d34dc4 commit 2ac4372

22 files changed

+267
-48
lines changed
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
21
export interface Schema {
32
config: 'all' | 'store' | 'effects' | 'component-store' | 'signals';
43
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { ESLintUtils } from '@typescript-eslint/utils';
2+
import { InvalidTestCase, ValidTestCase } from '@typescript-eslint/rule-tester';
3+
import rule, {
4+
messageId,
5+
} from '../../../src/rules/component-store/require-super-ondestroy';
6+
import { fromFixture, ruleTester } from '../../utils';
7+
import path = require('path');
8+
9+
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
10+
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;
11+
12+
const valid: () => (string | ValidTestCase<Options>)[] = () => [
13+
`
14+
import { ComponentStore } from '@ngrx/component-store';
15+
16+
class BooksStore extends ComponentStore<BooksState>
17+
{
18+
}`,
19+
`
20+
import { ComponentStore } from '@ngrx/component-store';
21+
22+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy
23+
{
24+
override ngOnDestroy(): void {
25+
super.ngOnDestroy();
26+
}
27+
}`,
28+
`
29+
import { ComponentStore } from '@ngrx/component-store';
30+
31+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy
32+
{
33+
cleanUp() {}
34+
35+
override ngOnDestroy(): void {
36+
this.cleanUp();
37+
super.ngOnDestroy();
38+
}
39+
}`,
40+
`
41+
import { ComponentStore } from '@ngrx/component-store';
42+
43+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy
44+
{
45+
cleanUp() {}
46+
47+
override ngOnDestroy(): void {
48+
super.ngOnDestroy();
49+
this.cleanUp();
50+
}
51+
}`,
52+
`
53+
import { ComponentStore } from '@ngrx/component-store';
54+
55+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy
56+
{
57+
cleanUp() {}
58+
59+
override ngOnDestroy(): void {
60+
this.cleanUp();
61+
super.ngOnDestroy();
62+
this.cleanUp();
63+
}
64+
}`,
65+
`
66+
import { ComponentStore } from '../components/component-store';
67+
68+
class BooksStore extends ComponentStore implements OnDestroy
69+
{
70+
cleanUp() {}
71+
72+
override ngOnDestroy(): void {
73+
this.cleanUp();
74+
}
75+
}`,
76+
];
77+
78+
const invalid: () => InvalidTestCase<MessageIds, Options>[] = () => [
79+
fromFixture(`
80+
import { ComponentStore } from '@ngrx/component-store';
81+
82+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy {
83+
override ngOnDestroy(): void {
84+
~~~~~~~~~~~ [${messageId}]
85+
}
86+
}`),
87+
fromFixture(`
88+
import { ComponentStore } from '@ngrx/component-store';
89+
90+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy {
91+
cleanUp() {}
92+
93+
override ngOnDestroy(): void {
94+
~~~~~~~~~~~ [${messageId}]
95+
this.cleanUp();
96+
}
97+
}`),
98+
fromFixture(`
99+
import { ComponentStore } from '@ngrx/component-store';
100+
101+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy {
102+
override ngOnDestroy(): void {
103+
~~~~~~~~~~~ [${messageId}]
104+
super.ngOnDestroy;
105+
}
106+
}`),
107+
fromFixture(`
108+
import { ComponentStore } from '@ngrx/component-store';
109+
110+
class BooksStore extends ComponentStore<BooksState> implements OnDestroy {
111+
override ngOnDestroy(): void {
112+
~~~~~~~~~~~ [${messageId}]
113+
super.get();
114+
}
115+
}`),
116+
];
117+
118+
ruleTester().run(path.parse(__filename).name, rule, {
119+
valid: valid(),
120+
invalid: invalid(),
121+
});

modules/eslint-plugin/src/configs/all.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"rules": {
55
"@ngrx/avoid-combining-component-store-selectors": "error",
66
"@ngrx/avoid-mapping-component-store-selectors": "error",
7+
"@ngrx/require-super-ondestroy": "error",
78
"@ngrx/updater-explicit-return-type": "error",
89
"@ngrx/avoid-cyclic-effects": "error",
910
"@ngrx/no-dispatch-in-effects": "error",
@@ -13,9 +14,9 @@
1314
"@ngrx/prefer-effect-callback-in-block-statement": "error",
1415
"@ngrx/use-effects-lifecycle-interface": "error",
1516
"@ngrx/prefer-concat-latest-from": "error",
17+
"@ngrx/prefer-protected-state": "error",
1618
"@ngrx/signal-state-no-arrays-at-root-level": "error",
1719
"@ngrx/signal-store-feature-should-use-generic-type": "error",
18-
"@ngrx/prefer-protected-state": "error",
1920
"@ngrx/with-state-no-arrays-at-root-level": "error",
2021
"@ngrx/avoid-combining-selectors": "error",
2122
"@ngrx/avoid-dispatching-multiple-actions-sequentially": "error",

modules/eslint-plugin/src/configs/all.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default (
3232
rules: {
3333
'@ngrx/avoid-combining-component-store-selectors': 'error',
3434
'@ngrx/avoid-mapping-component-store-selectors': 'error',
35+
'@ngrx/require-super-ondestroy': 'error',
3536
'@ngrx/updater-explicit-return-type': 'error',
3637
'@ngrx/avoid-cyclic-effects': 'error',
3738
'@ngrx/no-dispatch-in-effects': 'error',
@@ -41,9 +42,9 @@ export default (
4142
'@ngrx/prefer-effect-callback-in-block-statement': 'error',
4243
'@ngrx/use-effects-lifecycle-interface': 'error',
4344
'@ngrx/prefer-concat-latest-from': 'error',
45+
'@ngrx/prefer-protected-state': 'error',
4446
'@ngrx/signal-state-no-arrays-at-root-level': 'error',
4547
'@ngrx/signal-store-feature-should-use-generic-type': 'error',
46-
'@ngrx/prefer-protected-state': 'error',
4748
'@ngrx/with-state-no-arrays-at-root-level': 'error',
4849
'@ngrx/avoid-combining-selectors': 'error',
4950
'@ngrx/avoid-dispatching-multiple-actions-sequentially': 'error',

modules/eslint-plugin/src/configs/component-store.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"rules": {
55
"@ngrx/avoid-combining-component-store-selectors": "error",
66
"@ngrx/avoid-mapping-component-store-selectors": "error",
7+
"@ngrx/require-super-ondestroy": "error",
78
"@ngrx/updater-explicit-return-type": "error"
89
}
910
}

modules/eslint-plugin/src/configs/component-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default (
2727
rules: {
2828
'@ngrx/avoid-combining-component-store-selectors': 'error',
2929
'@ngrx/avoid-mapping-component-store-selectors': 'error',
30+
'@ngrx/require-super-ondestroy': 'error',
3031
'@ngrx/updater-explicit-return-type': 'error',
3132
},
3233
},

modules/eslint-plugin/src/configs/signals.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"parser": "@typescript-eslint/parser",
33
"plugins": ["@ngrx"],
44
"rules": {
5+
"@ngrx/prefer-protected-state": "error",
56
"@ngrx/signal-state-no-arrays-at-root-level": "error",
67
"@ngrx/signal-store-feature-should-use-generic-type": "error",
7-
"@ngrx/prefer-protected-state": "error",
88
"@ngrx/with-state-no-arrays-at-root-level": "error"
99
},
1010
"parserOptions": {

modules/eslint-plugin/src/configs/signals.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export default (
3030
},
3131
},
3232
rules: {
33+
'@ngrx/prefer-protected-state': 'error',
3334
'@ngrx/signal-state-no-arrays-at-root-level': 'error',
3435
'@ngrx/signal-store-feature-should-use-generic-type': 'error',
35-
'@ngrx/prefer-protected-state': 'error',
3636
'@ngrx/with-state-no-arrays-at-root-level': 'error',
3737
},
3838
},

modules/eslint-plugin/src/rules/component-store/avoid-combining-component-store-selectors.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import type { TSESTree } from '@typescript-eslint/utils';
22
import * as path from 'path';
33
import { createRule } from '../../rule-creator';
4-
import {
5-
asPattern,
6-
getNgRxComponentStores,
7-
namedExpression,
8-
} from '../../utils';
4+
import { getNgrxComponentStoreNames, namedExpression } from '../../utils';
95
export const messageId = 'avoidCombiningComponentStoreSelectors';
106
type MessageIds = typeof messageId;
117
type Options = readonly [];
@@ -25,8 +21,7 @@ export default createRule<Options, MessageIds>({
2521
},
2622
defaultOptions: [],
2723
create: (context) => {
28-
const { identifiers = [] } = getNgRxComponentStores(context);
29-
const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;
24+
const storeNames = getNgrxComponentStoreNames(context);
3025

3126
const thisSelects = `CallExpression[callee.object.type='ThisExpression'][callee.property.name='select']`;
3227
const storeSelects = storeNames ? namedExpression(storeNames) : null;

modules/eslint-plugin/src/rules/component-store/avoid-mapping-component-store-selectors.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import type { TSESTree } from '@typescript-eslint/utils';
22
import * as path from 'path';
33
import { createRule } from '../../rule-creator';
44
import {
5-
asPattern,
6-
getNgRxComponentStores,
5+
getNgrxComponentStoreNames,
76
namedCallableExpression,
87
} from '../../utils';
98

@@ -27,8 +26,7 @@ export default createRule<Options, MessageIds>({
2726
},
2827
defaultOptions: [],
2928
create: (context) => {
30-
const { identifiers = [] } = getNgRxComponentStores(context);
31-
const storeNames = identifiers.length > 0 ? asPattern(identifiers) : null;
29+
const storeNames = getNgrxComponentStoreNames(context);
3230

3331
const mapOperatorSelector = `[callee.property.name=pipe] > CallExpression[callee.name=map]`;
3432
const selectors = [

0 commit comments

Comments
 (0)