mAAdhaTTah/brookjs

View on GitHub
packages/brookjs-cli/src/commands/FormatCommand/index.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React, { useEffect } from 'react';
import { Argv } from 'yargs';
import { useDelta, RootJunction } from 'brookjs-silt';
import { EddyReducer, loop } from 'brookjs-eddy';
import {
ActionType,
getType,
createAsyncAction,
createAction,
} from 'typesafe-actions';
import Kefir from 'kefir';
import { Delta, Maybe, unreachable } from 'brookjs-types';
import * as t from 'io-ts';
import { ofType, sampleByAction } from 'brookjs-flow';
import { Box, Color } from 'ink';
import { Command, useExit, ExitError } from '../../cli';
import * as glob from '../../glob';
import { service as fs } from '../../fs';
import { ESLintService } from '../../eslint';
import * as prettier from '../../prettier';
 
type Args = {};
 
type State = {
status: 'init' | 'globbing' | 'formatting' | 'finished' | 'errored';
cwd: string;
rc: Maybe<RC>;
paths: string[];
results: Record<string, FormatResults>;
error: Maybe<Error>;
};
 
type Action = ActionType<typeof glob.actions | typeof actions>;
 
type RC = t.TypeOf<typeof RC>;
 
const RC = t.partial({
dir: t.string,
});
 
type FormatResults = {
path: string;
contents: string;
changed: boolean;
};
 
const actions = {
fileFormatted: createAction('FILE_FORMATTED')<FormatResults>(),
format: createAsyncAction(
'FORMAT_REQUESTED',
'FORMAT_SUCCEEDED',
'FORMAT_FAILED',
)<void, void, Error>(),
};
 
const reducer: EddyReducer<State, Action> = (
state = initialState({}, { rc: null, cwd: '/' }),
action,
) => {
switch (action.type) {
case getType(glob.actions.format.request):
return {
...state,
status: 'globbing',
};
case getType(glob.actions.format.success):
return loop(
{ ...state, status: 'formatting', paths: action.payload },
actions.format.request(),
);
case getType(actions.fileFormatted):
return {
...state,
results: {
...state.results,
[action.payload.path]: action.payload,
},
};
case getType(actions.format.success):
return {
...state,
status: 'finished',
};
case getType(actions.format.failure):
return {
...state,
status: 'errored',
error: action.payload,
};
default:
return state;
}
};
 
const initialState = (
args: Args,
{ rc, cwd }: { rc: unknown; cwd: string },
): State => ({
status: 'init',
cwd,
rc: RC.decode(rc).fold(
() => null,
rc => rc,
),
paths: [],
results: {},
error: null,
});
 
Function `exec` has 36 lines of code (exceeds 25 allowed). Consider refactoring.
const exec: Delta<Action, State> = (action$, state$) => {
const glob$ = glob.delta(
action$.thru(ofType(glob.actions.format.request)),
state$.map(state => ({
cwd: state.cwd,
rc: state.rc,
})),
);
 
const format$ = state$
.thru(sampleByAction(action$, actions.format.request))
.flatMap(state => {
const eslint = ESLintService.create({ cwd: state.cwd, fix: true });
 
return Kefir.constant(state.paths)
.flatten(paths => paths)
.flatMapConcurLimit(
file => fs.readFile(file).map(buffer => ({ buffer, file })),
4,
)
.flatMap(({ buffer, file }) => prettier.format(file, buffer))
.flatMap(result =>
eslint.check(result.path, result.contents).map(r => ({
path: result.path,
contents: r.results[0].output ?? result.contents,
changed: result.changed || r.results[0].output != null,
})),
)
.flatMap(result =>
result.changed
? fs.writeFile(result.path, result.contents).map(() => result)
: Kefir.constant(result),
)
.map(actions.fileFormatted)
.concat(Kefir.constant(actions.format.success()))
.takeErrors(1)
.flatMapErrors(err => Kefir.constant(actions.format.failure(err)));
});
 
return Kefir.merge<Action, never>([glob$, format$]);
};
 
const Errored: React.FC<{ message: string }> = ({ message }) => {
useExit(new ExitError(1));
 
return (
<Box flexDirection="column">
<Color red>Failure! Error formatting files.</Color>
<Color redBright>{message}</Color>
</Box>
);
};
 
Function `View` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
const View: React.FC<State> = ({ status, paths, results, error }) => {
switch (status) {
case 'init':
case 'globbing':
case 'formatting':
return (
<Box>
<Color yellow>Formatting...</Color>
</Box>
);
case 'finished':
const total = paths.reduce(
(total, path) => total + (results[path].changed ? 1 : 0),
0,
);
Similar blocks of code found in 2 locations. Consider refactoring.
return (
<Box flexDirection="column">
<Color green>Success! All files have been formatted.</Color>
<Color greenBright>
Formatted {total} file{total === 1 ? '' : 's'}
</Color>
</Box>
);
case 'errored':
return <Errored message={error?.message ?? 'Unknown error.'} />;
default:
return unreachable(status);
}
};
 
const FormatCommand: Command<Args> = {
cmd: 'format',
describe: 'Format all the files in the brookjs application.',
builder(yargs: Argv<Args>): Argv<Args> {
return yargs;
},
 
View: ({ args, rc, cwd }) => {
const { state, root$, dispatch } = useDelta(
reducer,
initialState(args, { rc, cwd }),
exec,
);
 
useEffect(() => {
dispatch(glob.actions.format.request());
}, [dispatch]);
 
return (
<RootJunction root$={root$}>
<View {...state} />
</RootJunction>
);
},
};
 
export default FormatCommand;