1 | # Babel Typecheck
|
2 |
|
3 | This is a [Babel](https://babeljs.io/) plugin for static and runtime type checking using [flow type](http://flowtype.org/) annotations.
|
4 |
|
5 | [![Build Status](https://travis-ci.org/codemix/babel-plugin-typecheck.svg)](https://travis-ci.org/codemix/babel-plugin-typecheck)
|
6 |
|
7 | > Note: Now requires babel 6.1, babel 5 users [see the 2.0 tag](https://github.com/codemix/babel-plugin-typecheck/tree/2.0.0).
|
8 |
|
9 | # What?
|
10 |
|
11 | Turns code like this:
|
12 | ```js
|
13 | function sendMessage (to: User, message: string): boolean {
|
14 | return socket.send(to, message);
|
15 | }
|
16 | ```
|
17 | into code like this:
|
18 | ```js
|
19 | function sendMessage(to, message) {
|
20 | var _socket$send;
|
21 |
|
22 | if (!(to instanceof User)) throw new TypeError("Value of argument 'to' violates contract.");
|
23 | if (typeof message !== "string") throw new TypeError("Value of argument 'message' violates contract.");
|
24 | _socket$send = socket.send(to, message);
|
25 | if (typeof _socket$send !== "boolean") throw new TypeError("Function 'sendMessage' return value violates contract.");
|
26 | return _socket$send;
|
27 | }
|
28 | ```
|
29 |
|
30 | And guards against some silly mistakes, for example the following code will fail to compile with a `SyntaxError`, because the function can return the wrong type.
|
31 |
|
32 | ```js
|
33 | function foo (): boolean {
|
34 | if (Math.random() > 0.5) {
|
35 | return "yes"; // <-- SyntaxError - string is not boolean
|
36 | }
|
37 | else {
|
38 | return false;
|
39 | }
|
40 | }
|
41 |
|
42 | function bar (input: string = 123): string { // <-- SyntaxError: default value is not string
|
43 | return input + "456";
|
44 | }
|
45 | ```
|
46 |
|
47 | # Installation
|
48 |
|
49 | First, install via [npm](https://npmjs.org/package/babel-plugin-typecheck).
|
50 | ```sh
|
51 | npm install --save-dev babel-plugin-typecheck
|
52 | ```
|
53 | Then, in your babel configuration (usually in your `.babelrc` file), add `"typecheck"` to your list of plugins:
|
54 | ```json
|
55 | {
|
56 | "plugins": ["typecheck"]
|
57 | }
|
58 | ```
|
59 |
|
60 | **Important**: This plugin has a dependency on `babel-plugin-syntax-flow` and `babel-plugin-transform-flow-strip-types`.
|
61 | Without `syntax-flow`, babel will be unable to parse the flow annotation syntax.
|
62 | Without `transform-flow-strip-types`, the type annotations will be included in the output which will make it unparsable by JS engines.
|
63 |
|
64 | If you are not already using the `babel-preset-react` plugin, you **must** install those plugins and include them in your babel configuration (usually `.babelrc`). Put them *after* `typecheck` in the list, e.g.
|
65 | ```json
|
66 | {
|
67 | "plugins": ["typecheck", "syntax-flow", "transform-flow-strip-types"]
|
68 | }
|
69 | ```
|
70 | If you *are* using `babel-preset-react` you can ignore this warning.
|
71 |
|
72 | # Examples
|
73 |
|
74 | The basic format is similar to [Flow Type Annotations](http://flowtype.org/docs/type-annotations.html).
|
75 |
|
76 | Here are a few examples of annotations this plugin supports:
|
77 |
|
78 | ```js
|
79 | function foo(
|
80 | aNum: number,
|
81 | anOptionalString: ?string, // will allow null/undefined
|
82 | anObject: Object,
|
83 | aDate: Date,
|
84 | anError: Error,
|
85 | aUnionType: Object|string,
|
86 | aClass: User,
|
87 | aShape: {foo: number, bar: ?string},
|
88 | anArray: Array,
|
89 | arrayOf: string[] | Array<string>,
|
90 | {x, y}: {x: string, y: number}, // destructuring works
|
91 | es6Defaults: number = 42
|
92 | ) : number {
|
93 | return aNum;
|
94 | }
|
95 | ```
|
96 |
|
97 | # Optimization
|
98 |
|
99 | In cases where typecheck can statically verify that the return value is of the correct type, no type checks will be inserted, for instance:
|
100 | ```js
|
101 | function bar (): string|Object {
|
102 | if (Math.random() > 0.5) {
|
103 | return "yes";
|
104 | }
|
105 | else {
|
106 | return {
|
107 | message: "no"
|
108 | };
|
109 | }
|
110 | }
|
111 | ```
|
112 | will produce no type checks at all, because we can trivially tell that the function can only return one of the two permitted types.
|
113 | This is also true for simple cases like:
|
114 | ```js
|
115 | function createUser (): User {
|
116 | return new User(); // <-- no typecheck required
|
117 | }
|
118 | ```
|
119 | This is currently quite limited though, as the plugin can only statically infer the types of literals and very simple expressions, it can't (yet) statically verify e.g. the result of a function call. In those cases a runtime type check is required:
|
120 | ```js
|
121 | function createUser (): User {
|
122 | return User.create(); // <-- produces runtime typecheck
|
123 | }
|
124 | ```
|
125 |
|
126 |
|
127 | ## Changes in 3.0.0
|
128 |
|
129 | ### Supports type aliases:
|
130 | ```js
|
131 | type Foo = string|number;
|
132 |
|
133 | function demo (input: Foo): string {
|
134 | return input + ' world';
|
135 | }
|
136 |
|
137 | demo('hello'); // ok
|
138 | demo(123); // ok
|
139 | demo(["not", "a", "Foo"]); // fails
|
140 | ```
|
141 |
|
142 | ### Better static type inference
|
143 | ```js
|
144 | function demo (input: string): string[] {
|
145 | return makeArray(input); // no return type check required, knows that makeArray is compatible
|
146 | }
|
147 |
|
148 | function makeArray (input: string): string[] {
|
149 | return [input];
|
150 | }
|
151 | ```
|
152 |
|
153 | ### Type propagation
|
154 | ```js
|
155 | function demo (input: string): User {
|
156 | const user = new User({name: input});
|
157 | return user; // No check required, knows that user is the correct type
|
158 | }
|
159 | ```
|
160 |
|
161 | ### Assignment tracking
|
162 | ```js
|
163 | let name: string = "bob";
|
164 |
|
165 | name = "Bob"; // ok
|
166 | name = makeString(); // ok
|
167 | name = 123; // SyntaxError, expected string not number
|
168 |
|
169 | function makeString (): string {
|
170 | return "Sally";
|
171 | }
|
172 | ```
|
173 |
|
174 | ### Type casting
|
175 | ```js
|
176 | let name: string = "bob";
|
177 |
|
178 | name = "Bob";
|
179 | ((name: number) = 123);
|
180 | name = 456;
|
181 | name = "fish"; // SyntaxError, expected number;
|
182 | ```
|
183 |
|
184 | ### Array type parameters
|
185 | ```js
|
186 | function demo (input: string[]): number {
|
187 | return input.length;
|
188 | }
|
189 |
|
190 | demo(["a", "b", "c"]); // ok
|
191 | demo([1, 2, 3]); // TypeError
|
192 | ```
|
193 |
|
194 | ### Shape tracking
|
195 | ```js
|
196 | type User = {
|
197 | name: string;
|
198 | email: string;
|
199 | };
|
200 |
|
201 | function demo (input: User): string {
|
202 | return input.name;
|
203 | }
|
204 |
|
205 | demo({}); // TypeError
|
206 | demo({name: 123, email: "test@test.com"}); // TypeError
|
207 | demo({name: "test", email: "test@test.com"}); // ok
|
208 | ```
|
209 |
|
210 |
|
211 | ## Pragmas
|
212 |
|
213 | Sometimes you might need to disable type checking for a particular file or section of code.
|
214 | To ignore an entire file, add a comment at the top level scope of the file:
|
215 | ```js
|
216 | // typecheck: ignore file
|
217 | export function wrong (input: string = 123): boolean {
|
218 | return input + ' nope';
|
219 | }
|
220 | ```
|
221 |
|
222 | To ignore a particular statement:
|
223 | ```js
|
224 | let foo: string = "hello world";
|
225 | // typecheck: ignore statement
|
226 | foo = 123;
|
227 | ```
|
228 |
|
229 | > Note: Because of how typecheck works, it's not possible to ignore individual lines, only entire statements or files.
|
230 | > So if you ignore e.g. an if statement, the entire body of that statement will be ignored.
|
231 |
|
232 | # License
|
233 |
|
234 | Published by [codemix](http://codemix.com/) under a permissive MIT License, see [LICENSE.md](./LICENSE.md).
|
235 |
|