Skip to content

Commit

Permalink
feat: enforce qualifier in action names
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This ensures every action has name of the form
`[qualifier] action name`. See the
[new doc](https://github.com/ardoq/rxbeach/blob/master/docs/qualified-action-names.md)
for how to automate this process.
  • Loading branch information
tlaundal committed Sep 9, 2021
1 parent 4605d99 commit 9a8aae9
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 23 deletions.
42 changes: 42 additions & 0 deletions docs/qualified-action-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Converting to qualified action names
========================

With the introduced requirment that action names have to be of the form
`[qualifier] action name`, it might be useful to automate this change.

In Ardoq we used this codemod to convert 600 actions. It picks the name of the
file (or folder if the file is called `actions` or `utils`) and uses that as the
qualifier. It can be run with [jscodeshift](https://github.com/facebook/jscodeshift/):

npx jscodeshift --parser ts --extension ts -t codemod-qualified-actions.js src/js/ardoq


```javascript
function qualified(path, action) {
const parts = path.split('.')[0].split('/');
let name = parts[parts.length - 1];
if (name === 'actions' || name === 'utils') {
name = parts[parts.length - 2];
}
return `[${name}] ${action}`;
}

module.exports = function ({ source, path }, api, options) {
return api
.jscodeshift(source)
.find(api.jscodeshift.CallExpression, {
callee: {
type: 'Identifier',
name: 'actionCreator',
},
})
.filter(n => {
const arg = n.value.arguments[0];
return arg && arg.type === 'StringLiteral' && !arg.value.startsWith('[');
})
.forEach(n => {
n.value.arguments[0].value = qualified(path, n.value.arguments[0].value);
})
.toSource();
};
```
4 changes: 2 additions & 2 deletions src/actionCreator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import test from 'ava';
import { actionCreator } from './actionCreator';

type Payload = { num: number };
const myAction = actionCreator<Payload>('three');
const myAction = actionCreator<Payload>('[test] three');
const action = myAction({ num: 3 }) as {
type: string;
payload: Payload;
meta: { namespace?: string };
};
type AlternativePayload = { text: string };
const unionAction = actionCreator<Payload | AlternativePayload>('union');
const unionAction = actionCreator<Payload | AlternativePayload>('[test] union');
const union1 = unionAction({ num: 4 });
const union2 = unionAction({ text: 'hi' });

Expand Down
6 changes: 4 additions & 2 deletions src/actionCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
} from './types/ActionCreator';
import { actionMarker } from './internal/markers';

type ActionName = `[${string}] ${string}`;

interface ActionCreatorFunc {
/**
* Create an action creator without a payload
Expand All @@ -12,7 +14,7 @@ interface ActionCreatorFunc {
* @returns An action creator function that creates complete action objects with
* a type unique to this action creator
*/
(type: string): ActionCreatorWithoutPayload;
(type: ActionName): ActionCreatorWithoutPayload;
/**
* Create an action creator with a given payload type
*
Expand All @@ -22,7 +24,7 @@ interface ActionCreatorFunc {
* returns a complete action object with that payload and a type unique
* to this action creator
*/
<Payload>(type: string): ActionCreatorWithPayload<Payload>;
<Payload>(type: ActionName): ActionCreatorWithPayload<Payload>;
}

/**
Expand Down
20 changes: 12 additions & 8 deletions src/actionCreator.tspec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { actionCreator } from './actionCreator';

const booleanAction = actionCreator<boolean>('boolean');
const booleanAction = actionCreator<boolean>('[test] boolean');
booleanAction(true);
booleanAction(false);

Expand All @@ -9,41 +9,45 @@ enum Enum {
B,
C,
}
const enumAction = actionCreator<Enum>('enum');
const enumAction = actionCreator<Enum>('[test] enum');
enumAction(Enum.A);
enumAction(Enum.B);
const e: Enum = Enum.C;
enumAction(e);

const unionAction = actionCreator<Record<string, unknown> | number>(
'empty or number'
'[test] empty or number'
);
unionAction({});
unionAction(1);
unionAction(2);

type NumberGenerator = () => number;
type BoolFunc = (a: boolean) => boolean;
const unionFuncAction = actionCreator<NumberGenerator | BoolFunc>('functinos');
const unionFuncAction = actionCreator<NumberGenerator | BoolFunc>(
'[test] functions'
);
unionFuncAction(() => 1);
unionFuncAction((a) => !a);

const voidAction = actionCreator('void');
const voidAction = actionCreator('[test] void');
voidAction();

const nullAction = actionCreator<string | null>('string or null');
const nullAction = actionCreator<string | null>('[test] string or null');
nullAction(null);
nullAction('hello');

const undefinedAction = actionCreator<string | undefined>(
'string or undefined'
'[test] string or undefined'
);

// undefinedAction(); // Would have thought TS would allow this
undefinedAction(undefined);
undefinedAction('world');

const optionalPayloadAction = actionCreator<string | void>('optional payload');
const optionalPayloadAction = actionCreator<string | void>(
'[test] optional payload'
);

optionalPayloadAction();
optionalPayloadAction('payload');
11 changes: 6 additions & 5 deletions src/operators/operators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ type FooPayload = {
foo: number;
};

const voidAction = actionCreator('void');
const fooAction = actionCreator<FooPayload>('foo');
const barAction = actionCreator<{ bar: number }>('bar');
const combinedAction = actionCreator<{ foo: number; bar: number }>('extended');
const voidAction = actionCreator('[test] void');
const fooAction = actionCreator<FooPayload>('[test] foo');
const barAction = actionCreator<{ bar: number }>('[test] bar');
const combinedAction =
actionCreator<{ foo: number; bar: number }>('[test] extended');

const v = voidAction();
const f = fooAction({ foo: 1 });
Expand Down Expand Up @@ -164,7 +165,7 @@ test(
test(
'it should be possible to chain withNamespace, ofType and extractPayload',
marbles((m) => {
const payloadAction = actionCreator<number>('payload action');
const payloadAction = actionCreator<number>('[test] payload action');
const payloadActionWS = namespaceActionCreator('WS', payloadAction);
const payloadActionNS = namespaceActionCreator('NS', payloadAction);
const voidActionWS = namespaceActionCreator('WS', voidAction);
Expand Down
21 changes: 15 additions & 6 deletions src/operators/operators.tspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { extractPayload, ofType } from './operators';
import { AssertTrue, Has } from 'conditional-type-checks';

const stringRoutine = routine(
ofType(actionCreator<string>('simple string')),
ofType(actionCreator<string>('[test] simple string')),
extractPayload()
);
type StringRoutine = AssertTrue<Has<typeof stringRoutine, Routine<string>>>;
Expand All @@ -14,39 +14,48 @@ type Left = { a: number } & Overlap;
type Right = Overlap & { c: symbol };

const inferredOverlapRoutine = routine(
ofType(actionCreator<Overlap>('overlap'), actionCreator<Right>('right')),
ofType(
actionCreator<Overlap>('[test] overlap'),
actionCreator<Right>('[test] right')
),
extractPayload()
);
type InferredOverlapRoutine = AssertTrue<
Has<typeof inferredOverlapRoutine, Routine<Overlap>>
>;

const notInferredOverlapRoutine = routine(
ofType(actionCreator<Left>('left'), actionCreator<Right>('right')),
ofType(
actionCreator<Left>('[test] left'),
actionCreator<Right>('[test] right')
),
extractPayload()
);
type NotInferredOverlapRoutine = AssertTrue<
Has<typeof notInferredOverlapRoutine, Routine<any>>
>;

const hintedInferredOverlapRoutine = routine(
ofType<Overlap>(actionCreator<Left>('left'), actionCreator<Right>('right')),
ofType<Overlap>(
actionCreator<Left>('[test] left'),
actionCreator<Right>('[test] right')
),
extractPayload()
);
type HintedInferredOverlapRoutine = AssertTrue<
Has<typeof hintedInferredOverlapRoutine, Routine<Overlap>>
>;

const unionRoutine = routine(
ofType(actionCreator<string | number>('string or number')),
ofType(actionCreator<string | number>('[test] string or number')),
extractPayload()
);
type UnionRoutine = AssertTrue<
Has<typeof unionRoutine, Routine<string | number>>
>;

const optionalPayloadRoutine = routine(
ofType(actionCreator<string | void>('optional string')),
ofType(actionCreator<string | void>('[test] optional string')),
extractPayload()
);
type OptionalPayloadRoutine = AssertTrue<
Expand Down

0 comments on commit 9a8aae9

Please sign in to comment.