• Stars
    star
    116
  • Rank 303,894 (Top 6 %)
  • Language
    JavaScript
  • Created over 7 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

⚡️ Runtime type-checker for JavaScript

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

More Repositories

1

x-editable

In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
JavaScript
6,477
star
2

react-native-extended-stylesheet

Extended StyleSheets for React Native
JavaScript
2,926
star
3

github-trending-repos

Track GitHub trending repositories in your favorite programming language by native GitHub notifications!
HTML
2,678
star
4

angular-xeditable

Edit in place for AngularJS
HTML
1,913
star
5

awesome-smart-tv

⚡A curated list of awesome resources for building Smart TV apps
1,072
star
6

checklist-model

AngularJS directive for list of checkboxes
HTML
1,051
star
7

websocket-as-promised

A Promise-based API for WebSockets
JavaScript
595
star
8

bootstrap-editable

This plugin no longer supported! Please use x-editable instead!
JavaScript
558
star
9

await-timeout

A Promise-based API for setTimeout / clearTimeout
JavaScript
428
star
10

playwright-bdd

BDD testing with Playwright runner
TypeScript
302
star
11

combodate

Dropdown date and time picker
JavaScript
208
star
12

autotester

Chrome extension that allows to develop and run automation tests right in browser
JavaScript
170
star
13

clockface

Clockface timepicker for Twitter Bootstrap
CSS
168
star
14

awesome-browser-extensions-and-apps

⚡A curated list of awesome resources for building browser extensions and apps
126
star
15

x-editable-yii

Yii extension for creating editable elements
JavaScript
112
star
16

docker-tizen-webos-sdk

Docker image with Samsung Tizen CLI and LG webOS CLI
Dockerfile
94
star
17

tinkoff-invest-api

Node.js SDK для работы с Tinkoff Invest API
HTML
44
star
18

bro-fs

Promise-based HTML5 Filesystem API similar to Node.js fs module
JavaScript
43
star
19

yii-bootstrap-editable

Yii extension for Bootstrap-editable plugin
JavaScript
31
star
20

alice-renderer

Node.js библиотека для формирования ответов в навыках Яндекс Алисы.
JavaScript
30
star
21

playwright-bdd-example

Example project that uses playwright-bdd to run BDD tests
TypeScript
30
star
22

playwright-magic-steps

Auto-transform JavaScript comments into Playwright steps
TypeScript
29
star
23

alice-workshop

Воркшоп по разработке навыка для Алисы на Node.js
JavaScript
28
star
24

js-testrunners-bench

JavaScript test-runners benchmark
JavaScript
27
star
25

groupgridview

Yii extension to group data in your grid
PHP
24
star
26

lazy-model

AngularJS directive that works like `ng-model` but accept changes only when form is submitted (otherwise changes are cancelled)
JavaScript
21
star
27

tinkoff-robot

Пример торгового робота для Tinkoff Invest API (Node.js)
TypeScript
21
star
28

chnl

JavaScript event channels compatible with Chrome extensions API
JavaScript
19
star
29

docker-stack-wait-deploy

A script waiting for docker stack deploy command to complete.
Shell
18
star
30

playwright-network-cache

Cache and mock network requests in Playwright
TypeScript
17
star
31

alice-tester

Библиотека для автоматического тестирования навыков Алисы на Node.js.
JavaScript
14
star
32

promise-controller

Advanced control of JavaScript promises
JavaScript
13
star
33

controlled-promise

Advanced control of JavaScript promises
JavaScript
13
star
34

mocha-es6-modules

Running Mocha tests in the browser with ES6 Modules support
JavaScript
12
star
35

yandex-cloud-deploy-fn

CLI для деплоя функций в Yandex Cloud на Node.js
TypeScript
11
star
36

tinkoff-local-broker

Локальный сервер для тестирования торговых роботов на Tinkoff Invest API
TypeScript
9
star
37

alice-cloud-proxy

Готовая облачная функция для развертывания своего прокси-навыка для Алисы
JavaScript
9
star
38

throw-utils

Helpers for error throwing
TypeScript
5
star
39

wait-for-cmd

A pure shell script waiting for provided command to exit with zero code
Shell
5
star
40

selgridview

Yii extension to keep selected rows in CGridView when sorting and pagination
JavaScript
5
star
41

alice-protocol

JSON схемы запросов и ответов в навыках Алисы
JavaScript
5
star
42

alice-skill-starter

Быстрый старт навыка для Алисы на Node.js
JavaScript
5
star
43

ydb-sdk-lite

Lightweight implementation of Yandex Database SDK for Node.js
JavaScript
4
star
44

flat-options

One-level options with default values and validation
JavaScript
4
star
45

yandex-cloud-fn

Хелперы для функций в Yandex Cloud (Node.js)
TypeScript
4
star
46

alice-testing-example

Пример функционального тестирования навыка для Яндекс Алисы на Node.js.
JavaScript
4
star
47

page-object

A Page Object pattern implementation library for JavaScript
JavaScript
4
star
48

yc-serverless-live-debug-original

Live debug of Yandex cloud functions with local code on Node.js
TypeScript
4
star
49

eslint-plugin-visual-complexity

Enforce a visual complexity of the code
JavaScript
4
star
50

npxd

Run NPX commands inside Docker container
Shell
3
star
51

fetchers

Semantic RESTful Fetch API wrappers
JavaScript
3
star
52

early-errors

A tiny script to catch webpage errors earlier.
JavaScript
3
star
53

json-micro-schema

Minimal JSON schema validation format
JavaScript
3
star
54

loggee

Zero-dependency JavaScript logger with namespaces
JavaScript
3
star
55

selenium-fileserver

Public website serving Selenium self-test static pages
JavaScript
2
star
56

alice-types

Тайпинги для протокола Алисы.
TypeScript
2
star
57

marusya-types

Тайпинги для протокола Маруси.
TypeScript
2
star
58

micro-schema

JavaScript implementation of json-micro-schema validation format
JavaScript
2
star
59

sheeva

Concurrent Automation Test Runner
JavaScript
2
star
60

subpath-imports-typescript

Example of TypeScript project with Subpath Imports
TypeScript
2
star
61

uni-skill

Универсальный интерфейс для разработки навыков голосовых ассистентов.
TypeScript
2
star
62

alice-asset-manager

Node.js API для загрузки изображений и звуков в навык Алисы.
JavaScript
2
star
63

pendings

[DEPRECATED] Better control of promises
JavaScript
2
star
64

playwright-webhook-reporter

Universal Playwright reporter to send test results to any webhook
JavaScript
1
star
65

react-server-actions-jsx

Example Next.js app with server actions returning JSX
TypeScript
1
star
66

skill-afisha-moscow

TypeScript
1
star
67

qrlink

HTML
1
star
68

promised-map

A map of promises that can be resolved or rejected by key
TypeScript
1
star
69

json-paths

Collect different paths of JSON data.
TypeScript
1
star
70

yandex-cloud-fn-internals

Roff
1
star
71

retry

Retry async function with exponential delay, timeouts and abort signals
TypeScript
1
star
72

alice-dev

Инструменты разработчика для навыков Алисы
JavaScript
1
star
73

skill-dev-proxy

Навык для Алисы, позволяющий отлаживать другие навыки прямо на устройстве
TypeScript
1
star
74

logger

Pure typescript logger with levels and prefix support
TypeScript
1
star
75

yandex-cloud-lite

Минимальный Node.js клиент для доступа к API сервисов Yandex Cloud по GRPC
JavaScript
1
star