Analyzing React’s Source Code for Type Mismatch Operations

Vitaliy Potapov
May 8, 2017 · 5 min read
Image for post
Image for post

Several weeks ago, I created a Babel plugin for runtime type-checking in JavaScript. After testing it on my own projects I applied it to React’s source code and got some interesting results. In this article, I will go step by step through my experiment.

What is checked

How it works

// before
return x + y;
// after
return add(x, y);
function add(a, b) {
if (typeof a !== typeof b) {
console.warn('Warning: ' + typeof a + ' + ' + typeof b);
}
return a + b;
}

The plugin guess types by code itself with no manual annotations needed. That allows to apply such checking to any existing project with minimal effort. Let’s try it on React.

Plan

  1. Include Runtyper into the build process.
  2. Run all of React’s unit tests and analyze the output.

1. Get React source code

git clone https://github.com/facebook/react.git
cd react && npm install

2. Include Runtyper into build

npm install babel-plugin-runtyper --save-dev

and included it to scripts/jest/preprocessor.js with following config:

plugins: babelOptions.plugins.concat([
[path.resolve('../babel-plugin-runtyper'), {
enabled: true,
warnLevel: 'info',
implicitAddStringNumber: 'allow',
implicitEqualNull: 'allow',
implicitEqualUndefined: 'allow',
explicitAddEmptyString: 'allow',
implicitEqualCustomTypes: 'allow',
}],
]),

I allowed some rules because the default configuration of plugin is very strict. For example, implicitAddStringNumber allows concatenation ofstring + number . It is widely used in React code and will noise results.

Also I disabled a few lines in DOMProperty.js and Transaction.js. React executes these lines many times and their warnings overfill the output. In results section I will cover what is happening there.

3. Run tests

jest --no-colors 2> log.txt

Results

1. ReactDOMComponent.js:956

Strict equal of different types: {} (object) === "foo" (string)

Code (source):

if (        
!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
(nextProp == null && lastProp == null)
) {
continue;
}

This line detects props of component that did not change. As props in React can be of any type, it looks like nothing wrong with it.

2. dangerousStyleValue.js:49

Add operation should be used for numbers or strings: NaN + "px" (string)

Code (source):

if (    
typeof value === 'number' &&
value !== 0 &&
!(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name]) ) {
return value + 'px';
}

Concatenation of NaN + "px" will return string "NaNpx" that looks like a bug. But when I inspected the test code I found that this is intentional check for NaN. React cares of developers and shows warning if some style calculates to NaN .

First good point is that runtime analysis warned it also. If similar concatenation happens in your code you will instantly know about it.

And the second point: what if style property calculates to Infinity ? For example, { fontSize: 1/0 }. Currently, React does not warn about it and tries to render:

"font-size: Infinitypx;"

Browsers ignore this css but warning would be helpful.

3. shouldUpdateReactComponent.js:26

Strict equal of different types: "bar" (string) === false (boolean)

Code (source):

function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
...
}

This function compares two React elements. And makes decision should component be updated or not. Internally it compares elements with false . Frankly speaking, I can’t qualify this. Because the difference between null and false element is not clear for me. Documentation says: any of true|false|null|undefined is ignored and not rendered. It seems like null should be enough to flag that element does not exist.

4. ReactComponentTreeTestUtils.js:35

Add operation should be used for numbers or strings: 
"Wrapper > div > " (string) + null

Code (source):

var path = parentPath ? `${parentPath} > ${displayName}` : displayName;

This line generates string of component tree path for testing purpose. Here it will return "Wrapper > div > null" that looks incorrect. I wondered why this does not break tests? I saw closely into the source code. Function expectTree() compares expected and actual trees but does not check displayName. That means I can change this line in test code from "Bar" to "Bar123" and test will continue to pass. It is not big impact for React users because module is not exported. But needs to put attention on it as some React tests are irrelevant.

5. DOMProperty.js:105

Add operation should be used for numbers or strings: 0 (number) + false (boolean)

Code (source):

invariant(
propertyInfo.hasBooleanValue +
propertyInfo.hasNumericValue +
propertyInfo.hasOverloadedBooleanValue <= 1,
...

That is line I disabled before running tests. React calls it during initialization in every pre-test setup. The code ensures that only one of three boolean values is true. The solution is elegant: just check that sum is less or equal 1. For me this is esthetic question — maybe explicit conversion to number will look better:

invariant(
Number(propertyInfo.hasBooleanValue) +
Number(propertyInfo.hasNumericValue) +
Number(propertyInfo.hasOverloadedBooleanValue) <= 1,
...

Conclusion

You can inspect the full results yourself and play with Runtyper in your project. I‘m sure such a tool will help to find more bugs and edge-cases before production.

I will appreciate your feedback and comments. Thanks for reading!

If you liked this, click the💚 below to share it with other developers here on Medium.

DailyJS

JavaScript news and opinion.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store