パッケージの詳細

babel-plugin-tcomb

gcanti3.6kMIT0.4.0

Babel plugin for static and runtime type checking using Flow and tcomb

flow, babel, babel-plugin, tcomb

readme

Babel plugin for static and runtime type checking using Flow and tcomb.

Tools

Flow is a static type checker for JavaScript.

tcomb is a library for Node.js and the browser which allows you to check the types of JavaScript values at runtime with a simple and concise syntax. It's great for Domain Driven Design and for adding safety to your internal code.

Why?

Runtime type checking (tcomb)

  • you don't want or you can't use Flow
  • you want refinement types
  • you want to validate the IO boundary (for example API payloads)
  • you want to enforce immutability
  • you want to leverage the runtime type introspection provided by tcomb's types

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, add type annotations where you think they are most useful, file by file, leveraging the runtime type safety provided by tcomb
  • then, when you feel comfortable, turn on Flow and unleash the power of static type checking
  • third, for even more type safety, define your refinement types and validate the IO boundary

Fork

Here you can find a fork of this plugin that provides the following additional features:

  • Avoid checks on confident assignment
  • Bounded polymorphism partial support
  • let checks
  • Assignment type checking

Setup

First, install via npm.

npm install --save-dev 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"
  ]
}

Note. syntax-flow and transform-flow-strip-types are already included with the React Preset.

Note. Use Babel's env option to only use this plugin in development.

Warning. If you use multiple presets and are experiencing issues, try tweaking the preset order and setting passPerPreset: true. Related issues: #78 #99

Important. tcomb must be requireable

Plugin configuration

skipAsserts?: boolean = false

Removes the asserts and keeps the domain models

warnOnFailure?: boolean = false

Warns (console.warn) about type mismatch instead of throwing an error

globals?: Array<Object>

With this option you can handle global types, like Class or react SyntheticEvent

Example

"plugins" : [
  ["tcomb", {
    globals: [
      // flow
      {
        'Class': true
      }
      // react
      {
        'SyntheticEvent': true,
        ...
      },
      // your custom global types (if any)
      ...
    ]
  }],
]

Definition files

Definition files for tcomb and tcomb-react are temporarily published here.

