Skip to content

Commit 40477dd

Browse files
feat(eslint-plugin): add new avoid-mapping-component-store-selectors rule (#4026)
Closes #3940
1 parent fdd701e commit 40477dd

File tree

42 files changed

+321
-78
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+321
-78
lines changed

modules/eslint-plugin/scripts/generate-docs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFileSync, writeFileSync } from 'fs';
1+
import { readFileSync, writeFileSync, existsSync } from 'fs';
22
import * as path from 'path';
33
import { format, resolveConfig } from 'prettier';
44
import { rules } from '../src/rules';
@@ -9,6 +9,9 @@ const RULES_PATH = './projects/ngrx.io/content/guide/eslint-plugin/rules';
99

1010
for (const [ruleName, { meta }] of Object.entries(rules)) {
1111
const docPath = path.join(RULES_PATH, `${ruleName}.md`);
12+
if (!existsSync(docPath)) {
13+
writeFileSync(docPath, ``);
14+
}
1215
const doc = readFileSync(docPath, 'utf-8');
1316
const docContent = doc.substring(
1417
doc.indexOf(PLACEHOLDER) + PLACEHOLDER.length
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import type {
2+
ESLintUtils,
3+
TSESLint,
4+
} from '@typescript-eslint/experimental-utils';
5+
import { fromFixture } from 'eslint-etc';
6+
import * as path from 'path';
7+
import rule, {
8+
messageId,
9+
} from '../../../src/rules/component-store/avoid-mapping-component-store-selectors';
10+
import { ruleTester } from '../../utils';
11+
12+
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
13+
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;
14+
type RunTests = TSESLint.RunTests<MessageIds, Options>;
15+
16+
const validConstructor: () => RunTests['valid'] = () => [
17+
`
18+
import { ComponentStore } from '@ngrx/component-store'
19+
20+
class Ok extends ComponentStore<MoviesState> {
21+
movies$ = this.select((state) => state.movies);
22+
firstMovie$ = this.select(this.movies$, (movies) => movies[0]);
23+
24+
constructor() {
25+
super({ movies: [] })
26+
}
27+
}`,
28+
`
29+
import { ComponentStore } from '@ngrx/component-store'
30+
31+
class Ok {
32+
readonly movies$ = this.store.select((state) => state.movies);
33+
firstMovie$ = this.store.select(this.movies$, (movies) => movies[0]);
34+
35+
constructor(private readonly store: ComponentStore<MoviesState>) {}
36+
}`,
37+
`
38+
import { ComponentStore } from '@ngrx/component-store'
39+
40+
class Ok {
41+
readonly movies$: Observable<unknown>
42+
readonly firstMovie$: Observable<unknown>
43+
44+
constructor(private customStore: ComponentStore<MoviesState>) {
45+
const movies = customStore.select((state) => state.movies);
46+
this.firstMovie$ = customStore.select(movies, (movies) => movies[0]);
47+
48+
this.movies$ = this.customStore.select((state) => state.movies);
49+
this.firstMovie$ = this.customStore.select(this.movies$, (movies) => movies[0]);
50+
}
51+
}`,
52+
`
53+
import { ComponentStore } from '@ngrx/component-store'
54+
55+
export class UserStore extends ComponentStore<UserState> {
56+
loggedInUser$ = this.select((state) => state.loggedInUser);
57+
name$ = this.select(this.loggedInUser$, (user) => user.name);
58+
}
59+
`,
60+
];
61+
62+
const validInject: () => RunTests['valid'] = () => [
63+
`
64+
import { inject } from '@angular/core'
65+
import { ComponentStore } from '@ngrx/component-store'
66+
67+
class Ok {
68+
readonly store = inject(ComponentStore<MoviesState>)
69+
readonly movies$ = this.store.select((state) => state.movies)
70+
readonly firstMovie$ = this.store.select(this.movies$, (movies) => movies[0]);
71+
}`,
72+
];
73+
74+
const invalidConstructor: () => RunTests['invalid'] = () => [
75+
fromFixture(`
76+
import { ComponentStore } from '@ngrx/component-store'
77+
78+
class NotOk extends ComponentStore<MoviesState> {
79+
movies$ = this.select((state) => state.movies).pipe(map((movies) => movies))
80+
~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
81+
82+
constructor() {
83+
super({ movies: [] })
84+
}
85+
}`),
86+
fromFixture(`
87+
import { ComponentStore } from '@ngrx/component-store'
88+
89+
class NotOk {
90+
readonly movies$ = this.customStore.select((state) => state.movies).pipe(
91+
filter(Boolean),
92+
map((movies) => movies)
93+
~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
94+
)
95+
96+
constructor(private readonly customStore: ComponentStore<MoviesState>) {}
97+
}`),
98+
fromFixture(`
99+
import { ComponentStore } from '@ngrx/component-store'
100+
101+
class NotOk {
102+
readonly movies$: Observable<unknown>
103+
104+
constructor(private customStore: ComponentStore<MoviesState>) {
105+
this.movies$ = customStore.select((state) => state.movies).pipe(map((movies) => movies), filter(Boolean));
106+
~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
107+
this.movies$ = this.customStore.select((state) => state.movies).pipe(map((movies) => movies));
108+
~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
109+
}
110+
}`),
111+
fromFixture(`
112+
import { ComponentStore } from '@ngrx/component-store'
113+
114+
export class UserStore extends ComponentStore<UserState> {
115+
name$ = this.select((state) => state.loggedInUser).pipe(map((user) => user.name));
116+
~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
117+
}`),
118+
];
119+
120+
const invalidInject: () => RunTests['invalid'] = () => [
121+
fromFixture(`
122+
import { inject } from '@angular/core'
123+
import { ComponentStore } from '@ngrx/component-store'
124+
125+
class NotOk {
126+
readonly otherStoreName = inject(ComponentStore<MoviesState>)
127+
readonly movie$ = this.otherStoreName.select((state) => state.movies).pipe(
128+
map((m) => m),
129+
~~~~~~~~~~~~~ [${messageId}]
130+
filter(Boolean),
131+
map((movies) => movies[0]),
132+
~~~~~~~~~~~~~~~~~~~~~~~~~~ [${messageId}]
133+
)
134+
}`),
135+
];
136+
137+
ruleTester().run(path.parse(__filename).name, rule, {
138+
valid: [...validConstructor(), ...validInject()],
139+
invalid: [...invalidConstructor(), ...invalidInject()],
140+
});

modules/eslint-plugin/spec/rules/updater-explicit-return-type.spec.ts renamed to modules/eslint-plugin/spec/rules/component-store/updater-explicit-return-type.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/component-store/updater-explicit-return-type';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/component-store/updater-explicit-return-type';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/avoid-cyclic-effects.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/avoid-cyclic-effects.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import type {
44
} from '@typescript-eslint/experimental-utils';
55
import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
7-
import rule, { messageId } from '../../src/rules/effects/avoid-cyclic-effects';
8-
import { ruleTester } from '../utils';
7+
import rule, {
8+
messageId,
9+
} from '../../../src/rules/effects/avoid-cyclic-effects';
10+
import { ruleTester } from '../../utils';
911

1012
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1113
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/no-dispatch-in-effects.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/no-dispatch-in-effects.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import * as path from 'path';
77
import rule, {
88
noDispatchInEffects,
99
noDispatchInEffectsSuggest,
10-
} from '../../src/rules/effects/no-dispatch-in-effects';
11-
import { ruleTester } from '../utils';
10+
} from '../../../src/rules/effects/no-dispatch-in-effects';
11+
import { ruleTester } from '../../utils';
1212

1313
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1414
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/no-effects-in-providers.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/no-effects-in-providers.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/effects/no-effects-in-providers';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/effects/no-effects-in-providers';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/no-multiple-actions-in-effects.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/no-multiple-actions-in-effects.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/effects/no-multiple-actions-in-effects';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/effects/no-multiple-actions-in-effects';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/prefer-effect-callback-in-block-statement.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/prefer-effect-callback-in-block-statement.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/effects/prefer-effect-callback-in-block-statement';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/effects/prefer-effect-callback-in-block-statement';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/use-effects-lifecycle-interface.spec.ts renamed to modules/eslint-plugin/spec/rules/effects/use-effects-lifecycle-interface.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/effects/use-effects-lifecycle-interface';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/effects/use-effects-lifecycle-interface';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

modules/eslint-plugin/spec/rules/avoid-combining-selectors.spec.ts renamed to modules/eslint-plugin/spec/rules/store/avoid-combining-selectors.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { fromFixture } from 'eslint-etc';
66
import * as path from 'path';
77
import rule, {
88
messageId,
9-
} from '../../src/rules/store/avoid-combining-selectors';
10-
import { ruleTester } from '../utils';
9+
} from '../../../src/rules/store/avoid-combining-selectors';
10+
import { ruleTester } from '../../utils';
1111

1212
type MessageIds = ESLintUtils.InferMessageIdsTypeFromRule<typeof rule>;
1313
type Options = ESLintUtils.InferOptionsTypeFromRule<typeof rule>;

0 commit comments

Comments
 (0)