[fix] Flow type checking and annotations

Fixes dozens of Flow errors; adds type annotations; marks more files for
Flow type checking. Fixes a bug in 'AppState'.

15 Flow errors remaining. Several React Native files are still not type
checked (e.g., PanResponder, Touchables)

Ref #465
This commit is contained in:
Nicolas Gallagher
2017-05-27 10:43:04 -07:00
parent edef737249
commit bcdeda5dab
40 changed files with 212 additions and 52 deletions

View File

@@ -24,7 +24,16 @@
"globals": {
"document": false,
"navigator": false,
"window": false
"window": false,
// Flow global types
"HTMLInputElement": false,
"ReactClass": false,
"ReactComponent": false,
"ReactElement": false,
"ReactPropsChainableTypeChecker": false,
"ReactPropsCheckType": false,
"ReactPropTypes": false,
"SyntheticEvent": false
},
"rules": {
"camelcase": 0,

View File

@@ -188,7 +188,7 @@ const GameEndOverlay = createReactClass({
const TicTacToeApp = createReactClass({
getInitialState() {
return { board: new Board(), player: 1 }
return { board: new Board(), player: 1 };
},
restartGame() {
@@ -214,7 +214,11 @@ const TicTacToeApp = createReactClass({
const rows = this.state.board.grid.map((cells, row) => (
<View key={'row' + row} style={styles.row}>
{cells.map((player, col) => (
<Cell key={'cell' + col} onPress={this.handleCellPress.bind(this, row, col)} player={player} />
<Cell
key={'cell' + col}
onPress={this.handleCellPress.bind(this, row, col)}
player={player}
/>
))}
</View>
));

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import StyleSheet from '../StyleSheet';
import View from '../../components/View';
import { any, object } from 'prop-types';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import findIndex from 'array-find-index';
import invariant from 'fbjs/lib/invariant';
@@ -17,7 +21,7 @@ class AppState {
static get currentState() {
if (!AppState.isAvailable) {
return AppState.ACTIVE;
return AppStates.ACTIVE;
}
switch (document.visibilityState) {

View File

@@ -2,6 +2,8 @@
* Copyright (c) 2015-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import merge from 'deep-assign';

View File

@@ -8,11 +8,9 @@
*
* web stub for BackAndroid.android.js
*
* @providesModule BackAndroid
* @flow
*/
'use strict';
function emptyFunction() {}
const BackAndroid = {

View File

@@ -1,4 +1,7 @@
/* global window */
/**
* @flow
*/
class Clipboard {
static isSupported() {

View File

@@ -10,7 +10,17 @@ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import debounce from 'debounce';
import invariant from 'fbjs/lib/invariant';
const win = canUseDOM ? window : { screen: {} };
const win = canUseDOM
? window
: {
devicePixelRatio: undefined,
innerHeight: undefined,
innerWidth: undefined,
screen: {
height: undefined,
width: undefined
}
};
const dimensions = {};

View File

@@ -1,9 +1,13 @@
/**
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
type I18nManagerStatus = {
allowRTL: (allowRTL: boolean) => {},
forceRTL: (forceRTL: boolean) => {},
setRTL: (setRTL: boolean) => {},
allowRTL: (allowRTL: boolean) => void,
forceRTL: (forceRTL: boolean) => void,
setPreferredLanguageRTL: (setRTL: boolean) => void,
isRTL: boolean
};

View File

@@ -31,7 +31,7 @@ const InteractionManager = {
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle) {
clearInteractionHandle(handle: number) {
invariant(!!handle, 'Must provide a handle to clear.');
},

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
const Linking = {
addEventListener() {},
removeEventListener() {},
@@ -7,7 +11,7 @@ const Linking = {
getInitialURL() {
return Promise.resolve('');
},
openURL(url) {
openURL(url: string) {
try {
iframeOpen(url);
return Promise.resolve();
@@ -35,9 +39,10 @@ const iframeOpen = url => {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const script = iframeDoc.createElement('script');
const openerExpression = noOpener ? 'child.opener = null' : '';
script.text = `
window.parent = null; window.top = null; window.frameElement = null;
var child = window.open("${url}"); ${noOpener && 'child.opener = null'};
var child = window.open("${url}"); ${openerExpression};
`;
iframeDoc.body.appendChild(script);
document.body.removeChild(iframe);

View File

@@ -50,7 +50,7 @@ const NetInfo = {
connection.removeEventListener(type, handler);
},
fetch(): Promise {
fetch(): Promise<any> {
return new Promise((resolve, reject) => {
try {
resolve(connection.type);
@@ -99,7 +99,7 @@ const NetInfo = {
connectionListeners.splice(listenerIndex, 1);
},
fetch(): Promise {
fetch(): Promise<any> {
return new Promise((resolve, reject) => {
try {
resolve(window.navigator.onLine);

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
const Platform = {
OS: 'web',
select: (obj: Object) => ('web' in obj ? obj.web : obj.default)

View File

@@ -12,6 +12,9 @@
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import invariant from 'fbjs/lib/invariant';
type Atom = number | boolean | Object | Array<?Atom>;
type StyleObj = Atom;
function getStyle(style) {
if (typeof style === 'number') {
return ReactNativePropRegistry.getByID(style);
@@ -19,7 +22,7 @@ function getStyle(style) {
return style;
}
function flattenStyle(style) {
function flattenStyle(style: ?StyleObj): ?Object {
if (!style) {
return undefined;
}

View File

@@ -1,4 +1,10 @@
const vibrate = pattern => {
/**
* @flow
*/
type VibratePattern = number | Array<number>;
const vibrate = (pattern: VibratePattern) => {
if ('vibrate' in window.navigator) {
if (typeof pattern === 'number' || Array.isArray(pattern)) {
window.navigator.vibrate(pattern);
@@ -12,7 +18,7 @@ const Vibration = {
cancel() {
vibrate(0);
},
vibrate(pattern) {
vibrate(pattern: VibratePattern) {
vibrate(pattern);
}
};

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import applyNativeMethods from '../../modules/applyNativeMethods';
import StyleSheet from '../../apis/StyleSheet';
import View from '../View';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import ColorPropType from '../../propTypes/ColorPropType';
import StyleSheet from '../../apis/StyleSheet';
import TouchableOpacity from '../Touchable/TouchableOpacity';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
const ImageResizeMode = {
center: 'center',
contain: 'contain',

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';
import ImageResizeMode from './ImageResizeMode';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
class ImageUriCache {
static _maximumEntries: number = 256;
static _entries = {};

View File

@@ -1,4 +1,8 @@
/* global window */
/**
* @flow
*/
import applyNativeMethods from '../../modules/applyNativeMethods';
import createDOMElement from '../../modules/createDOMElement';
import ImageLoader from '../../modules/ImageLoader';
@@ -42,10 +46,12 @@ const resolveAssetDimensions = source => {
};
const resolveAssetSource = source => {
return (typeof source === 'object' ? source.uri : source) || null;
return (typeof source === 'object' ? source.uri : source) || '';
};
class Image extends Component {
state: { shouldDisplaySource: boolean };
static displayName = 'Image';
static propTypes = {
@@ -76,6 +82,11 @@ class Image extends Component {
static resizeMode = ImageResizeMode;
_imageRequestId = null;
_imageState = null;
_isMounted = false;
_loadRequest = null;
constructor(props, context) {
super(props, context);
// If an image has been loaded before, render it immediately
@@ -84,7 +95,6 @@ class Image extends Component {
this.state = { shouldDisplaySource: isPreviouslyLoaded };
this._imageState = getImageState(uri, isPreviouslyLoaded);
isPreviouslyLoaded && ImageUriCache.add(uri);
this._isMounted = false;
}
componentDidMount() {

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import applyNativeMethods from '../../modules/applyNativeMethods';
import ColorPropType from '../../propTypes/ColorPropType';
import StyleSheet from '../../apis/StyleSheet';

View File

@@ -67,14 +67,11 @@ export default class ScrollViewBase extends Component {
scrollEventThrottle: 0
};
constructor(props) {
super(props);
this._debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100);
this._state = { isScrolling: false };
}
_debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100);
_state = { isScrolling: false, scrollLastTick: 0 };
_handlePreventableScrollEvent = handler => {
return e => {
_handlePreventableScrollEvent = (handler: Function) => {
return (e: Object) => {
if (!this.props.scrollEnabled) {
e.preventDefault();
} else {
@@ -85,7 +82,7 @@ export default class ScrollViewBase extends Component {
};
};
_handleScroll = e => {
_handleScroll = (e: SyntheticEvent) => {
e.persist();
e.stopPropagation();
const { scrollEventThrottle } = this.props;
@@ -102,12 +99,12 @@ export default class ScrollViewBase extends Component {
}
};
_handleScrollStart(e) {
_handleScrollStart(e: Object) {
this._state.isScrolling = true;
this._state.scrollLastTick = Date.now();
}
_handleScrollTick(e) {
_handleScrollTick(e: Object) {
const { onScroll } = this.props;
this._state.scrollLastTick = Date.now();
if (onScroll) {
@@ -115,7 +112,7 @@ export default class ScrollViewBase extends Component {
}
}
_handleScrollEnd(e) {
_handleScrollEnd(e: Object) {
const { onScroll } = this.props;
this._state.isScrolling = false;
if (onScroll) {
@@ -123,7 +120,7 @@ export default class ScrollViewBase extends Component {
}
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
_shouldEmitScrollEvent(lastTick: number, eventThrottle: number) {
const timeSinceLastTick = Date.now() - lastTick;
return eventThrottle > 0 && timeSinceLastTick >= eventThrottle;
}

View File

@@ -17,7 +17,7 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import View from '../View';
import ViewPropTypes from '../View/ViewPropTypes';
import ViewStylePropTypes from '../View/ViewStylePropTypes';
import React, { Component } from 'react';
import React from 'react';
import { bool, element, func, number, oneOf } from 'prop-types';
const emptyObject = {};
@@ -54,7 +54,7 @@ const ScrollView = createReactClass({
* implement this method so that they can be composed while providing access
* to the underlying scroll responder's methods.
*/
getScrollResponder(): Component {
getScrollResponder(): ScrollView {
return this;
},

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import { Component } from 'react';
class StatusBar extends Component {

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import applyNativeMethods from '../../modules/applyNativeMethods';
import ColorPropType from '../../propTypes/ColorPropType';
import createDOMElement from '../../modules/createDOMElement';
@@ -14,6 +18,8 @@ const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)';
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`;
class Switch extends PureComponent {
_checkbox: HTMLInputElement;
static displayName = 'Switch';
static propTypes = {

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import ColorPropType from '../../propTypes/ColorPropType';
import ViewStylePropTypes from '../View/ViewStylePropTypes';
import { number, oneOf, oneOfType, shape, string } from 'prop-types';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import TextStylePropTypes from '../Text/TextStylePropTypes';
import { oneOf } from 'prop-types';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import { Component } from 'react';
@@ -50,6 +54,8 @@ const setSelection = (node, selection) => {
};
class TextInput extends Component {
_node: HTMLInputElement;
static displayName = 'TextInput';
static propTypes = {

View File

@@ -20,7 +20,7 @@ var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
var warning = require('fbjs/lib/warning');
var StyleSheet = require('../../apis/StyleSheet');
import createReactClass from 'create-react-class';
import { bool, func, number, string } from 'prop-types';
import { bool, element, func, number, string } from 'prop-types';
type Event = Object;
@@ -42,6 +42,7 @@ const TouchableWithoutFeedback = createReactClass({
accessible: bool,
accessibilityLabel: string,
accessibilityRole: string,
children: element,
/**
* If true, disable all interactions for this component.
*/

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import AnimationPropTypes from '../../propTypes/AnimationPropTypes';
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';

View File

@@ -1,3 +1,7 @@
/**
* @flow
*/
import AccessibilityUtil from '../../modules/AccessibilityUtil';
import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';

View File

@@ -15,6 +15,29 @@ import UIManager from '../../apis/UIManager';
const hyphenPattern = /-([a-z])/g;
const toCamelCase = str => str.replace(hyphenPattern, m => m[1].toUpperCase());
type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void;
type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number
) => void;
type MeasureLayoutOnSuccessCallback = (
left: number,
top: number,
width: number,
height: number
) => void;
const NativeMethodsMixin = {
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
@@ -34,7 +57,7 @@ const NativeMethodsMixin = {
/**
* Determines the position and dimensions of the view
*/
measure(callback) {
measure(callback: MeasureOnSuccessCallback) {
UIManager.measure(findNodeHandle(this), callback);
},
@@ -53,14 +76,18 @@ const NativeMethodsMixin = {
* Note that these measurements are not available until after the rendering
* has been completed.
*/
measureInWindow(callback) {
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
UIManager.measureInWindow(findNodeHandle(this), callback);
},
/**
* Measures the view relative to another view (usually an ancestor)
*/
measureLayout(relativeToNativeNode, onSuccess, onFail) {
measureLayout(
relativeToNativeNode: Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void
) {
UIManager.measureLayout(findNodeHandle(this), relativeToNativeNode, onFail, onSuccess);
},

View File

@@ -35,7 +35,7 @@ const safeOverride = (original, next) => {
return next;
};
const applyLayout = Component => {
const applyLayout = (Component: ReactClass<any>) => {
const componentDidMount = Component.prototype.componentDidMount;
const componentDidUpdate = Component.prototype.componentDidUpdate;
const componentWillUnmount = Component.prototype.componentWillUnmount;

View File

@@ -7,7 +7,7 @@
import NativeMethodsMixin from '../NativeMethodsMixin';
const applyNativeMethods = Component => {
const applyNativeMethods = (Component: ReactClass<any>) => {
Object.keys(NativeMethodsMixin).forEach(method => {
if (!Component.prototype[method]) {
Component.prototype[method] = NativeMethodsMixin[method];

View File

@@ -10,11 +10,18 @@
* @providesModule EdgeInsetsPropType
* @flow
*/
'use strict';
import createStrictShapeTypeChecker from './createStrictShapeTypeChecker';
import { number } from 'prop-types';
var EdgeInsetsPropType = require('./createStrictShapeTypeChecker')({
export type EdgeInsetsProp = {
top: number,
left: number,
bottom: number,
right: number
};
const EdgeInsetsPropType = createStrictShapeTypeChecker({
top: number,
left: number,
bottom: number,

View File

@@ -5,18 +5,20 @@
* @flow
*/
module.exports = function StyleSheetPropType(shape) {
function StyleSheetPropType(shape: { [key: string]: ReactPropsCheckType }): ReactPropsCheckType {
const createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
const StyleSheet = require('../apis/StyleSheet');
const shapePropType = createStrictShapeTypeChecker(shape);
return function(props, propName, componentName, location?) {
return function(props, propName, componentName, location?, ...rest) {
let newProps = props;
if (props[propName]) {
// Just make a dummy prop object with only the flattened style
newProps = {};
newProps[propName] = StyleSheet.flatten(props[propName]);
}
return shapePropType(newProps, propName, componentName, location);
return shapePropType(newProps, propName, componentName, location, ...rest);
};
};
}
module.exports = StyleSheetPropType;

View File

@@ -8,11 +8,7 @@
*/
declare module 'fbjs/lib/invariant' {
declare function exports<T>(
condition: any,
message: string,
...args: Array<any>
): void;
declare function exports<T>(condition: any, message: string, ...args: Array<any>): void;
}
declare module 'fbjs/lib/nullthrows' {