Giter Site home page Giter Site logo

babel-plugin-runtyper's Introduction

Runtyper

Build Status Sauce Test Status npm license

Protect your App from type-coercion bugs

Runtyper is a Babel plugin for runtime type-checking in JavaScript. You should enable it for non-production build and check console for type-coercion warnings. As it works in runtime - no manual type-annotations needed in your codebase.

Tested in:
Sauce Test Status

Contents

Example

Imagine you have comparison x === y and in runtime values are x = 1, y = "1". When executed you will get false. In many cases this result is unexpected: you just missed type conversion. After applying Runtyper it will show warning when such situation happen:

Strict compare warning example

or you can configure to throw errors:

Strict compare error example

How it works

Runtyper wraps all type-important operations into function. When line is executed, function checks the argument types first and only then returns the result.
For example, before:

if (x === y) { ... }

After (simplified):

if (strictEqual(x, y)) { ... }

function strictEqual(a, b) {
  if (typeof a !== typeof b) {
    console.warn('Strict compare of different types: ' + typeof a + ' === ' + typeof b);
  }
  return a === b;
}

Installation

  1. Ensure you have Babel installed
  2. Install Runtyper from npm:
npm install babel-plugin-runtyper --save-dev

Usage

  1. No changes to your existing codebase needed.

  2. Add babel-plugin-runtyper to Babel config:

    • in .babelrc:

      {
        "plugins": ["babel-plugin-runtyper"]
      }

      To apply plugin only for development builds consider Babel's env option:

      {
        "env": {
          "development": {
            "plugins": ["babel-plugin-runtyper"]
          }
        }
      }
    • in webpack config:

        module: {
          rules: [
            {
              test: /\.js$/,
              exclude: /node_modules/,
              use: {
                loader: 'babel-loader',
                options: {
                  plugins: [
                    ['babel-plugin-runtyper', {enabled: process.env.NODE_ENV !== 'production'}]
                  ]
                }
              }
            }
          ]
        }  

      Please note to run webpack as NODE_ENV='production' webpack -p (see #2537)

    • in package.json scripts:

      "scripts": {
        "runtyper": "babel src --out-dir out --plugins=babel-plugin-runtyper --source-maps"
      }
  3. Enable source-maps to see original place of error:

  • In Chrome set Enable JavaScript source maps in devtools settings
  • In Firefox please follow this instruction
  • In Node.js use source-map-support package:
    require('source-map-support').install();

Tip: checkout examples directory to see browser and Node.js demos

Configuration

To configure plugin pass it to Babel as array:

  plugins: [
      ['babel-plugin-runtyper', options]
  ]

Options

Name Default Values Description
enabled true true, false Is plugin enabled
warnLevel "warn" "info", "warn", "error", "break" How do you want to be notified
implicitAddStringNumber "deny" "allow", "deny" Allow/deny (variable1) + (variable2) where (variable1), (variable1) are (string, number)
implicitEqualNull "deny" "allow", "deny" Allow/deny (variable1) === (variable2) where (variable1) or (variable2) is null
implicitEqualUndefined "deny" "allow", "deny" Allow/deny (variable1) === (variable2) where (variable1) or (variable2) is undefined
explicitAddEmptyString "deny" "allow", "deny" Allow/deny (variable) + "" where (variable) is not string
explicitEqualTrue "deny" "allow", "deny" Allow/deny (variable) === true where (variable) is not boolean
explicitEqualFalse "deny" "allow", "deny" Allow/deny (variable) === false where (variable) is not boolean
implicitEqualCustomTypes "deny" "allow", "deny" Allow/deny (variable1) === (variable2) where (variable1) instanceof MyClass1 and (variable2) instanceof MyClass2
excludeOperators [] ["equal", "numeric", "add", "relational"] Excludes operators checking where equal excludes ===, numeric excludes -, *, /, %, add excludes + and relational excludes >, >=, <, <=
forbiddenNodeEnvs ["production"] Array<String> Values of NODE_ENV where plugin shows warning if enabled

Warning level description

  • info - notification via console.info without stacktrace
  • warn - notification via console.warn with stacktrace
  • error - notification via console.error with stacktrace
  • break - notification via throwing error and breaking execution

The softest configuration

By default configuration is very strict. You can start with the softest one:

{
    enabled: true,
    implicitAddStringNumber: "allow",
    implicitEqualNull: "allow",
    implicitEqualUndefined: "allow",
    explicitAddEmptyString: "allow",
    explicitEqualTrue: "allow",
    explicitEqualFalse: "allow",
    implicitEqualCustomTypes: "allow"
}

The result can be something like this:

Error: Strict equal of different types: -1 (number) === "" (string)
Error: Strict equal of different types: 2 (number) === "" (string)
Error: Strict equal of different types: 56.9364 (number) === "" (string)
Error: Strict equal of different types: -0.0869 (number) === "" (string)
Error: Numeric operation with non-numeric value: null / 60 (number)
Error: Numeric operation with non-numeric value: "2017-03-29T00:00:00... (Date) / 1000 (number)
Error: Numeric operation with non-numeric value: "2017-03-29T00:00:00... (Date) / 1000 (number)
...

Supported operators

  • Strict equality (===, !==)
    Protects you from:

    1 === '1'        // false
    1 === [1]        // false
    1 === new Date() // false
    ...
  • Addition (+)
    Protects you from:

    '1' + null      // '1null'
    '1' + undefined // '1undefined'
    '1' + NaN       // '1NaN'
    1 + NaN         // NaN
    1 + {}          // '1[object Object]'
    ...
  • Arithmetic (-, *, /, %)
    Protects you from:

    1 - '1px'     // NaN
    1 - undefined // NaN
    1 * null      // 0
    1 * {}        // NaN
    ...
  • Relational (>, >=, <, <=)
    Protects you from:

    2 < '11'        // false (but '1' < '11' is true)
    1 < null        // false
    [1] < {}        // true
    2 < [11, null]  // false (but 2 < [11] is true)
    ...

Ignore line

You can exclude line from checking by special comment:

if (x === y) { // runtyper-disable-line 
    ...
}

Run on existing project

You can easily try Runtyper on existing project because no special code-annotations needed. Just build your project with Runtyper enabled and perform some actions in the app. Then inspect the console. You may see some warnings about type-mismatch operations:

Error: Strict equal of different types: -1 (number) === "" (string)
Error: Strict equal of different types: 2 (number) === "" (string)
Error: Strict equal of different types: 56.9364 (number) === "" (string)
Error: Strict equal of different types: -0.0869 (number) === "" (string)
Error: Numeric operation with non-numeric value: null / 60 (number)
Error: Numeric operation with non-numeric value: "2017-03-29T00:00:00... (Date) / 1000 (number)
Error: Numeric operation with non-numeric value: "2017-03-29T00:00:00... (Date) / 1000 (number)
...

Usage with Flow and TypeScript

Static code analysis also performs type checking. You can use Runtyper together with Flow or TypeScript to detect errors on both build and runtime stages.

Yet, static tools need extra efforts for:

  • Writing type-annotations
  • Integration with third-party libraries (as their API should be also annotated)
  • Processing external events from user / server (many different formats)
  • Training new members who is not familiar with typed JavaScript

To learn more about pros and cons of static types have a look on Eric Elliott's article You Might Not Need TypeScript (or Static Types).

Runtyper covers more cases due to it's runtime nature

Let's take an example from Flow's get started page:

// @flow
function square(n) {
  return n * n; // Error!
}

square("2");

But if square() is used to handle user's input from text field - error will not be found:

// @flow
function square(n) {
  return n * n; // no Error, until you annotate `event.target.value`
}

window.document.getElementById('username').addEventListener('change', function (event) {
  square(event.target.value);
});

Runtyper allows to catch such cases in runtime:

Textfield error

Consider both approaches to make your applications more robust and reliable.

Articles

FAQ

  1. Why I get error for template literals like ${name}${index}?
    Likely you are using babel-preset-es2015 that transforms template literals into concatenation +. And you get (string) + (number). You can fix it in several ways:

    • set plugin option implicitAddStringNumber: "allow"
    • add explicit conversion: ${name}${String(index)}
    • consider using babel-preset-env as many browsers already have native support of template literals
  2. Why explicit comparing like x === null or x === undefined are not warned?
    When you explicitly write (variable) === null you assume that variable can be null.

  3. Does it check non-strict equal == and !=?
    Nope. Non-strict comparison is a bad thing in most cases. Just quote Douglas Crockford from JavaScript, the Good Parts:

    JavaScript has two sets of equality operators: === and !==, and their evil twins == and !=. The good ones work the way you would expect. If the two operands are of the same type and have the same value, then === produces true and !== produces false. The evil twins do the right thing when the operands are of the same type, but if they are of different types, they attempt to coerce the values. The rules by which they do that are complicated and unmemorable.
    These are some of the interesting cases:

    '' == '0'           // false
    0 == ''             // true
    0 == '0'            // true
    
    false == 'false'    // false
    false == '0'        // true
    
    false == undefined  // false
    false == null       // false
    null == undefined   // true
    
    ' \t\r\n ' == 0     // true

    The lack of transitivity is alarming. My advice is to never use == and !=. Instead, always use === and !==.

    Explicit is always better when implicit, especially for readers of your code. You can set ESLint eqeqeq rule and forget about == once and for all.

If you have other questions or ideas feel free to open new issue.

Related links

Contributors

Thanks goes to these wonderful people (emoji key):


Vitaliy Potapov

💻

Revelup Zilvinas Rudzionis

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT @ Vitaliy Potapov

babel-plugin-runtyper's People

Contributors

vitalets avatar zilvinasrudzionis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

babel-plugin-runtyper's Issues

Guess variable type by it's name and check in assignment

For some variable names it is possible to guess type and check in assignment.
For example, this code could show warning:

const usersCount = '10';

Guessing rules:
Number

  • ends with count, length

String

  • ends with str

Boolean

  • starts with is, has

When compare objects split to Arrays, Date, etc

Currently if we have in runtime <Array> === <Date> it is considered ok because treated as (object) === (object). But it seems not normal situation. Treating object types should be more smart.

Check for NaN in all assertions

In most cases any operation with NaN is not expected and leads to errors.

NaN + 1   // NaN
NaN * 1   // NaN
NaN + '1' // 'NaN1'
...

prevent checking in library files or exclude directories

I didn't see anything in the documentation about this but I'd like to see a feature where we can exclude functions from libraries or certain directories (aka, node_modules).

I realize that this would reduce the coverage but right now the tool is giving unusable results.

I'm using it on my React Native project. I added it Babel and I'm running my tests to see the analysis.

I end up with several thousand errors and warning with things like:

console.warn node_modules/react-native/Libraries/StyleSheet/flattenStyle.js:30
      Error: Strict equal of different types: [{"marginRight":7,"m... (Array) !== true (boolean)
          at notStrictEqual (/Users/gmccullough/Development/Build/BuildApp/node_modules/react-native/Libraries/StyleSheet/flattenStyle.js:30:975)
          at flattenStyle (/Users/gmccullough/Development/Build/BuildApp/node_modules/react-native/Libraries/StyleSheet/flattenStyle.js:30:1006)
          at Object.style (/Users/gmccullough/Development/Build/BuildApp/node_modules/react-native/Libraries/StyleSheet/StyleSheetPropType.js:26:20)
          at checkReactTypeSpec (/Users/gmccullough/Development/Build/BuildApp/node_modules/react/lib/checkReactTypeSpec.js:57:40)
          at validatePropTypes (/Users/gmccullough/Development/Build/BuildApp/node_modules/react/lib/ReactElementValidator.js:151:5)
          at Object.ReactElementValidator.createElement (/Users/gmccullough/Development/Build/BuildApp/node_modules/react/lib/ReactElementValidator.js:194:5)
          at /Users/gmccullough/Development/Build/BuildApp/app/components/Library/Pager/Pager.js:71:17
          at Array.map (native)
          at Pager._this.renderPageMarker (/Users/gmccullough/Development/Build/BuildApp/app/components/Library/Pager/Pager.js:69:24)
          at Pager._this.renderIOSPager (/Users/gmccullough/Development/Build/BuildApp/app/components/Library/Pager/Pager.js:127:7)

or

console.warn node_modules/jsdom/lib/jsdom/level2/html.js:60
      Error: Strict equal of different types: undefined !== false (boolean)
          at notStrictEqual (/Users/gmccullough/Development/Build/BuildApp/node_modules/jsdom/lib/jsdom/level2/html.js:60:977)
          at /Users/gmccullough/Development/Build/BuildApp/node_modules/jsdom/lib/jsdom/level2/html.js:60:1008
          at Array.forEach (native)
          at define (/Users/gmccullough/Development/Build/BuildApp/node_modules/jsdom/lib/jsdom/level2/html.js:56:7)
          at Object.<anonymous> (/Users/gmccullough/Development/Build/BuildApp/node_modules/jsdom/lib/jsdom/level2/html.js:1455:1)
          at Runtime._execModule (/Users/gmccullough/Development/Build/BuildApp/node_modules/jest-runtime/build/index.js:447:13)
          at Runtime.requireModule (/Users/gmccullough/Development/Build/BuildApp/node_modules/jest-runtime/build/index.js:295:14)
          at Runtime.requireModuleOrMock (/Users/gmccullough/Development/Build/BuildApp/node_modules/jest-runtime/build/index.js:365:19)
          at Object.<anonymous> (/Users/gmccullough/Development/Build/BuildApp/node_modules/jsdom/lib/jsdom/living/index.js:8:1)
          at Runtime._execModule (/Users/gmccullough/Development/Build/BuildApp/node_modules/jest-runtime/build/index.js:447:13)

I don't want to turn off those rules altogether because I still want to see if they fail on my code.

Thanks for your help

Ignore different non-custom types in ===

I want to add this library to existing project but when I run it I get tons of warnings related to === comparing non-custom types like string and int and so on. I would like to be able to disable this type of checking. This basically is a feature request.

Add an option to disable the warning about using the plugin in production

I am getting a lot of warnings in the build process:

WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!
WARNING: you are using Runtyper in production build!

I want to use to runtyper in production.

A simple boolean configuration to indicate that I am knowingly using babel-plugin-runtyper in production would be useful.

Make it work with Babel 7

I have tried it on a project that uses Babel 7 and got bunch of errors, that I assume are due to the Babel 7 version:

{ Error: Cannot find module 'babel-types'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
    at Function.Module._load (internal/modules/cjs/loader.js:497:25)
    at Module.require (internal/modules/cjs/loader.js:626:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (/Users/gajus/Documents/dev/applaudience/cinema-data-scraper-sources/node_modules/babel-plugin-runtyper/src/binary/equal.js:3:11)
    at Module._compile (internal/modules/cjs/loader.js:678:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
    at Module.load (internal/modules/cjs/loader.js:589:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
    at Function.Module._load (internal/modules/cjs/loader.js:520:3) code: 'MODULE_NOT_FOUND' }
[..]

The errors are about missing dependencies (babel-types and babel-template).

Installing the missing dependencies makes the plugin work.

I am assuming those two dependencies could be moved from devDependencies to main dependencies and that could fix the issue (depending on how these dependencies are resolved).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.