Analyzing React’s Source Code for Type Mismatch Operations

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

Every math or comparison operation with different types in JavaScript is potentially unsafe. You can get silent unexpected result because values are converted by tricky rules. For example, 1 + 1 = 2 but if you accidentally add 1 + "1" you will get "11". To avoid such errors you can use Flow, TypeScript or check operand types in runtime. I will apply the last approach to the React source code.

How it works

The plugin called Runtyper wraps every operation into a function that performs some additional type checking:

// 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. Get the React source code.
  2. Include Runtyper into the build process.
  3. Run all of React’s unit tests and analyze the output.

1. Get React source code

To get React’s source code I cloned its repo from GitHub and installed all of the dependencies:

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

2. Include Runtyper into build

I installed Runtyper from npm:

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

React uses Jest for self-testing. I ran all tests and piped output to log.txt for later analysis:

jest --no-colors 2> log.txt

Results

When tests finished I found 33 unique places with type-mismatch operations. I published the full log here for everybody’s access. Below I will analyze 5 most interesting cases.

1. ReactDOMComponent.js:956

Warning:

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

Warning:

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

Warning:

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

Warning:

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

Warning:

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

It was interesting experiment to perform runtime type-checking of React source code. Most of warnings are just “info” and intended by React design. Some can be fixed. Finally I’ve created two pull requests:

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.

72

72 claps
Vitaliy Potapov

Written by

Web developer at Yandex

DailyJS

JavaScript news and opinion.