Caveats

  • tcomb must be requireable
  • type parameters (aka generics) are not handled (Flow's responsibility)

How it works

First, add type annotations.

// index.js

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

sum(1, 'a') // <= typo

Then run Flow (static type checking):

index.js:7
  7: sum(1, 'a')
     ^^^^^^^^^^^ function call
  7: sum(1, 'a')
            ^^^ string. This type is incompatible with
  3: function sum(a: number, b: number) {
                                ^^^^^^ number

or refresh your browser and look at the console (runtime type checking):

Uncaught TypeError: [tcomb] Invalid value "a" supplied to b: Number

Domain models

// index.js

type Person = {
  name: string, // required string
  surname?: string, // optional string
  age: number,
  tags: Array<string>
};

function getFullName(person: Person) {
  return `${person.name} ${person.surname}`
}

getFullName({ surname: 'Canti' })

Flow:

index.js:14
 14: getFullName({
     ^ function call
 10: function getFullName(person: Person) {
                                  ^^^^^^ property `name`. Property not found in
 14: getFullName({
                 ^ object literal

tcomb:

TypeError: [tcomb] Invalid value undefined supplied to person: Person/name: String

Refinements

In order to define refinement types you can use the $Refinement type, providing a predicate identifier:

import type { $Refinement } from 'tcomb'

// define your predicate...
const isInteger = n => n % 1 === 0

// ...and pass it to the suitable intersection type
type Integer = number & $Refinement<typeof isInteger>;

function foo(n: Integer) {
  return n
}

foo(2)   // flow ok, tcomb ok
foo(2.1) // flow ok, tcomb throws [tcomb] Invalid value 2.1 supplied to n: Integer
foo('a') // flow throws, tcomb throws

In order to enable this feature add the tcomb definition file to the [libs] section of your .flowconfig.

Runtime type introspection

Check out the meta object in the tcomb documentation.

import type { $Reify } from 'tcomb'

type Person = { name: string };

const ReifiedPerson = (({}: any): $Reify<Person>)
console.log(ReifiedPerson.meta) // => { kind: 'interface', props: ... }

In order to enable this feature add the tcomb definition file to the [libs] section of your .flowconfig.

Validating (at runtime) the IO boundary using typecasts

type User = { name: string };

export function loadUser(userId: string): Promise<User> {
  return axios.get('...').then(p => (p: User)) // <= type cast
}

Recursive types

Just add a // recursive comment on top:

// recursive
type Path = {
  node: Node,
  parentPath: Path
};

Type-checking Redux

import { createStore } from 'redux'

// types
type State = number;
type ReduxInitAction = { type: '@@redux/INIT' };
type Action = ReduxInitAction
  | { type: 'INCREMENT', delta: number }
  | { type: 'DECREMENT', delta: number };

function reducer(state: State = 0, action: Action): State {
  switch(action.type) {
    case 'INCREMENT' :
      return state + action.delta
    case 'DECREMENT' :
      return state - action.delta
  }
  return state
}

type Store = {
  dispatch: (action: Action) => any;
};

const store: Store = createStore(reducer)

store.dispatch({ type: 'INCREMEN', delta: 1 }) // <= typo

// throws [tcomb] Invalid value { "type": "INCREMEN", "delta": 1 } supplied to action: Action
// Flow throws as well

Type-checking React using tcomb-react

See tcomb-react:

// @flow

import React from 'react'
import ReactDOM from 'react-dom'
import { props } from 'tcomb-react'

type Props = {
  name: string
};

@props(Props)
class Hello extends React.Component<void, Props, void> {
  render() {
    return <div>Hello {this.props.name}</div>
  }
}


ReactDOM.render(<Hello />, document.getElementById('app'))

Flow will throw:

index.js:12
 12: class Hello extends React.Component<void, Props, void> {
                                               ^^^^^ property `name`. Property not found in
 19: ReactDOM.render(<Hello />, document.getElementById('app'))
                     ^^^^^^^^^ props of React element `Hello`

while tcomb-react will warn:

Warning: Failed propType: [tcomb] Invalid prop "name" supplied to Hello, should be a String.

Detected errors (1):

  1. Invalid value undefined supplied to String

Additional babel configuration:

{
  "presets": ["react", "es2015"],
  "passPerPreset": true,
  "plugins" : [
    "tcomb",
    "transform-decorators-legacy"
  ]
}

In order to enable this feature add the tcomb-react definition file to the [libs] section of your .flowconfig. Also you may want to set esproposal.decorators=ignore in the [options] section of your .flowconfig.

Without decorators

// @flow

import React from 'react'
import ReactDOM from 'react-dom'
import { propTypes } from 'tcomb-react'
import type { $Reify } from 'tcomb'

type Props = {
  name: string
};

class Hello extends React.Component<void, Props, void> {
  render() {
    return <div>Hello {this.props.name}</div>
  }
}

Hello.propTypes = propTypes((({}: any): $Reify<Props>))

ReactDOM.render(<Hello />, document.getElementById('app'))

Under the hood

Primitives

type MyString = string;
type MyNumber = number;
type MyBoolean = boolean;
type MyVoid = void;
type MyNull = null;

compiles to

import _t from "tcomb";

const MyString = _t.String;
const MyNumber = _t.Number;
const MyBoolean = _t.Boolean;
const MyVoid = _t.Nil;
const MyNull = _t.Nil;

Consts

const x: number = 1

compiles to

const x = _assert(x, _t.Number, "x");

Note: lets are not supported.

Functions

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

compiles to

import _t from "tcomb";

function sum(a, b) {
  _assert(a, _t.Number, "a");
  _assert(b, _t.Number, "b");

  const ret = function (a, b) {
    return a + b;
  }.call(this, a, b);

  _assert(ret, _t.Number, "return value");
  return ret;
}

where _assert is an helper function injected by babel-plugin-tcomb.

Type aliases

type Person = {
  name: string,
  surname: ?string,
  age: number,
  tags: Array<string>
};

compiles to

import _t from "tcomb";

const Person = _t.interface({
  name: _t.String,
  surname: _t.maybe(_t.String),
  age: _t.Number,
  tags: _t.list(_t.String)
}, "Person");

更新履歴

Changelog

Tags:

  • [New Feature]
  • [Bug Fix]
  • [Breaking Change]
  • [Documentation]
  • [Internal]
  • [Polish]
  • [Experimental]

Note: Gaps between patch versions are faulty/broken releases. Note: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.

v0.4.0

  • Breaking Change
    • upgrade to babel 7, fix #174 (@minedeljkovic)

v0.3.27

  • New Feature
    • add support for $Values (@jeantimex)

v0.3.26

  • Experimental
    • Add support for object type spread, fix #169 (@voldern)

v0.3.25

  • New Feature
    • Add support for EmptyTypeAnnotation, fix #150 (@gcanti)

v0.3.24

  • New Feature
    • add warnOnFailure plugin option, fix #152 (@gcanti)

v0.3.23

  • Bug Fix
    • fix regression with de-structuring and default parameters, fix #148 (@gcanti)

v0.3.22

  • Bug Fix

    • arguments object and arrow functions don't play well together, fix #144 (@gcanti)
  • Bug Fix

    • Assertions on object destructuring, fix #141 (@gcanti)

v0.3.21

  • Bug Fix
    • Assertions on object destructuring, fix #141 (@gcanti)

v0.3.20

  • Internal
    • remove requires from import type declarations, fix #139 (@gcanti)
  • Bug Fix
    • "keyword"-style args with function defaults that declare return types cause runtime errors, fix #136 (@gcanti)

v0.3.19

  • Bug Fix
    • return types on fat-arrow functions lose access to this, fix #134 (@gcanti)

v0.3.18

  • Bug Fix
    • support default type import shorthand (@STRML)

v0.3.17

  • Bug Fix
    • handle destructured "keyword" params with default values and return type, fix #129 (@gcanti)

v0.3.16

  • Bug Fix
    • annotated functions now handle exact types, fix #127 (@gcanti)

v0.3.15

  • New Feature
    • support exact object syntax (@christophehurpeau)

v0.3.14

  • New Feature
    • add support for $Exact magic type, fix #121 (@gcanti)

v0.3.13

  • Bug Fix
    • Immutability not working, fix #119 (@gcanti)

v0.3.12

  • Bug Fix
    • handle 'keyword'-style function arguments, fix #103 (@gcanti)

v0.3.11

  • Experimental
    • handle async / await, fix #95 (@gcanti)

v0.3.10

  • Bug Fix
    • Support complicated object property name, fix #89 (@gcanti)

v0.3.9

  • New Feature
    • add globals option, fix #56 (@gcanti)
  • Bug Fix
    • handle type parameters in casts (@gcanti)

v0.3.8

  • Bug Fix
    • allow mutually recursive types, fix #84 (@gcanti)
    • remove babel warning when defining an exported recursive type, fix #82 (@gcanti)

v0.3.7

  • Bug Fix
    • ignore superTypeParameters when retrieving class type parameters (@gcanti)

v0.3.6

  • Bug Fix
    • handle inner functions using type parameters (@gcanti)

v0.3.5

  • Bug Fix
    • avoid detecting relative paths as absolute and replace local with imported, fix #77 (@gcanti, thanks @minedeljkovic)

v0.3.4

  • New Feature
    • Add support for Variable declarations (const) (thanks @christophehurpeau)
  • Bug Fix
    • add support for ExistentialTypeParam, fix #67 (@gcanti)
    • add support for TypeofTypeAnnotation, fix #63 (@gcanti)

v0.3.3

  • Bug Fix
    • add support for BooleanLiteralTypeAnnotation, fix #54 (@gcanti)

v0.3.2

  • Bug Fix
    • support values in type casts, fix #50 (@gcanti)
  • Internal
    • add $Abstract and $Subtype Flow magic types (@gcanti)

v0.3.1

  • Bug Fix
    • retrieve type parameters from path recursively, fix #46 (@gcanti)
    • add support for super type parameters (@gcanti)

v0.3.0

  • Breaking Change
    • complete refactoring, tcomb ^3.2.2 is now required
    • add support for Flow syntax

v0.2.3

  • Bug Fix
    • broken noop case for default params, fix #31 (@ctrlplusb)
    • Import guarding correctly resets between files (@ctrlplusb)
    • Import guarding now short circuits import searching after the first valid tcomb import instance is resolved. This provides higher efficiency whilst also preventing strange bug cases that could occur (@ctrlplusb)

v0.2.2

  • New Feature
    • support alternative format for default param type annotations, fix #18
    • "require" based imports of tcomb libraries now resolve to a tcombLocalName (@ctrlplusb)
    • Guarding of tcomb imports, ensuring that tcomb is imported within the scope of any functions that have type checking, fix #21 (@ctrlplusb)
    • add support for object structure type annotations, e.g. function foo(x : { bob: t.String, baz: t.Number }), fix #24 (@ctrlplusb)
    • better error messages, fix #25
  • Bug fix
    • Errors were thrown for functions with default'ed params and a type checked return value, fix #19 (@ctrlplusb)
    • Imports of extended tcomb libraries (e.g. "tcomb-react") now correctly resolve to a tcombLocalName (@ctrlplusb)

v0.2.1

  • Bug fix
    • Functions that had a destructured argument as well as a type checked return would fail transpilation, fix #16 (@ctrlplusb)

v0.2.0

v0.1.4 (babel ^5.0.0)

  • New Feature
    • support for default values