Skip to content

Commit bb9add7

Browse files
alex-okrushkobrandonroberts
authored andcommitted
feat(store): add protection from type property use (#1923)
Closes #1917
1 parent 36f14bd commit bb9add7

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

modules/store/spec/action_creator.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ describe('Action Creators', () => {
7777
const value = fooAction.bar;
7878
`).toFail(/'bar' does not exist on type/);
7979
});
80+
it('should not allow type property', () => {
81+
expectSnippet(`
82+
const foo = createAction('FOO', (type: string) => ({type}));
83+
`).toFail(
84+
/Type '{ type: string; }' is not assignable to type '"type property is not allowed in action creators"/
85+
);
86+
});
8087
});
8188

8289
describe('empty', () => {
@@ -141,5 +148,14 @@ describe('Action Creators', () => {
141148
const value = fooAction.bar;
142149
`).toFail(/'bar' does not exist on type/);
143150
});
151+
152+
it('should not allow type property', () => {
153+
const foo = createAction('FOO', props<{ type: number }>() as any);
154+
expectSnippet(`
155+
const foo = createAction('FOO', props<{ type: number }>());
156+
`).toFail(
157+
/Argument of type '"type property is not allowed in action creators"' is not assignable to parameter of type/
158+
);
159+
});
144160
});
145161
});

modules/store/src/action_creator.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,34 @@ import {
33
ActionCreator,
44
TypedAction,
55
FunctionWithParametersType,
6-
ParametersType,
6+
PropsReturnType,
7+
DisallowTypeProperty,
78
} from './models';
89

9-
/**
10-
* Action creators taken from ts-action library and modified a bit to better
11-
* fit current NgRx usage. Thank you Nicholas Jamieson (@cartant).
12-
*/
10+
// Action creators taken from ts-action library and modified a bit to better
11+
// fit current NgRx usage. Thank you Nicholas Jamieson (@cartant).
12+
1313
export function createAction<T extends string>(
1414
type: T
1515
): ActionCreator<T, () => TypedAction<T>>;
1616
export function createAction<T extends string, P extends object>(
1717
type: T,
1818
config: { _as: 'props'; _p: P }
1919
): ActionCreator<T, (props: P) => P & TypedAction<T>>;
20-
export function createAction<T extends string, C extends Creator>(
20+
export function createAction<
21+
T extends string,
22+
P extends any[],
23+
R extends object
24+
>(
2125
type: T,
22-
creator: C
23-
): FunctionWithParametersType<
24-
ParametersType<C>,
25-
ReturnType<C> & TypedAction<T>
26-
> &
27-
TypedAction<T>;
28-
export function createAction<T extends string>(
26+
creator: Creator<P, DisallowTypeProperty<R>>
27+
): FunctionWithParametersType<P, R & TypedAction<T>> & TypedAction<T>;
28+
export function createAction<T extends string, C extends Creator>(
2929
type: T,
30-
config?: { _as: 'props' } | Creator
30+
config?: { _as: 'props' } | C
3131
): Creator {
3232
if (typeof config === 'function') {
33-
return defineType(type, (...args: unknown[]) => ({
33+
return defineType(type, (...args: any[]) => ({
3434
...config(...args),
3535
type,
3636
}));
@@ -40,17 +40,19 @@ export function createAction<T extends string>(
4040
case 'empty':
4141
return defineType(type, () => ({ type }));
4242
case 'props':
43-
return defineType(type, (props: unknown) => ({
44-
...(props as object),
43+
return defineType(type, (props: object) => ({
44+
...props,
4545
type,
4646
}));
4747
default:
4848
throw new Error('Unexpected config.');
4949
}
5050
}
5151

52-
export function props<P>(): { _as: 'props'; _p: P } {
53-
return { _as: 'props', _p: undefined! };
52+
export function props<P extends object>(): PropsReturnType<P> {
53+
// the return type does not match TypePropertyIsNotAllowed, so double casting
54+
// is used.
55+
return ({ _as: 'props', _p: undefined! } as unknown) as PropsReturnType<P>;
5456
}
5557

5658
export function union<

modules/store/src/models.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,24 @@ export type SelectorWithProps<State, Props, Result> = (
4949
props: Props
5050
) => Result;
5151

52-
export type Creator = (...args: any[]) => object;
52+
export type DisallowTypeProperty<T> = T extends { type: any }
53+
? TypePropertyIsNotAllowed
54+
: T;
55+
56+
export const typePropertyIsNotAllowedMsg =
57+
'type property is not allowed in action creators';
58+
type TypePropertyIsNotAllowed = typeof typePropertyIsNotAllowedMsg;
59+
60+
export type Creator<
61+
P extends any[] = any[],
62+
R extends object = object
63+
> = R extends { type: any }
64+
? TypePropertyIsNotAllowed
65+
: FunctionWithParametersType<P, R>;
66+
67+
export type PropsReturnType<T extends object> = T extends { type: any }
68+
? TypePropertyIsNotAllowed
69+
: { _as: 'props'; _p: T };
5370

5471
export type ActionCreator<
5572
T extends string = string,

0 commit comments

Comments
 (0)