gcanti / babel-plugin-tcomb

Babel plugin for static and runtime type checking using Flow and tcomb
MIT License
482 stars 22 forks source link

Adding type safety, gradually. Part I #48

Open gcanti opened 8 years ago

gcanti commented 8 years ago

Goal

The goal of this series of posts is to show how you can add type safety, both statically and at runtime, to your untyped codebase gradually and with a gentle migration path.

Static and runtime type checking are complementary and you can get benefits from both.

Tools

I will use the following tools:

Runtime type checking (tcomb)

Static type checking (Flow)

babel-plugin-tcomb is Flow compatible, this means that you can run them side by side, statically checking your code with Flow and let tcomb catching the remaining bugs at runtime.

Gentle migration path

You can add type safety to your untyped codebase gradually:

First, install via npm:

npm install --save tcomb
npm install --save-dev babel-plugin-tcomb

Then, in your babel configuration (usually in your .babelrc file), add (at least) the following plugins:

{
  "plugins" : [
    "syntax-flow",
    "tcomb",
    "transform-flow-strip-types"
  ]
}

If you are using the react preset, the babel-plugin-syntax-flow and babel-plugin-transform-flow-strip-types plugins are already included:

{
  "presets": ["react", "es2015"],
  "passPerPreset": true, // <= important!
  "plugins" : [
    "tcomb"
  ]
}

You can download Flow from here.

Get started

Say you have this untyped function:

function sum(a, b) {
  return a + b;
}

Adding type annotations is easy, just add a colon and a type after each parameter:

// means "both `a` and `b` must be numbers"
function sum(a: number, b: number) {
  return a + b;
}

For a quick reference on type annotations, start here.

Type annotations are not valid JavaScript, but they will be stripped out by babel-plugin-transform-flow-strip-types so your code will run as before.

Now let's introduce intentionally a bug:

function sum(a: number, b: number) {
  return a + b;
}

sum(1, 2);   // => ok
sum(1, 'a'); // => throws Uncaught TypeError: [tcomb] Invalid value "a" supplied to b: Number
screen shot 2016-06-23 at 11 09 50

Note that you can inspect the stack in order to find where the error was originated. The power of Chrome Dev Tools (or equivalent) are at your disposal.

Runnning Flow

In order to run Flow, just add a .flowconfig file to your project and a comment:

// @flow

at the beginning of the file. Then run flow from you command line. Here's the output:

$> flow
src/index.js:7
  7: sum(1, 'a'); // => throws Uncaught TypeError: [tcomb] Invalid value "a" supplied to b: Number
     ^^^^^^^^^^^ function call
  7: sum(1, 'a'); // => throws Uncaught TypeError: [tcomb] Invalid value "a" supplied to b: Number
            ^^^ string. This type is incompatible with
  2: function sum(a: number, b: number) {
                                ^^^^^^ number

Types

You are not limited to primitive types, this is a annotated function which works on every object that owns a name and a surname property:

function getFullName(x: { name: string, surname: string }) {
  return x.name + ' ' + x.surname;
}

getFullName({ name: 'Giulio' }); // => throws Uncaught TypeError: [tcomb] Invalid value undefined supplied to x: {name: String, surname: String}/surname: String

All the Flow type annotations are supported.

Immutability

Immutability is enforced by tcomb at runtime. The values passed "through" a type annotation will be immutables:

function getFullName(x: { name: string, surname: string }) {
  return x.name + ' ' + x.surname;
}

var person = { name: 'Giulio', surname: 'Canti' };
getFullName(person);
person.name = 1; // throws TypeError: Cannot assign to read only property 'name' of object '#<Object>'

Next post

In the next post I'll talk about how to tighten up your types with the help of refinements.

Note. If you are interested in the next posts, watch this repo, I'll open a new issue for each of them when they are ready.

ctrlplusb commented 8 years ago

Dude! Not sure how to say this, but I love you. It's been awesome getting this flow experience into my projects. I've installed the ide-flow atom plugin and I get autocomplete and type information displayed in my IDE now! No words for how sick this is.

You are a thorough legend.

volkanunsal commented 8 years ago

@ctrlplusb Welcome to @gcanti fan club. You are not the only one! I want to buy this man a beer. 🍺

gcanti commented 8 years ago

I'm pleased to announce the next post on refinements