├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── build ├── builtins │ ├── index.js │ └── index.js.map ├── index.js └── index.js.map ├── docs └── future.md ├── package.json ├── source ├── builtins │ └── index.js └── index.js └── test ├── builtins └── index.js └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | template 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "binaryLiterals": true, 13 | "blockBindings": true, 14 | "classes": false, 15 | "defaultParams": true, 16 | "destructuring": true, 17 | "forOf": true, 18 | "generators": true, 19 | "modules": true, 20 | "objectLiteralComputedProperties": true, 21 | "objectLiteralDuplicateProperties": true, 22 | "objectLiteralShorthandMethods": true, 23 | "objectLiteralShorthandProperties": true, 24 | "octalLiterals": true, 25 | "regexUFlag": true, 26 | "regexYFlag": true, 27 | "spread": true, 28 | "superInFunctions": false, 29 | "templateStrings": true, 30 | "unicodeCodePointEscapes": true, 31 | "globalReturn": true, 32 | "jsx": true 33 | }, 34 | 35 | "rules": { 36 | "block-scoped-var": [0], 37 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 38 | "camelcase": [0], 39 | "comma-dangle": [2, "never"], 40 | "comma-spacing": [2], 41 | "comma-style": [2, "last"], 42 | "complexity": [0, 11], 43 | "consistent-return": [2], 44 | "consistent-this": [0, "that"], 45 | "curly": [2, "multi-line"], 46 | "default-case": [2], 47 | "dot-notation": [2, { "allowKeywords": true }], 48 | "eol-last": [2], 49 | "eqeqeq": [2], 50 | "func-names": [0], 51 | "func-style": [0, "declaration"], 52 | "generator-star-spacing": [2, "after"], 53 | "guard-for-in": [0], 54 | "handle-callback-err": [0], 55 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 56 | "quotes": [2, "single", "avoid-escape"], 57 | "max-depth": [0, 4], 58 | "max-len": [0, 80, 4], 59 | "max-nested-callbacks": [0, 2], 60 | "max-params": [0, 3], 61 | "max-statements": [0, 10], 62 | "new-parens": [2], 63 | "new-cap": [0], 64 | "newline-after-var": [0], 65 | "no-alert": [2], 66 | "no-array-constructor": [2], 67 | "no-bitwise": [0], 68 | "no-caller": [2], 69 | "no-catch-shadow": [2], 70 | "no-cond-assign": [2], 71 | "no-console": [0], 72 | "no-constant-condition": [1], 73 | "no-continue": [2], 74 | "no-control-regex": [2], 75 | "no-debugger": [2], 76 | "no-delete-var": [2], 77 | "no-div-regex": [0], 78 | "no-dupe-args": [2], 79 | "no-dupe-keys": [2], 80 | "no-duplicate-case": [2], 81 | "no-else-return": [0], 82 | "no-empty": [2], 83 | "no-empty-character-class": [2], 84 | "no-empty-label": [2], 85 | "no-eq-null": [0], 86 | "no-eval": [2], 87 | "no-ex-assign": [2], 88 | "no-extend-native": [1], 89 | "no-extra-bind": [2], 90 | "no-extra-boolean-cast": [2], 91 | "no-extra-semi": [1], 92 | "no-fallthrough": [2], 93 | "no-floating-decimal": [2], 94 | "no-func-assign": [2], 95 | "no-implied-eval": [2], 96 | "no-inline-comments": [0], 97 | "no-inner-declarations": [2, "functions"], 98 | "no-invalid-regexp": [2], 99 | "no-irregular-whitespace": [2], 100 | "no-iterator": [2], 101 | "no-label-var": [2], 102 | "no-labels": [2], 103 | "no-lone-blocks": [2], 104 | "no-lonely-if": [2], 105 | "no-loop-func": [2], 106 | "no-mixed-requires": [0, false], 107 | "no-mixed-spaces-and-tabs": [2, false], 108 | "no-multi-spaces": [2], 109 | "no-multi-str": [2], 110 | "no-multiple-empty-lines": [2, { "max": 2 }], 111 | "no-native-reassign": [1], 112 | "no-negated-in-lhs": [2], 113 | "no-nested-ternary": [0], 114 | "no-new": [2], 115 | "no-new-func": [2], 116 | "no-new-object": [2], 117 | "no-new-require": [0], 118 | "no-new-wrappers": [2], 119 | "no-obj-calls": [2], 120 | "no-octal": [2], 121 | "no-octal-escape": [2], 122 | "no-param-reassign": [2], 123 | "no-path-concat": [0], 124 | "no-plusplus": [0], 125 | "no-process-env": [0], 126 | "no-process-exit": [2], 127 | "no-proto": [2], 128 | "no-redeclare": [2], 129 | "no-regex-spaces": [2], 130 | "no-reserved-keys": [0], 131 | "no-restricted-modules": [0], 132 | "no-return-assign": [2], 133 | "no-script-url": [2], 134 | "no-self-compare": [0], 135 | "no-sequences": [2], 136 | "no-shadow": [2], 137 | "no-shadow-restricted-names": [2], 138 | "no-spaced-func": [2], 139 | "no-sparse-arrays": [2], 140 | "no-sync": [0], 141 | "no-ternary": [0], 142 | "no-throw-literal": [2], 143 | "no-trailing-spaces": [2], 144 | "no-undef": [2], 145 | "no-undef-init": [2], 146 | "no-undefined": [0], 147 | "no-underscore-dangle": [2], 148 | "no-unreachable": [2], 149 | "no-unused-expressions": [2], 150 | "no-unused-vars": [1, { "vars": "all", "args": "after-used" }], 151 | "no-use-before-define": [2], 152 | "no-void": [0], 153 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 154 | "no-with": [2], 155 | "no-extra-parens": [0], 156 | "one-var": [0], 157 | "operator-assignment": [0, "always"], 158 | "operator-linebreak": [2, "after"], 159 | "padded-blocks": [0], 160 | "quote-props": [0], 161 | "radix": [0], 162 | "semi": [2], 163 | "semi-spacing": [2, { "before": false, "after": true }], 164 | "sort-vars": [0], 165 | "space-after-keywords": [2, "always"], 166 | "space-before-function-paren": [2, { "anonymous": "always", "named": "always" }], 167 | "space-before-blocks": [0, "always"], 168 | "space-in-brackets": [0, "never", { 169 | "singleValue": true, 170 | "arraysInArrays": false, 171 | "arraysInObjects": false, 172 | "objectsInArrays": true, 173 | "objectsInObjects": true, 174 | "propertyName": false 175 | }], 176 | "space-in-parens": [2, "never"], 177 | "space-infix-ops": [2], 178 | "space-return-throw-case": [2], 179 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 180 | "spaced-line-comment": [0, "always"], 181 | "strict": [2, "never"], 182 | "use-isnan": [2], 183 | "valid-jsdoc": [0], 184 | "valid-typeof": [2], 185 | "vars-on-top": [0], 186 | "wrap-iife": [2], 187 | "wrap-regex": [2], 188 | "yoda": [2, "never", { "exceptRange": true }] 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Eric Elliott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rtype 2 | 3 | [![Join the chat at https://gitter.im/ericelliott/rtype](https://badges.gitter.im/ericelliott/rtype.svg)](https://gitter.im/ericelliott/rtype?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Intuitive structural type notation for JavaScript. 6 | 7 | ```js 8 | (parameterName: Type) => ReturnType 9 | ``` 10 | 11 | 12 | 13 | ## Table of Contents 14 | 15 | - [About Rtype](#about-rtype) 16 | - [What is Rtype?](#what-is-rtype) 17 | - [Status: RFC](#status-rfc) 18 | - [Why?](#why) 19 | - [Why Not Just Use TypeScript?](#why-not-just-use-typescript) 20 | - [Reading Function Signatures](#reading-function-signatures) 21 | - [Optional Parameters](#optional-parameters) 22 | - [Anonymous Parameters](#anonymous-parameters) 23 | - [Type Variables](#type-variables) 24 | - [Reserved Types](#reserved-types) 25 | - [Builtin Types](#builtin-types) 26 | - [The `Any` Type](#the-any-type) 27 | - [The `Void` Type](#the-void-type) 28 | - [The `Predicate` Type](#the-predicate-type) 29 | - [The `Iterable` Type](#the-iterable-type) 30 | - [The `TypedArray` Type](#the-typedarray-type) 31 | - [Literal Types](#literal-types) 32 | - [Tuples](#tuples) 33 | - [Union Types](#union-types) 34 | - [Negated Types](#negated-types) 35 | - [Constructors](#constructors) 36 | - [Accessor Descriptors](#accessor-descriptors) 37 | - [Throwing Functions](#throwing-functions) 38 | - [Dependencies](#dependencies) 39 | - [Interface: User Defined Types](#interface-user-defined-types) 40 | - [Function Interface](#function-interface) 41 | - [`this` Binding](#this-binding) 42 | - [Dynamic Property Keys](#dynamic-property-keys) 43 | - [Predicate Literals](#predicate-literals) 44 | - [Composing Types](#composing-types) 45 | - [Event Emitters](#event-emitters) 46 | - [Comments](#comments) 47 | - [References](#references) 48 | 49 | 50 | 51 | 52 | ## About Rtype 53 | 54 | * Great for simple documentation. 55 | * Compiler-agnostic type notation - for use with ECMAScript standard tools. 56 | * Low learning curve for JavaScript developers. 57 | * Can embed in JS as strings, for example with [rfx](https://github.com/ericelliott/rfx) for easy runtime reflection. 58 | * Standing on the shoulders of giants. Inspired by: ES6, TypeScript, Haskell, Flow, & React 59 | 60 | ### What is Rtype? 61 | 62 | Rtype is a JS-native representation of structural type interfaces with a TypeScript-inspired notation that's great for documentation. 63 | 64 | 65 | ### Status: RFC 66 | 67 | Developer preview. [Please comment](https://github.com/ericelliott/rtype/issues/new). 68 | 69 | Currently in-use for production library API documentation, but breaking changes are expected. 70 | 71 | In the future, libraries may parse rtype strings and return predicate functions for runtime type checking. Static analysis tools are also [possible](https://github.com/ericelliott/rtype/issues/89), but no significant developer tooling is currently available. Feel free to build some! 72 | 73 | 74 | ### Why? 75 | 76 | Perhaps the most important part of API documentation is to quickly grasp the function signatures and data structures required to work with the API. There are existing standards for this stuff, but we think we can improve on them: 77 | 78 | * JSDoc is too verbose, not intuitive, and painful to maintain. 79 | * TypeScript's structural types are very appealing, but opting into TypeScript's JS superset and runtime limitations is not. 80 | 81 | We want a type representation that is very clear to modern JavaScript developers (ES2015+), that could potentially be used at runtime with simple utilities. 82 | 83 | 84 | ### Why Not Just Use TypeScript? 85 | 86 | We want the best of all worlds: 87 | 88 | * An intuitive way to describe interfaces for the purposes of documentation, particularly function signatures. 89 | * Runtime accessible type reflection (even in production) with optional runtime type checks that can be disabled in production (like React.PropTypes). See [rfx](https://github.com/ericelliott/rfx#rfx). 90 | * A way to specify types in standard ES2015+ code. Use any standard JS compiler. See [rfx](https://github.com/ericelliott/rfx#rfx). 91 | * An easy way to generate interface documentation (like JSDoc). 92 | 93 | TypeScript is great for compile-time and IDE features, and you could conceivably generate docs with it, but runtime features are lacking. For example, I want the ability to query function signatures inside the program at runtime, along with the ability to turn runtime type checking on and off. AFAIK, that's not possible with TypeScript (yet - there is experimental runtime support using experimental features of the ESNext `Reflect` API). 94 | 95 | 96 | 97 | ## Reading Function Signatures 98 | 99 | Function types are described by a **function signature**. The function signature tells you each parameter and its type, separated by a colon, and the corresponding return type: 100 | 101 | ```js 102 | (param: Type) => ReturnType 103 | ``` 104 | 105 | To make the signature familiar to readers, we use common JavaScript idioms such as destructuring, defaults, and rest parameters: 106 | 107 | ```js 108 | (...args: [...String]) => Any 109 | ({ count = 0: Number }) => Any 110 | ``` 111 | 112 | If a parameter or property has a default value, most built-in types can be inferred: 113 | 114 | ```js 115 | ({ count = 0 }) => Any 116 | ``` 117 | 118 | If the type is a [union](#union-types) or [`Any`](#the-any-type), it must be specified: 119 | 120 | ```js 121 | ({ collection = []: Array | Object }) => Any 122 | ``` 123 | 124 | Optionally, you may name the return value, similar to named parameters: 125 | 126 | ```js 127 | (param: Type) => name: Type 128 | ``` 129 | 130 | Or even name a signature to reuse it later on: 131 | 132 | ```js 133 | connect(options: Object) => connection: Object 134 | ``` 135 | 136 | ### Optional Parameters 137 | 138 | Optional parameters can be indicated with `?`: 139 | 140 | ```js 141 | (param: Type, optParam?: Type) => ReturnType 142 | ``` 143 | 144 | ### Anonymous Parameters 145 | 146 | Parameter names can be omitted: 147 | 148 | ```js 149 | is(Any) => Boolean 150 | ``` 151 | 152 | In the case of an anonymous [optional parameter](#optional-parameters) the type must be prefixed by `?:`: 153 | 154 | ```js 155 | toggle(String, ?: Boolean) => Boolean 156 | ``` 157 | 158 | In the case of an anonymous rest parameter, simply omit the name: 159 | 160 | ```js 161 | (...: [...Any]) => Array 162 | ``` 163 | 164 | ### Type Variables 165 | 166 | Type variables are types that do not need to be declared in advance. They may represent any type, but a single type variable may only represent one type at a time in the scope of the signature being declared. 167 | 168 | The signature for double is usually thought of like this: 169 | 170 | ```js 171 | double(x: Number) => Number 172 | ``` 173 | 174 | But what if we want it to accept objects as well? 175 | 176 | ```js 177 | const one = { 178 | name: 'One', 179 | valueOf: () => 1 180 | }; 181 | 182 | double(one); // 2 183 | ``` 184 | 185 | In that case, we'll need to change the signature to use a type variable: 186 | 187 | ```js 188 | double(x: n) => Number 189 | ``` 190 | 191 | By convention, type variables are single letters and lowercased in order to visually distinguish them from predefined types. That way the reader doesn't need to scan back through documentation looking for a type declaration where there is no type declaration to be found. 192 | 193 | 194 | ### Reserved Types 195 | 196 | #### Builtin Types 197 | 198 | ```js 199 | Array, Boolean, Function, Number, Object, RegExp, String, Symbol 200 | ArrayBuffer, Date, Error, Map, Promise, Proxy, Set, WeakMap, WeakSet 201 | ``` 202 | 203 | ##### Notes 204 | 205 | - `null` is part of `Any` and is *not* covered by `Object`. If you want to allow `null` with `Object`, you must specify the union explicitly: `Object | null` 206 | - the `Function` builtin type expands to `(...args: [...Any]) => Any` 207 | 208 | #### The `Any` Type 209 | 210 | The special type `Any` means that any type is allowed: 211 | 212 | ```js 213 | (...args: [...Any]) => Array 214 | ``` 215 | 216 | #### The `Void` Type 217 | 218 | The special type `Void` should only be used to indicate that a function returns no meaningful value (i.e., `undefined`). Since `Void` is the default return type, it can be optionally omitted. Nevertheless `Void` return types *should* usually be explicitly annotated to denote function side-effects. 219 | 220 | ```js 221 | set(name: String, value: String) => Void 222 | ``` 223 | 224 | Is equivalent to: 225 | 226 | ```js 227 | set(name: String, value: String) 228 | ``` 229 | 230 | #### The `Predicate` Type 231 | 232 | The special type `Predicate` is a function with the following signature: 233 | 234 | ```js 235 | (...args: [...Any]) => Boolean 236 | ``` 237 | 238 | #### The `Iterable` Type 239 | 240 | Arrays, typed arrays, strings, maps and sets are iterable. Additionally any object that implements the @@iterator method can be iterated. 241 | 242 | ```js 243 | (paramName: Iterable) => Void 244 | ``` 245 | 246 | Is equivalent to 247 | 248 | ```ts 249 | interface Iterator { 250 | next() => { 251 | done: Boolean, 252 | value?: Any 253 | } 254 | } 255 | 256 | interface IterableObject { 257 | [Symbol.iterator]: () => Iterator 258 | } 259 | 260 | (paramName: IterableObject) => Void 261 | ``` 262 | 263 | #### The `TypedArray` Type 264 | 265 | It covers these contructors: `Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float32Array`, `Float64Array`. 266 | 267 | ### Literal Types 268 | 269 | Literals are also accepted as types. 270 | 271 | ```js 272 | signatureName(param1: String, param2: 'value1' | 'value2' | 'value3') => -1 | 0 | 1 273 | ``` 274 | 275 | ### Tuples 276 | 277 | The type of arrays' elements can also be specified: 278 | 279 | ```js 280 | // an array that contains exactly 2 elements 281 | [Number, String] 282 | ``` 283 | 284 | For **∅ or more** and **1 or more** element(s) of the same type you can use the rest operator like so: 285 | 286 | ```js 287 | // 0 or more 288 | [...Number] 289 | 290 | // 1 or more 291 | [Number...] 292 | //which is equivalent to 293 | [Number, ...Number] 294 | ``` 295 | 296 | ### Union Types 297 | 298 | Union types are denoted with the pipe symbol, `|`: 299 | 300 | ```js 301 | (userInput: String | Number) => String | Number 302 | ``` 303 | 304 | ### Negated Types 305 | 306 | It is sometime easier and more informative to delimit a type by defining what it's not. The negation operator lets you exclude by substracting from `Any`. 307 | 308 | ```js 309 | JSON::parse(String, reviver: Function) 310 | => Boolean | Number | String | Object | Array | null, 311 | throws SyntaxError 312 | 313 | // is less concise than 314 | 315 | JSON::parse(String, reviver: Function) 316 | => !Function & !Void & !Symbol, 317 | throws SyntaxError 318 | 319 | // which is equivalent to 320 | 321 | JSON::parse(String, reviver: Function) 322 | => !(Function | Void | Symbol), 323 | throws SyntaxError 324 | ``` 325 | 326 | ### Constructors 327 | 328 | Constructors in JavaScript require the `new` keyword. You can identify a constructor signature using the `new` keyword as if you were demonstrating usage: 329 | 330 | ```js 331 | new User({ username: String }) => UserInstance 332 | ``` 333 | 334 | In JavaScript, a class or constructor is not synonymous with an interface. The class or constructor definition describe the function signature to create the object instances. A separate signature is needed to describe the instances created by the function. For that, use a separate interface with a different name: 335 | 336 | ```js 337 | interface UserInstance { 338 | username: String, 339 | credentials: String 340 | } 341 | ``` 342 | 343 | ### Accessor Descriptors 344 | 345 | An accessor function is defined by prefixing a method with `get` or `set`. 346 | 347 | ```js 348 | new User({ username: String }) => { 349 | username: String, 350 | get name() => String, 351 | set name(newName: String) // return type defaults to Void 352 | } 353 | ``` 354 | 355 | ### Throwing Functions 356 | 357 | To indicate that a function can throw an error you can use the `throws` keyword. 358 | 359 | ```js 360 | (paramName: Type) => Type, throws: TypeError | DOMException 361 | ``` 362 | 363 | For the generic `Error` type, you can optionally omit the throw type: 364 | 365 | ```js 366 | (paramName: Type) => Type, throws 367 | ``` 368 | 369 | Is equivalent to: 370 | 371 | ```js 372 | (paramName: Type) => Type, throws: Error 373 | ``` 374 | 375 | 376 | ### Dependencies 377 | 378 | You can optionally list your functions' dependencies. In the future, add-on tools may automatically scan your functions and list dependencies for you, which could be useful for documentation and to identify polyfill requirements. 379 | 380 | ```js 381 | // one dependency 382 | signatureName() => Type, requires: functionA 383 | 384 | // several dependencies 385 | signatureName() 386 | => Type, 387 | requires: functionA, functionB 388 | ``` 389 | 390 | 391 | ## Interface: User Defined Types 392 | 393 | You can create your own types using the `interface` keyword. 394 | 395 | An interface can spell out the structure of an object: 396 | 397 | ```js 398 | interface UserProfile { 399 | name: String, 400 | avatarUrl?: Url, 401 | about?: String 402 | } 403 | ``` 404 | 405 | Interfaces support builtin literal types: 406 | 407 | ```js 408 | interface UserInstance { 409 | name: /\w+/, 410 | description?: '', 411 | friends?: [], 412 | profile?: {} 413 | } 414 | ``` 415 | 416 | A one-line interface doesn't need brackets: 417 | 418 | ```js 419 | interface Name: /\w+/ 420 | ``` 421 | 422 | ### Function Interface 423 | 424 | A regular function signature is a shorthand for a function interface: 425 | 426 | ```js 427 | user({ name: String, avatarUrl?: Url }) => UserInstance 428 | ``` 429 | 430 | A function interface must have a function signature: 431 | 432 | ```js 433 | interface user { 434 | ({ name: String, avatarUrl?: Url }) => UserInstance 435 | } 436 | ``` 437 | 438 | For polymorphic functions, use multiple function signatures: 439 | 440 | ```js 441 | interface Collection { 442 | (items: [...Array]) => [...Array], 443 | (items: [...Object]) => [...Object] 444 | } 445 | ``` 446 | 447 | If all signatures return/emit/throw/require the same thing, you can consolidate this information in one place: 448 | 449 | ```js 450 | interface Bar { 451 | (String, Object), 452 | (String, Boolean) 453 | } => Void 454 | ``` 455 | 456 | Note that named function signatures in an interface block indicate methods, rather than additional function signatures: 457 | 458 | ```js 459 | interface Collection { 460 | (signatureParam: Any) => Any, // Collection() signature 461 | method1(items: [...Array]) => [...Array], // method 462 | method2(items: [...Object]) => [...Object] // method 463 | } 464 | ``` 465 | 466 | For convenience you can inline overloaded methods directly inside a function interface. 467 | 468 | ```js 469 | interface Foo { 470 | (Type) => Type, 471 | 472 | a(Object) => Void, 473 | a(String, Number) => Void, 474 | 475 | b(Object) => Void, 476 | b(String, Number) => Void 477 | } 478 | ``` 479 | 480 | Here is the equivalent using separate interfaces: 481 | 482 | ```js 483 | interface a { 484 | (Object) => Void, 485 | (String, Number) => Void 486 | } 487 | 488 | interface b { 489 | (Object) => Void, 490 | (String, Number) => Void 491 | } 492 | 493 | interface Foo { 494 | (Type) => Type, 495 | 496 | a, 497 | b 498 | } 499 | ``` 500 | 501 | ### `this` Binding 502 | 503 | Sometimes you want to define the shape of the call-site of a function; the `::` operator lets you do just that, _granted_ that you have declared the newly bound interface. 504 | For convenience let's reuse the previously defined [`IterableObject` interface](#the-iterable-type): 505 | 506 | ```js 507 | // IterableObject::head() => Any, throws: TypeError 508 | const head = function () { 509 | const [first] = this; 510 | return first; 511 | }; 512 | 513 | head.call([1,2,3]); // 1 514 | ``` 515 | 516 | ### Dynamic Property Keys 517 | 518 | Dynamic properties may be labeled and typed. If omitted, the type defaults to `String`. 519 | 520 | ```js 521 | { 522 | [id1]: { 523 | skating: {time: 1000, money: 300}, 524 | 'cooking': {time: 9999, money: 999} 525 | }, 526 | [id2]: { 527 | "jogging": {time: 300, money: 0} 528 | } 529 | // etc... 530 | } 531 | ``` 532 | 533 | The preceding object can be expressed using these interfaces: 534 | 535 | ```ts 536 | interface Expenditure { 537 | time: Number, 538 | money: Number 539 | } 540 | 541 | interface clientHobbies { 542 | [id: Symbol]: { 543 | // The following: 544 | [hobby]: Expenditure 545 | // is equivalent to 546 | // [hobby: String]: Expenditure 547 | } 548 | } 549 | ``` 550 | 551 | ### Predicate Literals 552 | 553 | Interfaces may use predicate literals, terminated by a semicolon: 554 | 555 | ```js 556 | interface Integer (number) => number === parseInt(number, 10); 557 | ``` 558 | 559 | You can combine predicate literals with interface blocks. Semicolon disambiguates: 560 | 561 | ```js 562 | interface EnhancedInteger (number) => number === parseInt(number, 10); { 563 | isDivisibleBy3() => Boolean, 564 | double() => Number 565 | } 566 | ``` 567 | 568 | Multi-line example: 569 | 570 | ```js 571 | interface EnhancedInteger (number) => { 572 | return number === parseInt(number, 10); 573 | }; { 574 | isDivisibleBy3() => Boolean, 575 | double() => Number 576 | } 577 | ``` 578 | 579 | ## Composing Types 580 | 581 | Whenever you want to compose an interface out of several others, use the spread operator for that: 582 | 583 | ```js 584 | interface Person { 585 | name: Name, 586 | birthDate: Number 587 | } 588 | 589 | interface User { 590 | username: String, 591 | description?: String, 592 | kudos = 0: Number 593 | } 594 | 595 | interface HumanUser { 596 | ...Person, 597 | ...User, 598 | avatarUrl: String 599 | } 600 | ``` 601 | 602 | You can also use the spread inside object type literals: 603 | 604 | ```js 605 | interface Company { 606 | name: Name, 607 | owner: { ...Person, shareStake: Number } 608 | } 609 | ``` 610 | 611 | In case of a name conflict, properties with same names are merged. It means all prerequisites must be satisfied. It’s fine to make types more specific through type literals: 612 | 613 | ```js 614 | interface Creature { 615 | name: String, 616 | character: String, 617 | strength: (number) => (number >= 0 && number <= 100) 618 | } 619 | 620 | interface Human { 621 | ...Creature, 622 | name: /^(.* )?[A-Z][a-z]+$/, 623 | character: 'friendly' | 'grumpy' 624 | } 625 | ``` 626 | 627 | To make sure we can run a static type check for you, we don’t allow merging two different literals. So this would result in a compile error: 628 | 629 | ```js 630 | // Invalid! 631 | interface Professor { 632 | ...Human, 633 | name: /^prof\. \w+$/ 634 | } 635 | ``` 636 | 637 | Obviously, merging incompatible interfaces is also invalid: 638 | 639 | ```js 640 | // Invalid! 641 | interface Bot { 642 | ...Creature, 643 | name: Number 644 | } 645 | ``` 646 | 647 | ## Event Emitters 648 | 649 | When composing an observable interface, you can use the `emits` keyword to describe the events it emits: 650 | 651 | ```js 652 | interface Channel { 653 | ...EventEmitter 654 | } emits: { 655 | 'messageAdded': (body: String, authorId: Number), 656 | 'memberJoined': (id: Number, { name: String, email: String }) 657 | } 658 | 659 | // this is equivalent 660 | 661 | interface Channel { 662 | ...EventEmitter 663 | } emits: { 664 | messageAdded(body: String, authorId: Number), 665 | memberJoined(id: Number, { name: String, email: String }) 666 | } 667 | ``` 668 | 669 | ## Comments 670 | 671 | Standard JS comment syntax applies, e.g.: 672 | 673 | ```js 674 | // A single-line comment, can appear at the end of a line. 675 | 676 | /* 677 | A multi-line comment. 678 | Can span many lines. 679 | */ 680 | ``` 681 | 682 | 683 | ## References 684 | 685 | Somewhat related ideas and inspiration sources. 686 | 687 | * [TypeScript](http://www.typescriptlang.org/) 688 | * [Flow](http://flowtype.org/) 689 | * [Typed Objects](http://wiki.ecmascript.org/doku.php?id=harmony:typed_objects) 690 | * [jsig](https://github.com/jsigbiz/spec) 691 | -------------------------------------------------------------------------------- /build/builtins/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.isVoid = exports.isString = exports.isPredicate = exports.isObject = exports.isNumber = exports.isFunction = exports.isBoolean = exports.isArray = exports.isAny = undefined; 7 | 8 | var _is = require('is'); 9 | 10 | var _is2 = _interopRequireDefault(_is); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | var isAny = exports.isAny = function isAny() { 15 | return true; 16 | }; 17 | 18 | var isArray = exports.isArray = function isArray(x) { 19 | return _is2.default.array(x); 20 | }; 21 | 22 | var isBoolean = exports.isBoolean = function isBoolean(x) { 23 | return _is2.default.bool(x); 24 | }; 25 | 26 | var isFunction = exports.isFunction = function isFunction(x) { 27 | return _is2.default.fn(x); 28 | }; 29 | 30 | var isNumber = exports.isNumber = function isNumber(x) { 31 | return _is2.default.number(x); 32 | }; 33 | 34 | var isObject = exports.isObject = function isObject(x) { 35 | return _is2.default.object(x); 36 | }; 37 | 38 | var isPredicate = exports.isPredicate = function isPredicate(x) { 39 | if (!_is2.default.fn(x)) { 40 | return false; 41 | } 42 | return _is2.default.bool(x()); 43 | }; 44 | 45 | var isString = exports.isString = function isString(x) { 46 | return _is2.default.string(x); 47 | }; 48 | 49 | var isVoid = exports.isVoid = function isVoid(x) { 50 | return _is2.default.undef(x); 51 | }; 52 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /build/builtins/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../source/builtins/index.js"],"names":[],"mappings":";;;;;;;;;;;;;AAEO,IAAM,KAAK,WAAL,KAAK,GAAG,SAAR,KAAK;SAAS,IAAI;CAAA,CAAC;;AAEzB,IAAM,OAAO,WAAP,OAAO,GAAG,SAAV,OAAO,CAAI,CAAC;SAAK,aAAG,KAAK,CAAC,CAAC,CAAC;CAAA,CAAC;;AAEnC,IAAM,SAAS,WAAT,SAAS,GAAG,SAAZ,SAAS,CAAI,CAAC;SAAK,aAAG,IAAI,CAAC,CAAC,CAAC;CAAA,CAAC;;AAEpC,IAAM,UAAU,WAAV,UAAU,GAAG,SAAb,UAAU,CAAI,CAAC;SAAK,aAAG,EAAE,CAAC,CAAC,CAAC;CAAA,CAAC;;AAEnC,IAAM,QAAQ,WAAR,QAAQ,GAAG,SAAX,QAAQ,CAAI,CAAC;SAAK,aAAG,MAAM,CAAC,CAAC,CAAC;CAAA,CAAC;;AAErC,IAAM,QAAQ,WAAR,QAAQ,GAAG,SAAX,QAAQ,CAAI,CAAC;SAAK,aAAG,MAAM,CAAC,CAAC,CAAC;CAAA,CAAC;;AAErC,IAAM,WAAW,WAAX,WAAW,GAAG,SAAd,WAAW,CAAI,CAAC,EAAK;AAChC,MAAI,CAAC,aAAG,EAAE,CAAC,CAAC,CAAC,EAAE;AACb,WAAO,KAAK,CAAC;GACd;AACD,SAAO,aAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;CACrB,CAAC;;AAEK,IAAM,QAAQ,WAAR,QAAQ,GAAG,SAAX,QAAQ,CAAI,CAAC;SAAK,aAAG,MAAM,CAAC,CAAC,CAAC;CAAA,CAAC;;AAErC,IAAM,MAAM,WAAN,MAAM,GAAG,SAAT,MAAM,CAAI,CAAC;SAAK,aAAG,KAAK,CAAC,CAAC,CAAC;CAAA,CAAC","file":"index.js","sourcesContent":["import is from 'is';\n\nexport const isAny = () => true;\n\nexport const isArray = (x) => is.array(x);\n\nexport const isBoolean = (x) => is.bool(x);\n\nexport const isFunction = (x) => is.fn(x);\n\nexport const isNumber = (x) => is.number(x);\n\nexport const isObject = (x) => is.object(x);\n\nexport const isPredicate = (x) => {\n if (!is.fn(x)) {\n return false;\n }\n return is.bool(x());\n};\n\nexport const isString = (x) => is.string(x);\n\nexport const isVoid = (x) => is.undef(x);\n"]} -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _index = require('./builtins/index'); 8 | 9 | var _loop = function _loop(_key2) { 10 | if (_key2 === "default") return 'continue'; 11 | Object.defineProperty(exports, _key2, { 12 | enumerable: true, 13 | get: function get() { 14 | return _index[_key2]; 15 | } 16 | }); 17 | }; 18 | 19 | for (var _key2 in _index) { 20 | var _ret = _loop(_key2); 21 | 22 | if (_ret === 'continue') continue; 23 | } 24 | 25 | var rtype = function rtype(type) { 26 | if ((0, _index.isString)(type)) { 27 | console.warn('Warning rtype string parsing is not implemented yet'); 28 | return true; 29 | } 30 | if ((0, _index.isPredicate)(type)) { 31 | return type; 32 | } 33 | throw new TypeError('rtype(type: Predicate | String) => Predicate'); 34 | }; 35 | 36 | exports.default = rtype; 37 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /build/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../source/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAGA,IAAM,KAAK,GAAG,SAAR,KAAK,CAAI,IAAI,EAAK;AACtB,MAAI,WAJgB,QAAQ,EAIf,IAAI,CAAC,EAAE;AAClB,WAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;AACpE,WAAO,IAAI,CAAC;GACb;AACD,MAAI,WARG,WAAW,EAQF,IAAI,CAAC,EAAE;AACrB,WAAO,IAAI,CAAC;GACb;AACD,QAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;CACrE,CAAC;;kBAEa,KAAK","file":"index.js","sourcesContent":["import { isPredicate, isString } from './builtins/index';\nexport * from './builtins/index';\n\nconst rtype = (type) => {\n if (isString(type)) {\n console.warn('Warning rtype string parsing is not implemented yet');\n return true;\n }\n if (isPredicate(type)) {\n return type;\n }\n throw new TypeError('rtype(type: Predicate | String) => Predicate');\n};\n\nexport default rtype;\n"]} -------------------------------------------------------------------------------- /docs/future.md: -------------------------------------------------------------------------------- 1 | # Runtime rtypes 2 | 3 | Rtypes can be checked at runtime: 4 | 5 | ```js 6 | import { rtype, check } from 'rtype'; 7 | 8 | const alpha = rtype(/[a-zA-Z]+/); 9 | 10 | alpha(3); // false 11 | check(alpha, 3); // false 12 | ``` 13 | 14 | Any object which contains a `.test()` method (like regular expressions) can be used as an rtype. 15 | 16 | A function can be used as an rtype, as long as it has the following signature: 17 | 18 | ```js 19 | (any): boolean 20 | ``` 21 | 22 | **Example:** 23 | 24 | ```js 25 | import { rtype, string } from 'rtype'; 26 | import isUrl from './utils/is-url'; 27 | 28 | const url = rtype(isUrl); 29 | 30 | const user = rtype({ 31 | name: string, 32 | avatarUrl?: url, 33 | about?: string 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtype", 3 | "version": "1.0.0", 4 | "description": "Intuitive type notation for JavaScript.", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "lint": "eslint test source", 8 | "clean": "rimraf build && mkdir build", 9 | "prebuild": "npm run clean", 10 | "build": "babel -d build source -s", 11 | "test": "babel-node test/index.js", 12 | "check": "npm run -s lint && npm -s test && npm -s run build", 13 | "toc": "doctoc README.md", 14 | "precommit": "git diff --staged --exit-code README.md || (npm run toc && git add README.md)", 15 | "watch": "watch 'clear && npm run -s lint && npm -s test' source test" 16 | }, 17 | "directories": { 18 | "doc": "docs" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/ericelliott/rtype.git" 23 | }, 24 | "keywords": [ 25 | "type", 26 | "types", 27 | "type", 28 | "annotation", 29 | "annotation" 30 | ], 31 | "author": "Eric Elliott", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/ericelliott/rtype/issues" 35 | }, 36 | "homepage": "https://github.com/ericelliott/rtype#readme", 37 | "devDependencies": { 38 | "babel-cli": "^6.2.0", 39 | "babel-eslint": "^5.0.0-beta4", 40 | "babel-preset-es2015": "^6.1.18", 41 | "doctoc": "0.15.0", 42 | "eslint": "^1.10.2", 43 | "husky": "^0.10.2", 44 | "rimraf": "^2.4.4", 45 | "tape": "^4.2.2", 46 | "watch": "^0.16.0" 47 | }, 48 | "babel": { 49 | "presets": [ 50 | "es2015" 51 | ] 52 | }, 53 | "dependencies": { 54 | "is": "3.1.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /source/builtins/index.js: -------------------------------------------------------------------------------- 1 | import is from 'is'; 2 | 3 | export const isAny = () => true; 4 | 5 | export const isArray = (x) => is.array(x); 6 | 7 | export const isBoolean = (x) => is.bool(x); 8 | 9 | export const isFunction = (x) => is.fn(x); 10 | 11 | export const isNumber = (x) => is.number(x); 12 | 13 | export const isObject = (x) => is.object(x); 14 | 15 | export const isPredicate = (x) => { 16 | if (!is.fn(x)) { 17 | return false; 18 | } 19 | return is.bool(x()); 20 | }; 21 | 22 | export const isString = (x) => is.string(x); 23 | 24 | export const isVoid = (x) => is.undef(x); 25 | -------------------------------------------------------------------------------- /source/index.js: -------------------------------------------------------------------------------- 1 | import { isPredicate, isString } from './builtins/index'; 2 | export * from './builtins/index'; 3 | 4 | const rtype = (type) => { 5 | if (isString(type)) { 6 | console.warn('Warning rtype string parsing is not implemented yet'); 7 | return true; 8 | } 9 | if (isPredicate(type)) { 10 | return type; 11 | } 12 | throw new TypeError('rtype(type: Predicate | String) => Predicate'); 13 | }; 14 | 15 | export default rtype; 16 | -------------------------------------------------------------------------------- /test/builtins/index.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | 3 | import { 4 | isAny, isArray, isBoolean, isFunction, isNumber, isObject, isPredicate, isString, isVoid 5 | } from '../../source'; 6 | 7 | test('isAny', t => { 8 | t.ok(isAny(), 'permissively accepts no arguments'); 9 | t.ok(isAny('abc'), 'accepts any value'); 10 | 11 | t.end(); 12 | }); 13 | 14 | [ 15 | { type: 'Array', fn: isArray, example: ['abc'], nonExample: 1 }, 16 | { type: 'Boolean', fn: isBoolean, example: true, nonExample: 1 }, 17 | { type: 'Function', fn: isFunction, example: () => {}, nonExample: 1 }, 18 | { type: 'Number', fn: isNumber, example: 1, nonExample: true }, 19 | { type: 'Object', fn: isObject, example: {}, nonExample: 1 }, 20 | { type: 'Predicate', fn: isPredicate, example: (x) => !!x, nonExample: () => 1 }, 21 | { type: 'String', fn: isString, example: 'abc', nonExample: 1 } 22 | ].forEach(({ type, fn, example, nonExample }) => { 23 | 24 | test(fn.name, t => { 25 | t.ok(fn(example), `accepts an ${type} value`); 26 | 27 | t.notOk(fn(), 'rejects lack of arguments'); 28 | t.notOk(fn(nonExample), `rejects non-${type} value`); 29 | 30 | t.end(); 31 | }); 32 | 33 | }); 34 | 35 | test('isVoid', t => { 36 | t.ok(isVoid(), 'accepts no arguments'); 37 | t.ok(isVoid(undefined), 'accepts an undefined value'); 38 | 39 | t.notOk(isVoid('abc'), `rejects non-undefined value`); 40 | 41 | t.end(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | 3 | import rtype from '../source'; 4 | 5 | test('rtype(type: Predicate | String) => Predicate', (t) => { 6 | t.equal(typeof rtype, 'function', 'is a function'); 7 | t.ok(rtype('(type: Predicate | String) => Predicate'), 'rtype(String) => true'); 8 | 9 | const myPredicate = () => true; 10 | t.equal(rtype(myPredicate), myPredicate, 'rtype(Predicate) => Predicate'); 11 | 12 | const myFunc = () => {}; 13 | t.throws(() => { 14 | rtype(myFunc); 15 | }, TypeError, 'throws for non-Predicate Function'); 16 | 17 | t.end(); 18 | }); 19 | 20 | import './builtins'; 21 | --------------------------------------------------------------------------------