Skip to content

Commit

Permalink
add FlowType annotations (Khan#162)
Browse files Browse the repository at this point in the history
* add FlowType and run after ESLint

* add FlowType annotations using comments syntax

* FlowType: ignore problematic module files
  • Loading branch information
jokeyrhyme authored and xymostech committed Nov 16, 2016
1 parent 6e9a50d commit 35e22c4
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 32 deletions.
14 changes: 14 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[ignore]
<PROJECT_ROOT>/.nyc_output/.*
<PROJECT_ROOT>/coverage/.*
<PROJECT_ROOT>/dist/.*
.*/node_modules/babel.*
.*/node_modules/fbjs/.*
.*/node_modules/y18n/test/.*

[include]

[libs]

[options]
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,6 @@ It isn't determinate whether divs will be red or blue.
# TODO
- Add Flow annotations
- Add JSdoc
- Consider removing !important from everything.
Expand Down
1 change: 1 addition & 0 deletions examples/src/StyleTester.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* @flow */
import React from 'react';
import { StyleSheet, css } from '../../src/index.js';

Expand Down
1 change: 1 addition & 0 deletions examples/src/examples-ssr.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* @flow */
import ReactDOMServer from 'react-dom/server';
import React from 'react';
import { StyleSheetServer } from '../../src/index.js';
Expand Down
1 change: 1 addition & 0 deletions examples/src/examples.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* @flow */
import ReactDOM from 'react-dom';
import React from 'react';
import { StyleSheet } from '../../src/index.js';
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"main": "lib/index.js",
"scripts": {
"lint": "eslint --fix --cache .",
"lint": "eslint --fix --cache . && flow check",
"test": "npm run coverage",
"posttest": "npm run lint",
"coverage": "nyc --check-coverage --lines 100 --branches 100 npm run tests",
Expand Down Expand Up @@ -47,6 +47,7 @@
"eslint": "^3.7.1",
"eslint-config-standard-react": "^4.2.0",
"eslint-plugin-react": "^6.3.0",
"flow-bin": "^0.34.0",
"jsdom": "^6.5.1",
"mocha": "^2.3.3",
"npm-run-all": "^1.7.0",
Expand Down
27 changes: 21 additions & 6 deletions src/exports.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
/* @flow */
import {mapObj, hashObject} from './util';
import {
injectAndGetClassName,
reset, startBuffering, flushToString,
addRenderedClassNames, getRenderedClassNames,
} from './inject';

/* ::
import type { SelectorHandler } from './generate.js';
export type SheetDefinition = { [id:string]: any };
export type SheetDefinitions = SheetDefinition | SheetDefinition[];
type RenderFunction = () => string;
type Extension = {
selectorHandler: SelectorHandler
};
export type MaybeSheetDefinition = SheetDefinition | false | null | void
*/

const StyleSheet = {
create(sheetDefinition) {
create(sheetDefinition /* : SheetDefinition */) {
return mapObj(sheetDefinition, ([key, val]) => {
return [key, {
// TODO(emily): Make a 'production' mode which doesn't prepend
Expand All @@ -17,7 +29,7 @@ const StyleSheet = {
});
},

rehydrate(renderedClassNames=[]) {
rehydrate(renderedClassNames /* : string[] */ =[]) {
addRenderedClassNames(renderedClassNames);
},
};
Expand All @@ -26,7 +38,7 @@ const StyleSheet = {
* Utilities for using Aphrodite server-side.
*/
const StyleSheetServer = {
renderStatic(renderFunc) {
renderStatic(renderFunc /* : RenderFunction */) {
reset();
startBuffering();
const html = renderFunc();
Expand Down Expand Up @@ -76,7 +88,10 @@ const StyleSheetTestUtils = {
* Generate the Aphrodite API exports, with given `selectorHandlers` and
* `useImportant` state.
*/
const makeExports = (useImportant, selectorHandlers) => {
const makeExports = (
useImportant /* : boolean */,
selectorHandlers /* : SelectorHandler[] */
) => {
return {
StyleSheet: {
...StyleSheet,
Expand All @@ -97,7 +112,7 @@ const makeExports = (useImportant, selectorHandlers) => {
* @returns {Object} An object containing the exports of the new
* instance of Aphrodite.
*/
extend(extensions) {
extend(extensions /* : Extension[] */) {
const extensionSelectorHandlers = extensions
// Pull out extensions with a selectorHandler property
.map(extension => extension.selectorHandler)
Expand All @@ -115,7 +130,7 @@ const makeExports = (useImportant, selectorHandlers) => {
StyleSheetServer,
StyleSheetTestUtils,

css(...styleDefinitions) {
css(...styleDefinitions /* : MaybeSheetDefinition[] */) {
return injectAndGetClassName(
useImportant, styleDefinitions, selectorHandlers);
},
Expand Down
48 changes: 41 additions & 7 deletions src/generate.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
/* @flow */
import prefixAll from 'inline-style-prefixer/static';

import {
objectToPairs, kebabifyStyleName, recursiveMerge, stringifyValue,
importantify, flatten
} from './util';

/* ::
import type { SheetDefinition } from './index.js';
type StringHandlers = { [id:string]: Function };
type SelectorCallback = (selector: string) => any;
export type SelectorHandler = (
selector: string,
baseSelector: string,
callback: SelectorCallback
) => string | null;
*/

/**
* `selectorHandlers` are functions which handle special selectors which act
* differently than normal style definitions. These functions look at the
Expand Down Expand Up @@ -55,15 +67,23 @@ import {
*/
export const defaultSelectorHandlers = [
// Handle pseudo-selectors, like :hover and :nth-child(3n)
function pseudoSelectors(selector, baseSelector, generateSubtreeStyles) {
function pseudoSelectors(
selector /* : string */,
baseSelector /* : string */,
generateSubtreeStyles /* : Function */
) /* */ {
if (selector[0] !== ":") {
return null;
}
return generateSubtreeStyles(baseSelector + selector);
},

// Handle media queries (or font-faces)
function mediaQueries(selector, baseSelector, generateSubtreeStyles) {
function mediaQueries(
selector /* : string */,
baseSelector /* : string */,
generateSubtreeStyles /* : Function */
) /* */ {
if (selector[0] !== "@") {
return null;
}
Expand Down Expand Up @@ -121,8 +141,13 @@ export const defaultSelectorHandlers = [
* generateCSSRuleset(".foo", { height: 20 }, ...)
* generateCSSRuleset(".foo:hover", { backgroundColor: "black" }, ...)
*/
export const generateCSS = (selector, styleTypes, selectorHandlers=[],
stringHandlers={}, useImportant=true) => {
export const generateCSS = (
selector /* : string */,
styleTypes /* : SheetDefinition[] */,
selectorHandlers /* : SelectorHandler[] */ = [],
stringHandlers /* : StringHandlers */ = {},
useImportant /* : boolean */ = true
) /* : string */ => {
const merged = styleTypes.reduce(recursiveMerge);

const plainDeclarations = {};
Expand Down Expand Up @@ -165,7 +190,11 @@ export const generateCSS = (selector, styleTypes, selectorHandlers=[],
*
* See generateCSSRuleset for usage and documentation of paramater types.
*/
const runStringHandlers = (declarations, stringHandlers, selectorHandlers) => {
const runStringHandlers = (
declarations /* : SheetDefinition */,
stringHandlers /* : StringHandlers */,
selectorHandlers /* : SelectorHandler[] */
) /* */ => {
const result = {};

Object.keys(declarations).forEach(key => {
Expand Down Expand Up @@ -218,8 +247,13 @@ const runStringHandlers = (declarations, stringHandlers, selectorHandlers) => {
* generateCSSRuleset(".blah:hover", { color: "red" })
* -> ".blah:hover{color: red}"
*/
export const generateCSSRuleset = (selector, declarations, stringHandlers,
useImportant, selectorHandlers) => {
export const generateCSSRuleset = (
selector /* : string */,
declarations /* : SheetDefinition */,
stringHandlers /* : StringHandlers */,
useImportant /* : boolean */,
selectorHandlers /* : SelectorHandler[] */
) /* : string */ => {
const handledDeclarations = runStringHandlers(
declarations, stringHandlers, selectorHandlers);

Expand Down
29 changes: 23 additions & 6 deletions src/inject.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
/* @flow */
import asap from 'asap';

import {generateCSS} from './generate';
import {flattenDeep, hashObject} from './util';

/* ::
import type { SheetDefinition, SheetDefinitions } from './index.js';
import type { MaybeSheetDefinition } from './exports.js';
import type { SelectorHandler } from './generate.js';
*/

// The current <style> tag we are inserting into, or null if we haven't
// inserted anything yet. We could find this each time using
// `document.querySelector("style[data-aphrodite"])`, but holding onto it is
Expand All @@ -14,7 +21,7 @@ let styleTag = null;
// multiple injections. It will also use a style tag with the `data-aphrodite`
// tag on it if that exists in the DOM. This could be used for e.g. reusing the
// same style tag that server-side rendering inserts.
const injectStyleTag = (cssContents) => {
const injectStyleTag = (cssContents /* : string */) => {
if (styleTag == null) {
// Try to find a style tag with the `data-aphrodite` attribute first.
styleTag = document.querySelector("style[data-aphrodite]");
Expand All @@ -32,7 +39,9 @@ const injectStyleTag = (cssContents) => {
}
}


if (styleTag.styleSheet) {
// $FlowFixMe: legacy Internet Explorer compatibility
styleTag.styleSheet.cssText += cssContents;
} else {
styleTag.appendChild(document.createTextNode(cssContents));
Expand Down Expand Up @@ -138,8 +147,13 @@ const injectGeneratedCSSOnce = (key, generatedCSS) => {
}
}

export const injectStyleOnce = (key, selector, definitions, useImportant,
selectorHandlers) => {
export const injectStyleOnce = (
key /* : string */,
selector /* : string */,
definitions /* : SheetDefinition[] */,
useImportant /* : boolean */,
selectorHandlers /* : SelectorHandler[] */ = []
) => {
if (!alreadyInjected[key]) {
const generated = generateCSS(
selector, definitions, selectorHandlers,
Expand Down Expand Up @@ -182,7 +196,7 @@ export const getRenderedClassNames = () => {
return Object.keys(alreadyInjected);
};

export const addRenderedClassNames = (classNames) => {
export const addRenderedClassNames = (classNames /* : string[] */) => {
classNames.forEach(className => {
alreadyInjected[className] = true;
});
Expand All @@ -198,8 +212,11 @@ export const addRenderedClassNames = (classNames) => {
* arbitrarily nested arrays of them, as returned as properties of the
* return value of StyleSheet.create().
*/
export const injectAndGetClassName = (useImportant, styleDefinitions,
selectorHandlers) => {
export const injectAndGetClassName = (
useImportant /* : boolean */,
styleDefinitions /* : MaybeSheetDefinition[] */,
selectorHandlers /* : SelectorHandler[] */
) /* : string */ => {
styleDefinitions = flattenDeep(styleDefinitions);

// Filter out falsy values from the input, to allow for
Expand Down
1 change: 1 addition & 0 deletions src/no-important.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* @flow */
// Module with the same interface as the core aphrodite module,
// except that styles injected do not automatically have !important
// appended to them.
Expand Down
40 changes: 29 additions & 11 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
/* @flow */

/* ::
type Pair = [ string, any ];
type Pairs = Pair[];
type PairsMapper = (pair: Pair) => Pair;
type ObjectMap = { [id:string]: any };
*/

// {K1: V1, K2: V2, ...} -> [[K1, V1], [K2, V2]]
export const objectToPairs = (obj) => Object.keys(obj).map(key => [key, obj[key]]);
export const objectToPairs = (obj /* : ObjectMap */) /* : Pairs */ => Object.keys(obj).map(key => [key, obj[key]]);

// [[K1, V1], [K2, V2]] -> {K1: V1, K2: V2, ...}
const pairsToObject = (pairs) => {
const pairsToObject = (pairs /* : Pairs */) /* : ObjectMap */ => {
const result = {};
pairs.forEach(([key, val]) => {
result[key] = val;
});
return result;
};

export const mapObj = (obj, fn) => pairsToObject(objectToPairs(obj).map(fn))
export const mapObj = (
obj /* : ObjectMap */,
fn /* : PairsMapper */
) /* : ObjectMap */ => pairsToObject(objectToPairs(obj).map(fn))

// Flattens an array one level
// [[A], [B, C, [D]]] -> [A, B, C, [D]]
export const flatten = (list) => list.reduce((memo, x) => memo.concat(x), []);
export const flatten = (list /* : any[] */) /* : any[] */ => list.reduce((memo, x) => memo.concat(x), []);

export const flattenDeep = (list) =>
export const flattenDeep = (list /* : any[] */) /* : any[] */ =>
list.reduce((memo, x) => memo.concat(Array.isArray(x) ? flattenDeep(x) : x), []);

const UPPERCASE_RE = /([A-Z])/g;
const MS_RE = /^ms-/;

const kebabify = (string) => string.replace(UPPERCASE_RE, '-$1').toLowerCase();
export const kebabifyStyleName = (string) => kebabify(string).replace(MS_RE, '-ms-');
const kebabify = (string /* : string */) /* : string */ => string.replace(UPPERCASE_RE, '-$1').toLowerCase();
export const kebabifyStyleName = (string /* : string */) /* : string */ => kebabify(string).replace(MS_RE, '-ms-');

export const recursiveMerge = (a, b) => {
export const recursiveMerge = (
a /* : ObjectMap | any */,
b /* : ObjectMap */
) /* : ObjectMap */ => {
// TODO(jlfwong): Handle malformed input where a and b are not the same
// type.

Expand Down Expand Up @@ -117,7 +132,10 @@ Object.keys(isUnitlessNumber).forEach(function(prop) {
});
});

export const stringifyValue = (key, prop) => {
export const stringifyValue = (
key /* : string */,
prop /* : any */
) /* : string */ => {
if (typeof prop === "number") {
if (isUnitlessNumber[key]) {
return "" + prop;
Expand Down Expand Up @@ -186,14 +204,14 @@ function murmurhash2_32_gc(str) {
// this to produce consistent hashes browsers need to have a consistent
// ordering of objects. Ben Alpert says that Facebook depends on this, so we
// can probably depend on this too.
export const hashObject = (object) => murmurhash2_32_gc(JSON.stringify(object));
export const hashObject = (object /* : ObjectMap */) /* : string */ => murmurhash2_32_gc(JSON.stringify(object));


const IMPORTANT_RE = /^([^:]+:.*?)( !important)?;$/;

// Given a single style rule string like "a: b;", adds !important to generate
// "a: b !important;".
export const importantify = (string) =>
export const importantify = (string /* : string */) /* : string */ =>
string.replace(
IMPORTANT_RE,
(_, base) => base + " !important;");

0 comments on commit 35e22c4

Please sign in to comment.