Flow Coverage Report (May 1st)

We’re currently using Flow 0.47 and have 60% coverage. Here’s a quick run down of how we’re doing.

Components

Typing our components helps us with rendering logic, which can take advantage of our domain objects such as Frame.

function renderFrameTitle(frame: Frame) {
  const displayName = formatDisplayName(frame);
  return dom.div({ className: "title" }, displayName);
}

We are not yet typing our props and state, which would be a helpful way to get additional coverage. We currently use PropTypes, which do not include flow types. We will hopefully switch to props soon to take advantage of static typing.

shouldComponentUpdate(nextProps, nextState) {
  const { frames, selectedFrame } = this.props;
  const { showAllFrames } = this.state;
  return (
    frames !== nextProps.frames ||
    selectedFrame !== nextProps.selectedFrame ||
    showAllFrames !== nextState.showAllFrames
  );
}

There have been a couple places where flow types have been difficult to get right.

  1. covariants this.toggleFramesDisplay = this.toggleFramesDisplay.bind(this). We have an issue to improve this.
  2. defaultProps and other class properties have been awkward to add types for.

Overall, typing our components has been a nice win though as it gives us additional confidence our UI will behave appropriately and catches several lifecycle edge-cases where properties could be null or racey.

Utilities

We started typing our utility functions. Starting with utilities helped us document our primary business logic and made them easier to refactor.

In this example, we added types to a parser utility that checks to see if an expression is in a particular scope.

export function isExpressionInScope(expression: string, scope?: Scope) {
  if (!scope) {
    return false;
  }

  const variables = getVariablesInScope(scope);
  const firstPart = expression.split(/\./)[0];
  return variables.includes(firstPart);
}

We are not currently typing our test files, but I think it would be helpful if we did. In this example, we are searching for all of the functions in a given source, but because we did not provide a type for getSourceText, flow can not use getSymbols’s types to tell us if we’re writing a reasonable test.

it("finds functions", () => {
  const fncs = getSymbols(getSourceText("func")).functions;
  const names = fncs.map(f => f.value);
  expect(names).to.eql(["square", "child", "anonymous"]);
});

Overall, typing our utilities was a great first step. And the more that we type the components, actions, and reducers that call the utilities, the more value we will get out of them. Already, the types are forcing an internal consistency that helps us achieve a better API for our utils.

Actions / Reducers

Our Actions and Reducers are generally very well covered.

Actions like breakpoints and pause have a coverage score of over 80%. We initially, typed our action dispatcher params. We’ve recently added types for ThunkArgs and State. Typing State, allows flow to type check our selectors and utils.

export function newSource(source: Source) {
  return ({ dispatch, getState }: ThunkArgs) => {
    if (prefs.clientSourceMapsEnabled) {
      dispatch(loadSourceMap(source));
    }

    dispatch({ type: constants.ADD_SOURCE, source });

    checkSelectedSource(getState(), dispatch, source);
    checkPendingBreakpoints(getState(), dispatch, source);
  };
}

Our reducers are 80% covered and some reducers like breakpoints and pause are 80+ percent covered. Breakpoints is actually 97% covered. Most of the coverage comes from our State type and Action union types.

The State type describes the state of the the reducer store. Typing the State and action types lets us fully describe our reducers and get a high flow coverage score.

export type BreakpointsState = {
  breakpoints: I.Map<string, Breakpoint>,
  pendingBreakpoints: any[],
  breakpointsDisabled: false
};

Typing our actions gives us confidence that the action dispatched will be what our reducers expect to handle.

type BreakpointAction =
  | { type: "ADD_BREAKPOINT",
      breakpoint: Breakpoint,
      condition: string,
      value: BreakpointResult
    }
  | {
      type: "REMOVE_BREAKPOINT",
      breakpoint: Breakpoint,
      disabled: boolean
    }
...
export type Action = SourceAction | BreakpointAction | PauseAction | UIAction;