├── .eslintrc ├── .flowconfig ├── .gitignore ├── .mochadocrc ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── bin ├── coreTypes.js ├── duckTypes.js ├── recursiveTypes.js └── signet.js ├── client-index.js ├── dist ├── signet.js ├── signet.min.js └── signet.min.js.map ├── docs └── api │ ├── assets │ ├── code-collapse.js │ ├── doc-style.css │ ├── github-gist.css │ └── highlight.pack.js │ ├── details │ ├── SignetAPI.html │ ├── SignetMacros.html │ └── SignetTypes.html │ └── index.html ├── grunt ├── concat.json ├── eslint.json ├── mocha-test.js └── uglify.json ├── index.js ├── package-lock.json ├── package.json ├── signet.d.ts └── test ├── macros.test.js ├── signet.test.js ├── timer.js └── types.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 5, 5 | "sourceType": "module" 6 | }, 7 | "rules": { 8 | "no-console": 1 9 | }, 10 | "globals": { 11 | "require": false, 12 | "describe": false, 13 | "it": false, 14 | "beforeEach": false, 15 | "afterEach": false, 16 | "module": false, 17 | "console": false, 18 | "global": false, 19 | "runQuokkaMochaBdd": false, 20 | "Symbol": false, 21 | "Promise": false 22 | } 23 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ./test 3 | ./dist 4 | ./node_modules 5 | 6 | [include] 7 | ./bin/signet.js 8 | ./bin/coreTypes.js 9 | ./bin/duckTypes.js 10 | 11 | [libs] 12 | 13 | [options] 14 | suppress_comment= \\(.\\|\n\\)*\\@flowSuppress -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | example/ -------------------------------------------------------------------------------- /.mochadocrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "Signet", 3 | "files": "./test/*.test.js", 4 | "dest": "./docs/api/" 5 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const concatConfig = require('./grunt/concat.json'); 4 | const uglifyConfig = require('./grunt/uglify.json'); 5 | const mochaTest = require('./grunt/mocha-test'); 6 | const eslintConfig = require('./grunt/eslint'); 7 | 8 | module.exports = function (grunt) { 9 | grunt.initConfig({ 10 | pkg: grunt.file.readJSON('package.json'), 11 | concat: concatConfig, 12 | eslint: eslintConfig, 13 | uglify: uglifyConfig 14 | }); 15 | 16 | grunt.loadNpmTasks('grunt-contrib-concat'); 17 | grunt.loadNpmTasks('grunt-contrib-uglify'); 18 | grunt.loadNpmTasks('grunt-eslint'); 19 | 20 | grunt.registerTask('mocha-test', mochaTest); 21 | 22 | grunt.registerTask('build', ['concat', 'uglify']); 23 | grunt.registerTask('test', ['eslint', 'build', 'mocha-test']); 24 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License 2 | Version 2.0 3 | 1. Definitions 4 | 1.1. “Contributor” 5 | means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 6 | 7 | 1.2. “Contributor Version” 8 | means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution. 9 | 10 | 1.3. “Contribution” 11 | means Covered Software of a particular Contributor. 12 | 13 | 1.4. “Covered Software” 14 | means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 15 | 16 | 1.5. “Incompatible With Secondary Licenses” 17 | means 18 | 19 | that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or 20 | 21 | that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 22 | 23 | 1.6. “Executable Form” 24 | means any form of the work other than Source Code Form. 25 | 26 | 1.7. “Larger Work” 27 | means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 28 | 29 | 1.8. “License” 30 | means this document. 31 | 32 | 1.9. “Licensable” 33 | means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 34 | 35 | 1.10. “Modifications” 36 | means any of the following: 37 | 38 | any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or 39 | 40 | any new file in Source Code Form that contains any Covered Software. 41 | 42 | 1.11. “Patent Claims” of a Contributor 43 | means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 44 | 45 | 1.12. “Secondary License” 46 | means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 47 | 48 | 1.13. “Source Code Form” 49 | means the form of the work preferred for making modifications. 50 | 51 | 1.14. “You” (or “Your”) 52 | means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 53 | 54 | 2. License Grants and Conditions 55 | 2.1. Grants 56 | Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 57 | 58 | under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and 59 | 60 | under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 61 | 62 | 2.2. Effective Date 63 | The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 64 | 65 | 2.3. Limitations on Grant Scope 66 | The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: 67 | 68 | for any code that a Contributor has removed from Covered Software; or 69 | 70 | for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or 71 | 72 | under Patent Claims infringed by Covered Software in the absence of its Contributions. 73 | 74 | This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 75 | 76 | 2.4. Subsequent Licenses 77 | No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 78 | 79 | 2.5. Representation 80 | Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 81 | 82 | 2.6. Fair Use 83 | This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 84 | 85 | 2.7. Conditions 86 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 87 | 88 | 3. Responsibilities 89 | 3.1. Distribution of Source Form 90 | All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form. 91 | 92 | 3.2. Distribution of Executable Form 93 | If You distribute Covered Software in Executable Form then: 94 | 95 | such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and 96 | 97 | You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License. 98 | 99 | 3.3. Distribution of a Larger Work 100 | You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 101 | 102 | 3.4. Notices 103 | You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 104 | 105 | 3.5. Application of Additional Terms 106 | You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 107 | 108 | 4. Inability to Comply Due to Statute or Regulation 109 | If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 110 | 111 | 5. Termination 112 | 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 113 | 114 | 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 115 | 116 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 117 | 118 | 6. Disclaimer of Warranty 119 | Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 120 | 121 | 7. Limitation of Liability 122 | Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 123 | 124 | 8. Litigation 125 | Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims. 126 | 127 | 9. Miscellaneous 128 | This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 129 | 130 | 10. Versions of the License 131 | 10.1. New Versions 132 | Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 133 | 134 | 10.2. Effect of New Versions 135 | You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 136 | 137 | 10.3. Modified Versions 138 | If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 139 | 140 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 141 | If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. 142 | 143 | Exhibit A - Source Code Form License Notice 144 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. 145 | 146 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 147 | 148 | You may add additional accurate notices of copyright ownership. 149 | 150 | Exhibit B - “Incompatible With Secondary Licenses” Notice 151 | This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Signet # 2 | 3 | ## The fast, rich runtime documentation-through-type system for Javascript ## 4 | 5 | At its core, Signet aims to be a first-line-of-defense documentation library 6 | for your code. By attaching and enforcing rich type information to your 7 | functions, you communicate with other developers what your intent is and 8 | how they can use your code. Sometimes that other developer is future you! 9 | 10 | Although Signet is a deep, rich, extensible type system, the most important 11 | first takeaway is Signet is easy to use. Unlike other documentation libraries 12 | which require a lot of time and effort to get familiar with, Signet provides 13 | a familiar, simple means to fully document your behavior up front, like this: 14 | 15 | ``` 16 | const add = signet.enforce( 17 | 'a:number, b:number => sum:number`, 18 | (a, b) => a + b 19 | ); 20 | ``` 21 | 22 | Obviously, this is a trivial example, but it is easy to immediately understand 23 | what our add function requires and what it will do. More importantly, if someone 24 | were to try to use our function incorrectly, they would get a clear message: 25 | 26 | ``` 27 | add('foo', 23); // TypeError: Expected value of type a:number, but got foo of type string 28 | ``` 29 | 30 | Moreover, if this developer wanted to understand what the add function expected, they 31 | could simply request the signature: 32 | 33 | ``` 34 | console.log(add.signature); // a:number, b:number => sum:number 35 | ``` 36 | 37 | All of a sudden, those API endpoints which were left undocumented can be easily 38 | updated to provide parameter and result information without a lot of extra developer 39 | time. This kind of in-code documentation and type checking facilitates tribal 40 | knowledge even if a member of the tribe has long left. 41 | 42 | Finally, Signet won't let your documentation get out of date. Since Signet does real type checking and a review of your function properties against your signature, if you add parameters or change your function, Signet will let you know your documentation is out of date. 43 | 44 | All of this only scratches the surface of what you can do with Signet. You can define your own types, use constructed and algebraic types and even define macros to alter type strings just in time. Beyond that, Signet is 100% ECMAScript 5.1 (Harmony) compliant, so there is no need to transpile anything. As long as your code works, Signet works. 45 | 46 | Remember, code is not just a program to be run, it is a document programmers read. Wouldn't you like your document to tell you more? 47 | 48 | ## Install Signet ## 49 | 50 | Signet is available through NPM: 51 | 52 | `npm i signet --save` 53 | 54 | You can also find it on the NPM site for more information: 55 | 56 | [https://www.npmjs.com/package/signet](https://www.npmjs.com/package/signet) 57 | 58 | ## Library Usage ## 59 | 60 | First it is recommended that you create a types file so the local signet object can be cached for your module: 61 | 62 | ``` 63 | const signet = require('signet')(); 64 | 65 | //my aliased type 66 | signet.alias('foo', 'string'); 67 | 68 | //If you're in node, be sure to export your signet instance! 69 | module.exports = signet; 70 | ``` 71 | 72 | Now, include your types file into your other files and the signet types object will be properly enclosed in your module. Now you're ready to get some type and document work done: 73 | 74 | ``` 75 | const signet = require('./mySignetTypesFile'); 76 | 77 | const range = signet.enforce( 78 | 'start < end :: start:int, end:int, increment:leftBoundedInt<1> => array', 79 | (start, end, increment) => { 80 | let result = []; 81 | 82 | for(let i = start; i <= end; i += increment) { 83 | result.push(i); 84 | } 85 | 86 | return result; 87 | } 88 | ); 89 | ``` 90 | 91 | ## Namespacing Types ## 92 | 93 | It's a common need to namespace types in order to declare types for different contexts as you develop your application. In statically typed languages like C# and Java, this concept is built into the language. 94 | 95 | Javascript doesn't have this concept, but Signet provides a means to work within a namespace in order to segregate type concepts without creating painful names. 96 | 97 | In node, Signet namespaces are simply separate instances of Signet. This means you can do the following: 98 | 99 | ```javascript 100 | const signetFactory = require('signet'); 101 | 102 | const Permissions = signetFactory(); 103 | const Signup = signetFactory(); 104 | 105 | Permissions.defineDuckType('claim', { 106 | id: 'int', 107 | name: 'string' 108 | }); 109 | 110 | Permissions.defineDuckType('user', { 111 | userId: 'int', 112 | claims: 'array' 113 | }); 114 | 115 | // Don't use this crummy email validation, 116 | // it's for demonstration purposes only. 117 | Signup.alias('email', 'formattedString<[^@]+@.*\..*>'); 118 | 119 | Signup.defineDuckType('user', { 120 | name: 'string', 121 | email: 'email', 122 | username: 'string', 123 | password: 'string' 124 | }); 125 | 126 | ``` 127 | 128 | In the client, namspacing works a little differently: 129 | 130 | ```javascript 131 | const Permissions = signet.new(); 132 | const Signup = signet.new(); 133 | 134 | Permissions.defineDuckType('claim', { 135 | id: 'int', 136 | name: 'string' 137 | }); 138 | 139 | Permissions.defineDuckType('user', { 140 | userId: 'int', 141 | claims: 'array' 142 | }); 143 | 144 | // Don't use this crummy email validation, 145 | // it's for demonstration purposes only. 146 | Signup.defineDuckType('email', 'formattedString<[^@]+@.*\..*>'); 147 | 148 | Signup.defineDuckType('user', { 149 | name: 'string', 150 | email: 'email', 151 | username: 'string', 152 | password: 'string' 153 | }); 154 | ``` 155 | 156 | This means you can work with a variety of types without type name collisions, keeping your contexts well bounded and easier to manage! 157 | 158 | ## Basic Operators and Syntactic Characters ## 159 | 160 | - Type names -- All primary type names should adhere to the list of supported types below 161 | - Subtype names -- Subtype names must not contain any reserved characters as listed next 162 | - `<>` -- Angle brackets are for handling type constructors and verify value only when type logic supports it 163 | - `[]` -- Brackets are meant to enclose optional values and should always come in a matched pair 164 | - `=>` -- Function output "fat-arrow" notation used for expressing output from input 165 | - `,` -- Commas are required for separating types on functions 166 | - `:` -- Colons allow for object:instanceof annotation - This is not required or checked 167 | - `;` -- Semicolons allow for multiple values within the angle bracket notation 168 | - `()` -- Optional parentheses to group types, which will be treated as spaces by interpreter 169 | 170 | Example function signatures: 171 | 172 | - Empty argument list: `"() => function"` 173 | - Simple argument list: `"number, string => boolean"` 174 | - Subtyped object: `"object:InstantiableName => string"` 175 | - Typed array: `"array => string"` 176 | - Optional argument: `"array, [number] => number"` 177 | - Curried function: `"number => number => number"` 178 | 179 | ## Primary Types ## 180 | 181 | Signet supports all of the core Javascript types as well as a few others, which allow 182 | the core typesystem to be approachable, clear and easy to relate to for anyone 183 | familiar with Javascript and its built-in dynamic types. 184 | 185 | List of primary types: 186 | 187 | - `*` 188 | - `array` 189 | - `bigint` - `* -> nativeNumber -> bigint` 190 | - `boolean` 191 | - `function` 192 | - `null` 193 | - `number` - `* -> nativeNumber -> bigint` 194 | - `object` 195 | - `string` 196 | - `symbol` 197 | - `undefined` 198 | 199 | ## Extended types ## 200 | 201 | Signet has extended types provided as a separate module. In the node environment, the extended types 202 | are included in the required module, but can be removed by pointing to the signet.js module directly. 203 | In the browser environment, signet.min.js and signet.types.min.js in that order to include the extended types. 204 | 205 | Extended types, and their inheritance chain, are as follows: 206 | 207 | - `arguments` - `* -> variant` 208 | - `bounded` - `* -> bounded` 209 | - `boundedFiniteInt` - `* -> boundedFiniteInt` 210 | - `boundedFiniteNumber` - `* -> boundedFiniteNumber` 211 | - `boundedInt` - `* -> boundedInt` 212 | - `boundedNumber` - `* -> boundedNumber` 213 | - `boundedString` - `* -> boundedString` 214 | - `composite` - `* -> composite` (Type constructor only, evaluates left to right) 215 | - `decimalPrecision>` - `number -> decimalPrecision` 216 | - `decreasing`- `* -> array -> (sequence ->) decreasing` 217 | - `formattedString` - `* -> string -> formattedString` 218 | - `int` - `* -> nativeNumber -> int` 219 | - `increasing`- `* -> array -> (sequence ->) increasing` 220 | - `leftBounded` - `* -> leftBounded` 221 | - `leftBoundedFiniteInt` - `* -> leftBoundedFiniteInt` 222 | - `leftBoundedFiniteNumber` - `* -> leftBoundedFiniteNumber` 223 | - `leftBoundedInt` - `* -> leftBoundedInt` 224 | - `leftBoundedNumber` - `* -> leftBoundedNumber` 225 | - `leftBoundedString` - `* -> leftBoundedString` 226 | - `monotone`- `* -> array -> (sequence ->) monotone` 227 | - `not` - `* -> not` (Type constructor only) 228 | - `regexp` - `* -> object -> regexp` 229 | - `rightBounded` - `* -> number -> rightBounded` 230 | - `rightBoundedFiniteInt` - `* -> rightBoundedFiniteInt` 231 | - `rightBoundedFiniteNumber` - `* -> rightBoundedFiniteNumber` 232 | - `rightBoundedInt` - `* -> rightBoundedInt` 233 | - `rightBoundedNumber` - `* -> rightBoundedInt` 234 | - `rightBoundedString` - `* -> rightBoundedString` 235 | - `sequence` - `* -> array -> sequence` 236 | - `tuple` - `* -> object -> array -> tuple` 237 | - `unorderedProduct` - `* -> object -> array -> unorderedProduct` 238 | - `variant` - `* -> variant` 239 | 240 | ## Macro Types ## 241 | 242 | Signet supports type-level and signature-level macros. There are a small set of built-in macros which are as follows: 243 | 244 | - `()` - type-level macro for `*` 245 | - Example: `()` becomes `*` 246 | - `!*` - type-level macro for `not>` 247 | - Example: `definedType:!*` becomes `definedType:not` 248 | - `^typeName` - type-level macro for `not` 249 | - Example: `notNull:^null` becomes `notNull:not` 250 | - `?typeName` - type-level macro for `variant` 251 | - Example: `maybeTuple:?tuple<*, *, *>` becomes `maybeTuple:variant>` 252 | - `(types => types => ...)` - signature-level macro for `function types => ...>` 253 | - Example: `(string => int => null)` becomes `function int => null>` 254 | 255 | ## Dependent types ## 256 | 257 | Types can be named and dependencies can be declared between two arguments in the same call. Signet currently does not have the means to verify dependent types across function calls. 258 | 259 | Example for a range function might look like the following: 260 | 261 | `start < end :: start:int, end:int, increment:[leftBoundedInt<1>] => array` 262 | 263 | Built in type operations are as follows: 264 | 265 | - number: 266 | - `=` (value equality) 267 | - `!=` (value inequality) 268 | - `<` (A less than B) 269 | - `>` (A greater than B) 270 | - `<=` (A less than or equal to B) 271 | - `>=` (A greater than or equal to B) 272 | - string: 273 | - `=` (value equality) 274 | - `!=` (value inequality) 275 | - `#=` (length equality) 276 | - `#<` (A.length less than B.length) 277 | - `#>` (A.length greater than B.length) 278 | - array 279 | - `#=` (length equality) 280 | - `#<` (A.length less than B.length) 281 | - `#>` (A.length greater than B.length) 282 | - object: 283 | - `=` (property equality) 284 | - `!=`(property inequality) 285 | - `:>` (property superset) 286 | - `:<` (property subset) 287 | - `:=` (property congruence -- same property names, potentially different values) 288 | - `:!=` (property incongruence -- different property names) 289 | - variant: 290 | - `=:` (same type) 291 | - `<:` (subtype) 292 | - `>:` (supertype) 293 | 294 | ### Signet behaviors ### 295 | 296 | Signet can be used two different ways to sign your functions, as a function wrapper or as a decoration of your function. 297 | Below are examples of the two use cases: 298 | 299 | Function wrapper style: 300 | 301 | ``` 302 | const add = signet.sign('number, number => number', 303 | function add (a, b) { 304 | return a + b; 305 | }); 306 | 307 | console.log(add.signature); // number, number => number 308 | ``` 309 | 310 | Function decoration style: 311 | 312 | ``` 313 | signet.sign('number, number => number`, add); 314 | function add (a, b) { 315 | return a + b; 316 | } 317 | ``` 318 | 319 | Example of curried function type annotation: 320 | 321 | ``` 322 | const curriedAdd = signet.sign( 323 | 'number => number => number', 324 | (a) => (b) => a + b 325 | ); 326 | ``` 327 | 328 | Signet signatures are immutable, which means once they are declared, they cannot be tampered with. This adds a guarantee 329 | to the stability of your in-code documentation. Let's take a look: 330 | 331 | ``` 332 | const add = signet.sign( 333 | 'number, number => number', 334 | (a, b) => a + b 335 | ); 336 | 337 | add.signature = 'I am trying to change the signature property'; 338 | console.log(add.signature); // number, number => number 339 | ``` 340 | 341 | Arguments can be enforced directly with `enforceArguments` in places where enforce is inappropriate or not feasible for use: 342 | 343 | ``` 344 | const enforceAddArgs = signet.enforceArguments(['a: number', 'b: number']); 345 | 346 | function verifiedAdd (a, b) { 347 | enforceAddArgs(arguments); 348 | return a + b; 349 | } 350 | 351 | signet.sign('number, number => number', verifiedAdd); 352 | ``` 353 | 354 | Functions can be signed and verified all in one call with the enforce function: 355 | 356 | ``` 357 | const enforcedAdd = signet.enforce( 358 | 'a:number, b:number => sum:number', 359 | (a, b) => a + b 360 | ); 361 | ``` 362 | 363 | Curried functions are also fully enforced all the way down: 364 | 365 | ``` 366 | const curriedAdd = signet.enforce( 367 | 'a:number => b:number => sum:number', 368 | (a) => (b) => a + b 369 | ); 370 | 371 | curriedAdd(1)('foo'); // Throws -- Expected type number, but got string 372 | ``` 373 | 374 | ### Types and subtypes ### 375 | 376 | New types can be added by using the extend function with a key and a predicate function describing the behavior of the data type 377 | 378 | ``` 379 | signet.extend('foo', (value) => value !== 'bar'); 380 | signet.isTypeOf('foo')('baz'); // false 381 | ``` 382 | 383 | Subtypes can be added by using the subtype function. This is particularly useful for defining and using business types or defining restricted types. 384 | 385 | ``` 386 | signet.subtype('number')('int', (value) => Math.floor(value) === value && value !== infinity); 387 | 388 | const enforcedIntAdd = signet.enforce( 389 | 'a:int, b:int => sum:int', 390 | (a, b) => a + b 391 | ); 392 | 393 | enforcedIntAdd(1.2, 5); // Throws error 394 | enforcedIntAdd(99, 3000); // 3099 395 | ``` 396 | 397 | Using secondary type information for type constructor definition. Any secondary type strings for type constructors will be automatically split on ';' or ',' to allow for multiple type arguments. 398 | 399 | ``` 400 | signet.subtype('array')('triple` function (value) { 401 | return isTypeOf(typeObj.valueType[0])(value[0]) && 402 | isTypeOf(typeObj.valueType[1])(value[1]) && 403 | isTypeOf(typeObj.valueType[2])(value[2]); 404 | }); 405 | 406 | const multiplyTripleBy5 = signet.enforce( 407 | 'triple => triple', 408 | (values) => values.map(x => x * 5) 409 | ); 410 | 411 | multiplyTripleBy5([1, 2]); // Throws error 412 | multiplyTripleBy5([1, 2, 3]); // [5, 10, 15] 413 | ``` 414 | 415 | Types can be aliased using the `alias` function. This allows the programmer to define and declare a custom type based on existing types or a particular implementation on constructed types. 416 | 417 | ``` 418 | signet.alias('R3Point', 'triple'); 419 | signet.alias('R3Matrix', 'triple') 420 | 421 | signet.isTypeOf('R3Point')([1, 2, 3]); // true 422 | signet.isTypeOf('R3Point')([1, 'foo', 3]); // false 423 | 424 | // Matrix in R3: 425 | signet.isTypeOf('R3Matrix')([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); // true 426 | ``` 427 | 428 | ### Direct type checking ### 429 | 430 | Types can be checked from outside of a function call with isTypeOf. The isTypeOf function is curried, so a specific 431 | type check can be reused without recomputing the type object definition: 432 | 433 | ``` 434 | const isInt = signet.isTypeOf('int'); 435 | isInt(7); // true 436 | isInt(83.7); // false 437 | 438 | const isRanged3to4 = signet.isTypeOf('ranged<3;4>'); 439 | isRanged3to4(3.72); // true 440 | isRanged3to4(4000); // false 441 | ``` 442 | 443 | Types can also be checked as a passthrough using `signet.verifyValueType()`. This is especially useful for verifying the type of a value as it is being assigned to a variable. 444 | 445 | ```javascript 446 | function anIntegerOperation (myValue) { 447 | const myInt = signet.verifyValueType('int')(myValue); 448 | 449 | return doSomeIntegerThing(myInt); 450 | } 451 | 452 | // OR 453 | 454 | const verifyIntValue = signet.verifyValueType('int'); 455 | 456 | function anIntegerOperation (myValue) { 457 | const myInt = verifyIntValue(myValue); 458 | 459 | return doSomeIntegerThing(myInt); 460 | } 461 | ``` 462 | 463 | ### Object duck typing ### 464 | 465 | Duck typing functions can be created using the duckTypeFactory function. This means, if an object 466 | type depends on extant properties with correct types, it can be predefined with an object type definition. 467 | 468 | ``` 469 | const myObjDef = { foo: 'string', bar: 'array' }; 470 | const checkMyObj = signet.duckTypeFactory(myObjDef); 471 | 472 | signet.subtype('object')('myObj', checkMyObj); 473 | 474 | signet.isTypeOf('myObj')({ foo: 'testing', bar: [] }); // true 475 | signet.isTypeOf('myObj')({ foo: 'testing' }); // false 476 | signet.isTypeOf('myObj')({ foo: 42, bar: [] }); // false 477 | ``` 478 | 479 | ### Building Recursive Types ### 480 | 481 | Though recursive types such as trees and linked lists can be created with the signet type definition method, but this requires a fair amount of recursive thinking. Instead, Signet provides a means for simply creating recursive types without the recursive thinking. 482 | 483 | Here is an example of creating a linked list type function: 484 | 485 | ``` 486 | const isListNode = signet.duckTypeFactory({ 487 | value: 'int', 488 | next: 'composite, object>' 489 | }); 490 | 491 | const iterableFactory = signet.iterateOn('next'); 492 | const isIntList = signet.recursiveTypeFactory(iterableFactory, isListNode); 493 | ``` 494 | 495 | To create a more complex type like a binary tree, we would do the following: 496 | 497 | ``` 498 | const isBinaryTreeNode = signet.recursiveTypeFactory('binaryTreeNode', { 499 | value: 'int', 500 | left: 'composite<^array, object>', 501 | right: 'composite<^array, object>', 502 | }); 503 | 504 | const isNodeOrNull = (node) => node === null || isBinaryTreeNode(node); 505 | 506 | function isOrderedNode (node) { 507 | return isBinaryTreeNode(node) 508 | || isNodeOrNull(node.left) 509 | || isNodeOrNull(node.right) 510 | || (node.value > node.left 511 | && node.value <= node.right); 512 | } 513 | 514 | signet.subtype('object')('orderedBinaryTreeNode', isOrderedNode); 515 | 516 | function iteratorFactory (value) { 517 | var iterable = []; 518 | 519 | iterable = value.left !== null ? iterable.concat([value.left]) : iterable; 520 | iterable = value.right !== null ? iterable.concat([value.right]) : iterable; 521 | 522 | return signet.iterateOnArray(iterable); 523 | } 524 | 525 | signet.defineRecursiveType('orderedBinaryTree', iteratorFactory, 'binaryTreeNode'); 526 | ``` 527 | 528 | ### Type Chain Information ### 529 | 530 | Signet supports accessing a type's inheritance chain. This means, if you want to know what a type does, you can review the chain 531 | and get a rich understanding of the ancestors which make up the particular type. 532 | 533 | ``` 534 | signet.typeChain('array'); // * -> object -> array 535 | signet.typeChain('tuple'); // * -> object -> array -> tuple 536 | ``` 537 | 538 | ### Type-Level Macros ### 539 | 540 | Signet supports the creation of type-level macros to handle special cases where a 541 | type definition might need some pre-processing before being processed. This is especially 542 | useful if you want to create a type name which contains special characters. The example 543 | from Signet itself is the `()` type. 544 | 545 | ``` 546 | const starTypeDef = parser.parseType('*'); 547 | 548 | parser.registerTypeLevelMacro('()', function () { return starTypeDef; }); 549 | 550 | signet.enforce('() => undefined', function () {})(); 551 | signet.isType('()'); // false 552 | ``` 553 | 554 | ### Type Constructor Arity Declaration ### 555 | 556 | You can declare the number of arguments a type constructor requires (the arity of your type constructor) with curly-brace annotation at definition time. Following are examples of declaring type constructor arity with enforce, subtype and alias: 557 | 558 | ``` 559 | // variant requires at least 1 argument, though more are acceptable 560 | extend('variant{1,}', isVariant, optionsToFunctions); 561 | 562 | // array accepts up to 1 argument 563 | subtype('object')('array{0,1}', checkArray); 564 | 565 | // leftBounded requires exactly 1 argument 566 | alias('leftBounded{1}', 'bounded<_, Infinity>') 567 | ``` 568 | 569 | ## Signet API ### 570 | 571 | - alias: `aliasName != typeString :: aliasName:string, typeString:string => undefined` 572 | - buildInputErrorMessage: `validationResult:array, args:array, signatureTree:array, functionName:string => string` 573 | - buildOutputErrorMessage: `validationResult:array, args:array, signatureTree:array, functionName:string => string` 574 | - classTypeFactory: `class:function, otherProps:[composite, object>] => function` 575 | - defineClassType: `class:function, otherProps:[composite, object>] => function` 576 | - defineDuckType: `typeName:string, duckTypeDef:object => undefined` 577 | - defineExactDuckType: `typeName:string, duckTypeDef:object => undefined` 578 | - defineDependentOperatorOn: `typeName:string => operator:string, operatorCheck:function => undefined` 579 | - defineRecursiveType: `typeName:string, iteratorFactory:function, nodeType:type, typePreprocessor:[function] => undefined` 580 | - duckTypeFactory: `duckTypeDef:object => function` 581 | - enforce: `signature:string, functionToEnforce:function, options:[object] => function` 582 | - currently supported options: 583 | - inputErrorBuilder: `[validationResult:array], [args:array], [signatureTree:array], [functionName:string] => 'string'` 584 | - outputErrorBuilder: `[validationResult:array], [args:array], [signatureTree:array], [functionName:string] => 'string'` 585 | - enforceArguments: `array => arguments => undefined` 586 | - extend: `typeName:string, typeCheck:function, preprocessor:[function] => undefined` 587 | - exactDuckTypeFactory: `duckTypeDef:object => function` 588 | - isRegisteredDuckType: `typeName:string => boolean` 589 | - isSubtypeOf: `rootTypeName:string => typeNameUnderTest:string => boolean` 590 | - isType: `typeName:string => boolean` 591 | - isTypeOf: `typeToCheck:type => value:* => boolean` 592 | - iterateOn: `propertyKey:string => value:* => undefined => *` 593 | - iterateOnArray: `iterationArray:array => undefined => *` 594 | - recursiveTypeFactory: `iteratorFactory:function, nodeType:type => valueToCheck:* => boolean` 595 | - registerTypeLevelMacro: `macro:function => undefined` 596 | - reportDuckTypeErrors: `duckTypeName:string => valueToCheck:object => array>` 597 | - sign: `signature:string, functionToSign:function => function` 598 | - subtype: `rootTypeName:string => subtypeName:string, subtypeCheck:function, preprocessor:[function] => undefined` 599 | - typeChain: `typeName:string => string` 600 | - verify: `signedFunctionToVerify:function, functionArguments:arguments => undefined` 601 | - Throws on error 602 | - verifyValueType: `typeToCheck:type => value:* => result:*` 603 | - Throws on error 604 | - whichType: `typeNames:array => value:* => variant` 605 | - whichVariantType: `variantString:string => value:* => variant` 606 | 607 | ## Change Log ## 608 | 609 | ### 6.7.0 ### 610 | 611 | - Added class types for better TypeScript interop with the following methods: 612 | - `defineClassType` 613 | - `classTypeFactory` 614 | - Added `enforceArguments` method for situations where enforce and sign are either inappropriate or not feasible for use 615 | 616 | ### 6.6.0 ### 617 | 618 | - Introduced decimalPrecision type to declare required or returned decimal precision 619 | 620 | ### 6.5.0 ### 621 | 622 | - Added verifyValueType: provides inline value verification for assignments and other non-function-level enforcement 623 | 624 | ### 6.0.0 ### 625 | 626 | - Added enforcement around function type signatures for higher-order functions 627 | 628 | ### 4.0.0 ### 629 | 630 | - Changed bounded type to polymorphic type on strings, arrays and numbers (and associated proper subtypes) 631 | - Introduced sequence, monotone, increasing and decreasing types 632 | 633 | ### 3.15.0 ### 634 | 635 | - Added isRegisteredDuckType 636 | 637 | ### 3.14.0 ### 638 | 639 | - Updated duck type error reporter to resolve type-level macros to their proper types 640 | 641 | ### 3.13.0 ### 642 | 643 | - Added `^typeName` macro for `not` 644 | 645 | ### 3.12.0 ### 646 | 647 | - Added `!*` macro for `not>` 648 | 649 | ### 3.11.0 ### 650 | 651 | - Added support for declaring type constructor arity 652 | 653 | ### 3.10.0 ### 654 | 655 | - Added `not` type negation and `composite` type composition 656 | 657 | ### 3.9.0 ### 658 | 659 | - Added #=, #< and #> operators for string and array 660 | 661 | ### 3.8.0 ### 662 | 663 | - Added exact duck types to limit types to only those specified 664 | 665 | ### 3.7.0 ### 666 | 667 | - Added nested function type declarations 668 | 669 | ### 3.6.0 ### 670 | 671 | - Added partial application to type constructors in type aliasing 672 | 673 | ### 3.5.0 ### 674 | 675 | - Exposed preprocessor option for extend and subtype functions 676 | 677 | ### 3.4.0 ### 678 | 679 | - Updated error messages to include function name as available 680 | 681 | ### 3.3.0 ### 682 | 683 | - Added object context preservation to ensure constructors and methods can safely be 684 | decorated and standard bind, call and apply actions work as expected 685 | 686 | ### 3.2.0 ### 687 | 688 | - Added support for multiple dependent type expressions 689 | 690 | ### 3.1.0 ### 691 | 692 | - Extended reportDuckTypeErrors to perform a recursive search through an object when possible 693 | 694 | ### 3.0.0 ### 695 | 696 | - Added escape character `%` to parser to allow for special characters in type arguments 697 | 698 | ### 2.0.0 ### 699 | 700 | - Moved to macros which operate directly on uncompiled strings 701 | 702 | ### 1.10.0 ### 703 | 704 | - Introduced type-level macros 705 | 706 | ### 1.9.0 ### 707 | 708 | - Enhanced 'type' type check to verify type is registered 709 | 710 | ### 1.6.0 ### 711 | 712 | - Added new types: 713 | - `leftBounded` -- value must be greater than or equal to min 714 | - `rightBounded` -- value must be less than or equal to max 715 | - `leftBoundedInt` -- value must be greater than or equal to min 716 | - `rightBoundedInt` -- value must be less than or equal to max 717 | 718 | ### 1.5.0 ### 719 | 720 | - Added unorderedProduct -- like tuple but values can be in any order 721 | 722 | ## Breaking Changes 723 | 724 | ### 2.0.0 ### 725 | 726 | - Moved to macros which operate directly on uncompiled strings 727 | 728 | ### 1.0.0 ### 729 | 730 | - No-argument type '`()`' no longer supported 731 | - TaggedUnion deprecated in preference for 'variant' 732 | 733 | ### 0.18.0 ### 734 | 735 | - Function signatures now verify parameter length against total length and length of required paramters. 736 | 737 | ### 0.16.x ### 738 | 739 | - Signet and SignetTypes are now factories in node space to ensure types are encapsulated only in local module. 740 | 741 | ### 0.9.x ### 742 | 743 | - valueType is now an array instead of a string; any type constructor definitions relying on a string will need updating 744 | 745 | ### 0.4.x ### 746 | 747 | - Any top-level types will now cause an error if they are not part of the core Javascript types or in the following list: 748 | - () 749 | - any 750 | - array 751 | - boolean 752 | - function 753 | - number 754 | - object 755 | - string 756 | - symbol -------------------------------------------------------------------------------- /bin/coreTypes.js: -------------------------------------------------------------------------------- 1 | function signetCoreTypes( 2 | parser, 3 | extend, 4 | isTypeOf, 5 | isSignetType, 6 | isSignetSubtypeOf, 7 | subtype, 8 | alias, 9 | defineDependentOperatorOn) { 10 | 'use strict'; 11 | 12 | function not(pred) { 13 | return function (a, b) { 14 | return !pred(a, b); 15 | } 16 | } 17 | 18 | function compareSubsetProps(a, b) { 19 | var keys = Object.keys(b); 20 | var keyLength = keys.length; 21 | var compareOk = true; 22 | 23 | for (var i = 0; i < keyLength && compareOk; i++) { 24 | var key = keys[i]; 25 | compareOk = a[key] === b[key]; 26 | } 27 | 28 | return compareOk; 29 | } 30 | 31 | function objectsAreEqual(a, b) { 32 | if (isNull(a) || isNull(b) || a === b) { return a === b; } 33 | 34 | var objAKeys = Object.keys(a); 35 | var keyLengthEqual = objAKeys.length === Object.keys(b).length; 36 | 37 | return !keyLengthEqual ? false : compareSubsetProps(a, b); 38 | } 39 | 40 | function propertySuperSet(a, b) { 41 | var keyLengthOk = !(Object.keys(a).length < Object.keys(b).length); 42 | return keyLengthOk && compareSubsetProps(a, b); 43 | } 44 | 45 | function propertySubSet(a, b) { 46 | return propertySuperSet(b, a); 47 | } 48 | 49 | function propertyCongruence(a, b) { 50 | var keyLengthOk = Object.keys(a).length === Object.keys(b).length; 51 | return keyLengthOk && compareSubsetProps(a, b); 52 | } 53 | 54 | function isSameType(a, b, aType, bType) { 55 | var aTypeName = getVariantType(a, aType); 56 | var bTypeName = getVariantType(b, bType); 57 | 58 | return aTypeName === bTypeName; 59 | } 60 | 61 | function getVariantType(value, typeDef) { 62 | return whichType(typeDef.subtype)(value); 63 | } 64 | 65 | function whichVariantType(variantString) { 66 | var variantStrings = parser.parseType(variantString).subtype; 67 | 68 | return whichType(variantStrings); 69 | } 70 | 71 | function find(predicate, values) { 72 | var arrayLength = values.length; 73 | var result = null; 74 | 75 | for (var i = 0; i < arrayLength; i++) { 76 | if (predicate(values[i])) { 77 | result = values[i]; 78 | break; 79 | } 80 | } 81 | 82 | return result; 83 | } 84 | 85 | function whichType(typeStrings) { 86 | return function (value) { 87 | function isMatch(typeString) { 88 | return isTypeOf(typeString)(value); 89 | } 90 | 91 | var result = find(isMatch, typeStrings); 92 | return typeof result !== 'string' ? null : result; 93 | }; 94 | } 95 | 96 | function isSubtypeOf(a, b, aType, bType) { 97 | var aTypeName = getVariantType(a, aType); 98 | var bTypeName = getVariantType(b, bType); 99 | 100 | return isSignetSubtypeOf(bTypeName)(aTypeName); 101 | } 102 | 103 | function isSupertypeOf(a, b, aType, bType) { 104 | return isSubtypeOf(b, a, bType, aType); 105 | } 106 | 107 | // function typeImplication(a, b, aType, bType) { 108 | 109 | // } 110 | 111 | function greater(a, b) { 112 | return a > b; 113 | } 114 | 115 | function less(a, b) { 116 | return a < b; 117 | } 118 | 119 | function equal(a, b) { 120 | return a === b; 121 | } 122 | 123 | function isType(typeStr) { 124 | return function (value) { 125 | return typeof value === typeStr; 126 | } 127 | } 128 | 129 | function isNull(value) { 130 | return value === null; 131 | } 132 | 133 | function isFinite(value) { 134 | return Math.abs(value) !== Infinity; 135 | } 136 | 137 | var isNaN = typeof Number.isNaN === 'undefined' 138 | ? function (value) { return value !== value; } 139 | : function (value) { return Number.isNaN(value); }; 140 | 141 | function isNumber(value) { 142 | return typeof value === 'number' && !isNaN(value); 143 | } 144 | 145 | function isBigInt (value) { 146 | return typeof value === 'bigint' && !isNaN(value); // eslint-disable-line valid-typeof 147 | } 148 | 149 | function isNativeNumber (value) { 150 | return isNumber(value) || isBigInt(value); 151 | } 152 | 153 | var checkNumberSubtype = isSignetSubtypeOf('nativeNumber'); 154 | 155 | function isNumberOrSubtype(typeName) { 156 | return checkNumberSubtype(typeName); 157 | } 158 | 159 | var checkStringSubtype = isSignetSubtypeOf('string'); 160 | 161 | function isStringOrSubtype(typeName) { 162 | return typeName === 'string' || checkStringSubtype(typeName); 163 | } 164 | 165 | var checkArraySubtype = isSignetSubtypeOf('array'); 166 | 167 | function isArrayOrSubtype(typeName) { 168 | return typeName === 'array' || checkArraySubtype(typeName); 169 | } 170 | 171 | function getTypeFromTypeString(typeString) { 172 | return parser.parseType(typeString).type; 173 | } 174 | 175 | function isSequence(value, options) { 176 | var subtypeName = getTypeFromTypeString(options[0]); 177 | 178 | if (!isNumberOrSubtype(subtypeName) && !isStringOrSubtype(subtypeName)) { 179 | throw new Error('A sequence may only be comprised of numbers, strings or their subtypes.'); 180 | } 181 | 182 | return checkArray(value, options); 183 | } 184 | 185 | function getMonotoneCompare(values) { 186 | return greater(values[0], values[1]) ? greater : less 187 | } 188 | 189 | function checkMonotoneValues(values) { 190 | var result = true; 191 | var compare = getMonotoneCompare(values); 192 | 193 | for (var i = 1; i < values.length; i++) { 194 | result = result && compare(values[i - 1], values[i]); 195 | } 196 | 197 | return result; 198 | } 199 | 200 | function isMonotone(values, options) { 201 | return isSequence(values, options) && checkMonotoneValues(values); 202 | } 203 | 204 | function isIncreasing(values, options) { 205 | var firstValuesOk = values.length < 2 || less(values[0], values[1]); 206 | 207 | return isMonotone(values, options) && firstValuesOk; 208 | } 209 | 210 | function isDecreasing(values, options) { 211 | var firstValuesOk = values.length < 2 || greater(values[0], values[1]); 212 | 213 | return isMonotone(values, options) && firstValuesOk; 214 | } 215 | 216 | function checkArrayValues(arrayValues, options) { 217 | var result = true; 218 | var checkType = isTypeOf(options[0]); 219 | 220 | for (var i = 0; i < arrayValues.length; i++) { 221 | result = checkType(arrayValues[i]); 222 | 223 | if (!result) { 224 | break; 225 | } 226 | } 227 | 228 | return result; 229 | } 230 | 231 | function isArrayType(value) { 232 | return Object.prototype.toString.call(value) === '[object Array]'; 233 | } 234 | 235 | 236 | function checkArray(value, options) { 237 | var checkValues = options.length > 0 && options[0] !== '*'; 238 | 239 | return isArrayType(value) 240 | && (!checkValues || checkArrayValues(value, options)); 241 | } 242 | 243 | function isInt(value) { 244 | return isBigInt(value) || checkInt(value); 245 | } 246 | 247 | function checkInt(value) { 248 | return Math.floor(value) === value && value !== Infinity; 249 | } 250 | 251 | function isBounded(value, options) { 252 | var typeName = getTypeFromTypeString(options[0]); 253 | var range = optionsToRangeObject(options); 254 | var isArrayOrString = isArrayOrSubtype(typeName) || isStringOrSubtype(typeName); 255 | var isNumberType = isNumberOrSubtype(typeName); 256 | 257 | var typeStringIsValid = isNumberType || isArrayOrString; 258 | 259 | if (!typeStringIsValid) { 260 | var errorMessage = 'Bounded type only accepts types of number, string, array or subtypes of these.' 261 | throw new Error(errorMessage); 262 | } else if (isTypeOf(options[0])(value)) { 263 | var valueToCheck = isArrayOrString ? value.length : value; 264 | return checkRange(valueToCheck, range); 265 | } else { 266 | return false; 267 | } 268 | } 269 | 270 | function checkRange(value, range) { 271 | return range.min <= value && value <= range.max; 272 | } 273 | 274 | function optionsToRangeObject(options) { 275 | var range = { 276 | min: Number(options[1]), 277 | max: Number(options[2]) 278 | }; 279 | return range; 280 | } 281 | 282 | function optionsToRegex(options) { 283 | return options.length === 1 ? options[0] : options.join(';'); 284 | } 285 | 286 | function checkFormattedString(value, regex) { 287 | return value.match(regex) !== null; 288 | } 289 | 290 | function optionsToFunctions(options) { 291 | return options.map(isTypeOf); 292 | } 293 | 294 | function optionsToFunction(options) { 295 | return options.join(', '); 296 | } 297 | 298 | function checkArgumentsObject(value) { 299 | return !isNull(value); 300 | } 301 | 302 | function isRegExp(value) { 303 | return Object.prototype.toString.call(value) === '[object RegExp]'; 304 | } 305 | 306 | function compareTypes(typeA, typeB) { 307 | var result = typeA === typeB ? 1 : 0; 308 | return isSignetSubtypeOf(typeA)(typeB) ? -1 : result; 309 | } 310 | 311 | function insertTypeName(typeNameArray, typeName) { 312 | var index = 0; 313 | var offset = 0; 314 | 315 | for (index; index < typeNameArray.length; index++) { 316 | offset = compareTypes(typeNameArray[index], typeName); 317 | if (offset !== 0) { 318 | break; 319 | } 320 | } 321 | 322 | typeNameArray.splice(index + offset, 0, typeName); 323 | 324 | return typeNameArray; 325 | } 326 | 327 | function sortTypeNames(typeNames) { 328 | return typeNames.reduce(insertTypeName, []); 329 | } 330 | 331 | function castOutOn(predicate, values) { 332 | var result = false; 333 | 334 | for (var i = 0; i < values.length; i++) { 335 | result = predicate(values[i]); 336 | 337 | if (result) { 338 | values.splice(i, 1); 339 | break; 340 | } 341 | } 342 | 343 | return result; 344 | } 345 | 346 | function typeDoesNotExistIn(values) { 347 | var valuesCopy = values.slice(0); 348 | 349 | return function (typeName) { 350 | var isTypeOfTypeName = isTypeOf(typeName); 351 | 352 | return !castOutOn(isTypeOfTypeName, valuesCopy); 353 | }; 354 | } 355 | 356 | function reduce(action, values, initial) { 357 | var arrayLength = values.length; 358 | var result = initial; 359 | 360 | for (var i = 0; i < arrayLength; i++) { 361 | result = action(result, values[i]); 362 | } 363 | 364 | return result; 365 | } 366 | 367 | function filterOn(predicate) { 368 | return function (result, value) { 369 | if (predicate(value)) { 370 | result.push(value); 371 | } 372 | 373 | return result; 374 | } 375 | } 376 | 377 | function filter(predicate, values) { 378 | return reduce(filterOn(predicate), values, []); 379 | } 380 | 381 | function checkValueTypes(values, typeNames) { 382 | var sortedTypeNames = sortTypeNames(typeNames); 383 | var filterResult = filter(typeDoesNotExistIn(values), sortedTypeNames); 384 | return filterResult.length === 0; 385 | } 386 | 387 | function isUnorderedProduct(value, typeNames) { 388 | var isCorrectLength = value.length === typeNames.length; 389 | return isCorrectLength && checkValueTypes(value, typeNames); 390 | } 391 | 392 | function checkTuple(value, options) { 393 | var lengthOkay = value.length === options.length; 394 | 395 | return lengthOkay && options.reduce(verifyTupleTypes, true); 396 | 397 | function verifyTupleTypes(result, validator, index) { 398 | return result && validator(value[index]); 399 | } 400 | } 401 | 402 | function isVariant(value, options) { 403 | return options.length === 0 404 | || filter(checkValueType, options).length > 0; 405 | 406 | function checkValueType(validator) { 407 | return validator(value); 408 | } 409 | } 410 | 411 | function checkTaggedUnion(value, options) { 412 | console.warn('Tagged Union is deprecated, use variant instead.'); 413 | return isVariant(value, options); 414 | } 415 | 416 | function checkCompositeType(value, typePredicates) { 417 | return typePredicates.reduce(function (result, predicate) { 418 | return result && predicate(value); 419 | }, true); 420 | } 421 | 422 | function checkNot(value, typePredicates) { 423 | return !typePredicates[0](value); 424 | } 425 | 426 | function isRegisteredType(value) { 427 | return typeof value === 'function' || isSignetType(parser.parseType(value).type); 428 | } 429 | 430 | function equalLength(a, b) { 431 | return a.length === b.length; 432 | } 433 | 434 | function longer(a, b) { 435 | return a.length > b.length; 436 | } 437 | 438 | function shorter(a, b) { 439 | return a.length < b.length; 440 | } 441 | 442 | parser.registerTypeLevelMacro(function emptyParamsToStar(value) { 443 | return /^\(\s*\)$/.test(value.trim()) ? '*' : value; 444 | }); 445 | 446 | function buildTypePattern(macroPattern) { 447 | var token = '{{typePattern}}'; 448 | var typePattern = '^([^\\:]+\\:)?(\\[)?' + token + '(\\])?$'; 449 | 450 | return new RegExp(typePattern.replace(token, macroPattern)); 451 | } 452 | 453 | function matchAndReplace(value, pattern, replacement) { 454 | return pattern.test(value) ? value.replace(pattern, replacement) : value; 455 | } 456 | 457 | parser.registerTypeLevelMacro(function bangStarDefinedValues(value) { 458 | var pattern = buildTypePattern('(\\!\\*)'); 459 | var replacementStr = '$1$2not>$4'; 460 | 461 | return matchAndReplace(value.trim(), pattern, replacementStr); 462 | }); 463 | 464 | parser.registerTypeLevelMacro(function questionMarkToOptionalType(value) { 465 | var pattern = buildTypePattern('\\?([^\\]]*)'); 466 | var replacementStr = '$1$2variant$4'; 467 | 468 | return matchAndReplace(value.trim(), pattern, replacementStr); 469 | }); 470 | 471 | parser.registerTypeLevelMacro(function caretToNot(value) { 472 | var pattern = buildTypePattern('\\^([^\\]]*)'); 473 | var replacementStr = '$1$2not<$3>$4'; 474 | 475 | return matchAndReplace(value.trim(), pattern, replacementStr); 476 | }); 477 | 478 | parser.registerSignatureLevelMacro(function signatureToFunction(value) { 479 | var signaturePattern = /(\()((.*\=\>)+(.*))(\))/ 480 | var signatureMatch = signaturePattern.test(value); 481 | 482 | return signatureMatch ? value.replace(signaturePattern, 'function<$2>') : value; 483 | }); 484 | 485 | function checkSignatureMatch(fn, signature) { 486 | return signature !== '' 487 | ? fn.signature === signature 488 | : typeof fn.signature === 'string'; 489 | } 490 | 491 | var enforcePattern = /enforceDecorator/ig; 492 | 493 | function isEnforceFunction(fn) { 494 | var fnString = Function.prototype.toString.call(fn); 495 | return enforcePattern.test(fnString); 496 | } 497 | 498 | function isEnforcedFunction(value, options) { 499 | var signature = typeof options !== 'undefined' 500 | ? options.join(',').trim() 501 | : ''; 502 | var valueIsFunction = typeof value === 'function'; 503 | 504 | return valueIsFunction 505 | && isEnforceFunction(value) 506 | && checkSignatureMatch(value, signature); 507 | } 508 | 509 | function setDecimalPrecision(value, precision) { 510 | var magnitude = Math.pow(10, precision); 511 | 512 | return Math.floor(value * magnitude) / magnitude; 513 | } 514 | 515 | function checkDecimalPrecision(value, options) { 516 | var precision = parseFloat(options[0]); 517 | 518 | if (!checkInt(precision) || !checkRange(precision, { min: 0, max: Infinity })) { 519 | throw new Error('Precision value must be of type leftBoundedInt<0>, but got: ' + precision + 'of type ' + typeof precision); 520 | } 521 | 522 | return value === setDecimalPrecision(value, precision); 523 | } 524 | 525 | function isPromise(value) { 526 | return value instanceof Promise; 527 | } 528 | 529 | extend('boolean{0}', isType('boolean')); 530 | extend('function{0,1}', isType('function'), optionsToFunction); 531 | extend('enforcedFunction{0,1}', isEnforcedFunction); 532 | extend('nativeNumber', isNativeNumber); 533 | extend('object{0}', isType('object')); 534 | extend('string{0}', isType('string')); 535 | extend('symbol{0}', isType('symbol')); 536 | extend('undefined{0}', isType('undefined')); 537 | extend('not{1}', checkNot, optionsToFunctions); 538 | extend('null{0}', isNull); 539 | extend('variant{1,}', isVariant, optionsToFunctions); 540 | extend('taggedUnion{1,}', checkTaggedUnion, optionsToFunctions); 541 | extend('composite{1,}', checkCompositeType, optionsToFunctions); 542 | extend('bounded{3}', isBounded); 543 | extend('promise', isPromise); 544 | 545 | subtype('nativeNumber')('number{0}', isNumber); 546 | subtype('nativeNumber')('bigint{0}', isBigInt); 547 | subtype('nativeNumber')('int{0}', isInt); 548 | 549 | subtype('object')('array{0,}', checkArray); 550 | subtype('object')('regexp{0}', isRegExp); 551 | subtype('nativeNumber')('finiteNumber', isFinite); 552 | subtype('number')('decimalPrecision{1}', checkDecimalPrecision); 553 | subtype('finiteNumber')('finiteInt{0}', checkInt); 554 | subtype('string')('formattedString{1}', checkFormattedString, optionsToRegex); 555 | subtype('array')('tuple{1,}', checkTuple, optionsToFunctions); 556 | subtype('array')('unorderedProduct{1,}', isUnorderedProduct); 557 | subtype('object')('arguments{0}', checkArgumentsObject); 558 | 559 | subtype('array')('sequence{1}', isSequence); 560 | subtype('array')('monotoneSequence{1}', isMonotone); 561 | subtype('array')('increasingSequence{1}', isIncreasing); 562 | subtype('array')('decreasingSequence{1}', isDecreasing); 563 | 564 | alias('leftBounded', 'bounded<_, _, Infinity>'); 565 | alias('rightBounded', 'bounded<_, -Infinity, _>'); 566 | 567 | alias('boundedString{2}', 'bounded'); 568 | alias('leftBoundedString{1}', 'leftBounded'); 569 | alias('rightBoundedString{1}', 'rightBounded'); 570 | 571 | alias('boundedNumber{2}', 'bounded'); 572 | alias('leftBoundedNumber{1}', 'leftBounded'); 573 | alias('rightBoundedNumber{1}', 'rightBounded'); 574 | 575 | alias('boundedFiniteNumber{2}', 'bounded'); 576 | alias('leftBoundedFiniteNumber{1}', 'leftBounded'); 577 | alias('rightBoundedFiniteNumber{1}', 'rightBounded'); 578 | 579 | alias('boundedInt{2}', 'bounded'); 580 | alias('leftBoundedInt{1}', 'leftBounded'); 581 | alias('rightBoundedInt{1}', 'rightBounded'); 582 | 583 | alias('boundedFiniteInt{2}', 'bounded'); 584 | alias('leftBoundedFiniteInt{1}', 'leftBounded'); 585 | alias('rightBoundedFiniteInt{1}', 'rightBounded'); 586 | 587 | alias('typeValue{0}', 'variant'); 588 | subtype('typeValue')('type{0}', isRegisteredType); 589 | 590 | alias('any{0}', '*'); 591 | alias('void{0}', '*'); 592 | 593 | defineDependentOperatorOn('nativeNumber')('>', greater); 594 | defineDependentOperatorOn('nativeNumber')('<', less); 595 | defineDependentOperatorOn('nativeNumber')('=', equal); 596 | defineDependentOperatorOn('nativeNumber')('>=', not(less)); 597 | defineDependentOperatorOn('nativeNumber')('<=', not(greater)); 598 | defineDependentOperatorOn('nativeNumber')('!=', not(equal)); 599 | 600 | defineDependentOperatorOn('int')('>', greater); 601 | defineDependentOperatorOn('int')('<', less); 602 | defineDependentOperatorOn('int')('=', equal); 603 | defineDependentOperatorOn('int')('>=', not(less)); 604 | defineDependentOperatorOn('int')('<=', not(greater)); 605 | defineDependentOperatorOn('int')('!=', not(equal)); 606 | 607 | defineDependentOperatorOn('string')('=', equal); 608 | defineDependentOperatorOn('string')('!=', not(equal)); 609 | defineDependentOperatorOn('string')('#=', equalLength); 610 | defineDependentOperatorOn('string')('#<', shorter); 611 | defineDependentOperatorOn('string')('#>', longer); 612 | 613 | defineDependentOperatorOn('array')('#=', equalLength); 614 | defineDependentOperatorOn('array')('#<', shorter); 615 | defineDependentOperatorOn('array')('#>', longer); 616 | 617 | defineDependentOperatorOn('object')('=', objectsAreEqual); 618 | defineDependentOperatorOn('object')('!=', not(objectsAreEqual)); 619 | defineDependentOperatorOn('object')(':>', propertySuperSet); 620 | defineDependentOperatorOn('object')(':<', propertySubSet); 621 | defineDependentOperatorOn('object')(':=', propertyCongruence); 622 | defineDependentOperatorOn('object')(':!=', not(propertyCongruence)); 623 | 624 | defineDependentOperatorOn('variant')('isTypeOf', isSameType); 625 | defineDependentOperatorOn('variant')('=:', isSameType); 626 | defineDependentOperatorOn('variant')('<:', isSubtypeOf); 627 | defineDependentOperatorOn('variant')('>:', isSupertypeOf); 628 | 629 | return { 630 | whichType: whichType, 631 | whichVariantType: whichVariantType 632 | }; 633 | 634 | } 635 | 636 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 637 | module.exports = signetCoreTypes; 638 | } -------------------------------------------------------------------------------- /bin/duckTypes.js: -------------------------------------------------------------------------------- 1 | function signetDuckTypes(typelog, isTypeOf, parseType, assembleType) { 2 | 3 | var duckTypeErrorReporters = {}; 4 | 5 | function defineDuckType(typeName, objectDef) { 6 | var duckType = buildDuckType(objectDef); 7 | var duckTypeErrorReporter = buildDuckTypeErrorReporter(objectDef); 8 | 9 | typelog.defineSubtypeOf('object')(typeName, duckType); 10 | duckTypeErrorReporters[typeName] = duckTypeErrorReporter; 11 | } 12 | 13 | function getValueType(propertyValue) { 14 | var propertyType = typeof propertyValue; 15 | return isTypeOf('array')(propertyValue) 16 | ? 'array' 17 | : propertyType; 18 | } 19 | 20 | function buildDuckTypeObject(propertyList, baseObject) { 21 | return propertyList.reduce(function (result, key) { 22 | if (key !== 'constructor') { 23 | var propertyValue = baseObject[key]; 24 | var propertyType = getValueType(propertyValue); 25 | 26 | result[key] = propertyType; 27 | } 28 | 29 | return result 30 | }, {}); 31 | } 32 | 33 | function isPrototypalObject(value) { 34 | return typeof value.prototype === 'object'; 35 | } 36 | 37 | function throwIfNotPrototypalObject(value) { 38 | if (!isPrototypalObject(value)) { 39 | var message = "Function defineClassType expected a prototypal object or class, but got a value of type " + typeof value; 40 | throw new TypeError(message); 41 | } 42 | } 43 | 44 | function mergeTypeProps(destinationObject, propsObject) { 45 | Object.keys(propsObject).forEach(function (key) { 46 | if (isTypeOf('not')(destinationObject[key])) { 47 | var message = 'Cannot reassign property ' + key + ' on duck type object'; 48 | throw new Error(message); 49 | } 50 | 51 | destinationObject[key] = propsObject[key]; 52 | }); 53 | } 54 | 55 | function getDuckTypeObject(prototypalObject, otherProps) { 56 | throwIfNotPrototypalObject(prototypalObject); 57 | 58 | var prototype = prototypalObject.prototype; 59 | 60 | var propertyList = Object.getOwnPropertyNames(prototype); 61 | var duckTypeObject = buildDuckTypeObject(propertyList, prototype); 62 | 63 | if (isTypeOf('composite, object>')(otherProps)) { 64 | mergeTypeProps(duckTypeObject, otherProps); 65 | } 66 | 67 | return duckTypeObject 68 | } 69 | 70 | function defineClassType(prototypalObject, otherProps) { 71 | var className = prototypalObject.name; 72 | var duckTypeObject = getDuckTypeObject(prototypalObject, otherProps) 73 | 74 | defineDuckType(className, duckTypeObject); 75 | } 76 | 77 | function classTypeFactory(prototypalObject, otherProps) { 78 | var duckTypeObject = getDuckTypeObject(prototypalObject, otherProps); 79 | 80 | return duckTypeFactory(duckTypeObject); 81 | } 82 | 83 | function getErrorValue(value, typeName) { 84 | if (typeof duckTypeErrorReporters[typeName] === 'function') { 85 | return duckTypeErrorReporters[typeName](value); 86 | } 87 | 88 | return value; 89 | } 90 | 91 | var isString = isTypeOf('string'); 92 | 93 | function getTypeName(objectDef, key) { 94 | return typeof objectDef[key] === 'string' ? objectDef[key] : objectDef[key].name; 95 | } 96 | 97 | function buildTypeResolvedDefinition(keys, objectDef) { 98 | var result = {}; 99 | 100 | for (var i = 0; i < keys.length; i++) { 101 | var key = keys[i]; 102 | var typeValue = objectDef[key]; 103 | 104 | result[key] = isString(typeValue) 105 | ? assembleType(parseType(typeValue)) 106 | : typeValue 107 | } 108 | 109 | return result; 110 | } 111 | 112 | function isObjectInstance(value) { 113 | return typeof value === 'object' && value !== null; 114 | } 115 | 116 | function passThrough(result) { 117 | return result; 118 | } 119 | 120 | function buildTestAndReport(key, typeName, typePredicate) { 121 | return function (result, value) { 122 | if (!typePredicate(value[key])) { 123 | result.push([key, typeName, getErrorValue(value[key], typeName)]); 124 | } 125 | 126 | return result; 127 | }; 128 | } 129 | 130 | function combineReporters(reporter1, reporter2) { 131 | return function (result, value) { 132 | result = reporter1(result, value); 133 | return reporter2(result, value); 134 | }; 135 | } 136 | 137 | function buildDuckTypeReporter(keys, objectDef, typeResolvedDefinition) { 138 | var testAndReport = passThrough; 139 | 140 | for (var i = 0; i < keys.length; i++) { 141 | var key = keys[i]; 142 | var typePredicate = isTypeOf(objectDef[key]); 143 | var typeName = getTypeName(typeResolvedDefinition, key); 144 | 145 | var currentReporter = buildTestAndReport(key, typeName, typePredicate); 146 | testAndReport = combineReporters(testAndReport, currentReporter); 147 | } 148 | 149 | return function (value) { 150 | return testAndReport([], value); 151 | }; 152 | } 153 | 154 | function buildDuckTypeErrorReporter(objectDef) { 155 | var keys = Object.keys(objectDef); 156 | var typeResolvedDefinition = buildTypeResolvedDefinition(keys, objectDef); 157 | var duckTypeReporter = buildDuckTypeReporter(keys, objectDef, typeResolvedDefinition); 158 | 159 | return function (value) { 160 | if (!isObjectInstance(value)) { 161 | return [['badDuckTypeValue', 'object', value]] 162 | } 163 | 164 | return duckTypeReporter(value); 165 | }; 166 | } 167 | 168 | var isDuckTypeCheckable = isTypeOf('composite, variant>') 169 | 170 | 171 | function alwaysTrue() { return true; } 172 | 173 | function buildPropCheck(propCheck, key, type) { 174 | var typeCheck = isTypeOf(type); 175 | 176 | if(typeof typeCheck !== 'function') { 177 | typeCheck = buildDuckType(type); 178 | } 179 | 180 | return function (obj) { 181 | return typeCheck(obj[key]) && propCheck(obj); 182 | } 183 | } 184 | 185 | function buildDuckType(definition) { 186 | var keys = Object.keys(definition); 187 | var typeCheck = alwaysTrue; 188 | 189 | for (var i = 0; i < keys.length; i++) { 190 | var key = keys[i]; 191 | typeCheck = buildPropCheck(typeCheck, key, definition[key]) 192 | } 193 | 194 | return function (obj) { 195 | return isDuckTypeCheckable(obj) && typeCheck(obj); 196 | } 197 | } 198 | 199 | function duckTypeFactory(objectDef) { 200 | return buildDuckType(objectDef); 201 | } 202 | 203 | function reportDuckTypeErrors(typeName) { 204 | var errorChecker = duckTypeErrorReporters[typeName]; 205 | 206 | if (typeof errorChecker === 'undefined') { 207 | throw new Error('No duck type "' + typeName + '" exists.'); 208 | } 209 | 210 | return function (value) { 211 | return errorChecker(value); 212 | } 213 | } 214 | 215 | function exactDuckTypeFactory(objectDef) { 216 | var propertyLength = Object.keys(objectDef).length; 217 | var duckType = duckTypeFactory(objectDef); 218 | 219 | return function (value) { 220 | return propertyLength === Object.keys(value).length && duckType(value); 221 | }; 222 | } 223 | 224 | function defineExactDuckType(typeName, objectDef) { 225 | var duckType = exactDuckTypeFactory(objectDef); 226 | var duckTypeErrorReporter = buildDuckTypeErrorReporter(objectDef); 227 | 228 | typelog.defineSubtypeOf('object')(typeName, duckType); 229 | duckTypeErrorReporters[typeName] = duckTypeErrorReporter; 230 | 231 | } 232 | 233 | function isRegisteredDuckType(typeName) { 234 | return typeof duckTypeErrorReporters[typeName] === 'function'; 235 | } 236 | 237 | return { 238 | buildDuckTypeErrorChecker: buildDuckTypeErrorReporter, 239 | classTypeFactory: classTypeFactory, 240 | defineClassType: defineClassType, 241 | defineDuckType: defineDuckType, 242 | defineExactDuckType: defineExactDuckType, 243 | duckTypeFactory: duckTypeFactory, 244 | exactDuckTypeFactory: exactDuckTypeFactory, 245 | isRegisteredDuckType: isRegisteredDuckType, 246 | reportDuckTypeErrors: reportDuckTypeErrors 247 | }; 248 | } 249 | 250 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 251 | module.exports = signetDuckTypes; 252 | } 253 | -------------------------------------------------------------------------------- /bin/recursiveTypes.js: -------------------------------------------------------------------------------- 1 | function signetRecursiveTypes(extend, isTypeOf) { 2 | var isArray = isTypeOf('array'); 3 | var isUndefined = isTypeOf('undefined'); 4 | 5 | function checkIterator(nextValue, action) { 6 | var currentValue = nextValue(); 7 | var isOk = true; 8 | 9 | while (isOk && currentValue !== null) { 10 | isOk = action(currentValue); 11 | currentValue = nextValue(); 12 | } 13 | 14 | return isOk; 15 | } 16 | 17 | function recursiveTypeFactory(iteratorFactory, nodeType) { 18 | var isNode = isTypeOf(nodeType); 19 | 20 | return function checkType(value) { 21 | var iterator = iteratorFactory(value); 22 | return isNode(value) && checkIterator(iterator, checkType); 23 | }; 24 | } 25 | 26 | function iterateOn (key) { 27 | return function iteratorFactory(value) { 28 | var iterableValues = isArray(value[key]) 29 | ? value[key].slice(0) 30 | : [value[key]]; 31 | 32 | return function getNextValue() { 33 | var nextValue = iterableValues.shift(); 34 | return isUndefined(nextValue) ? null : nextValue; 35 | } 36 | } 37 | } 38 | 39 | function iterateOnArray (values) { 40 | var iterableValues = values.slice(0); 41 | 42 | return function () { 43 | var nextValue = iterableValues.shift(); 44 | return isUndefined(nextValue) ? null : nextValue; 45 | } 46 | } 47 | 48 | function defineRecursiveType (typeName, iteratorFactory, nodeType, preprocessor) { 49 | var recursiveType = recursiveTypeFactory(iteratorFactory, nodeType); 50 | extend(typeName, recursiveType, preprocessor); 51 | 52 | } 53 | 54 | return { 55 | defineRecursiveType: defineRecursiveType, 56 | iterateOn: iterateOn, 57 | iterateOnArray: iterateOnArray, 58 | recursiveTypeFactory: recursiveTypeFactory 59 | }; 60 | } 61 | 62 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 63 | module.exports = signetRecursiveTypes; 64 | } 65 | -------------------------------------------------------------------------------- /bin/signet.js: -------------------------------------------------------------------------------- 1 | function signetBuilder( 2 | typelog, 3 | validator, 4 | checker, 5 | parser, 6 | assembler, 7 | duckTypes, 8 | coreTypes, 9 | recursiveTypes) { 10 | 11 | 'use strict'; 12 | 13 | var placeholderPattern = /([<\;\,]\s*)(_)(\s*[>\;\,])/; 14 | 15 | function hasPlaceholder(typeStr) { 16 | return placeholderPattern.test(typeStr); 17 | } 18 | 19 | function replacePlaceholders(typeStr, typeValues) { 20 | return typeValues.reduce(function (result, typeValue) { 21 | return result.replace(placeholderPattern, '$1' + typeValue + '$3'); 22 | }, typeStr); 23 | } 24 | 25 | function buildTypeAlias(typeDef) { 26 | var checkValue = typelog.isTypeOf(typeDef); 27 | 28 | return function typeCheck(value) { 29 | return checkValue(value); 30 | }; 31 | } 32 | 33 | function buildPartialTypeAlias(typeStr) { 34 | return function typeCheck(value, typeValues) { 35 | var finalTypeStr = replacePlaceholders(typeStr, typeValues); 36 | var typeDef = parser.parseType(finalTypeStr); 37 | 38 | return buildTypeAlias(typeDef)(value); 39 | }; 40 | } 41 | 42 | function alias(key, typeStr) { 43 | var typeAlias = hasPlaceholder(typeStr) 44 | ? buildPartialTypeAlias(typeStr) 45 | : buildTypeAlias(parser.parseType(typeStr)); 46 | 47 | extend(key, typeAlias); 48 | } 49 | 50 | function isTypeOf(typeValue) { 51 | return typeof typeValue === 'string' 52 | ? typelog.isTypeOf(parser.parseType(typeValue)) 53 | : typeValue; 54 | } 55 | 56 | function verifyValueType(typeValue) { 57 | var isValidType = isTypeOf(typeValue); 58 | 59 | return function (value) { 60 | if (!isValidType(value)) { 61 | throw new TypeError('Expected value of type ' + typeValue + ', but got ' + String(value) + ' of type ' + typeof value); 62 | } 63 | 64 | return value; 65 | }; 66 | } 67 | 68 | function addImmutableProperty(obj, key, value) { 69 | Object.defineProperty(obj, key, { 70 | value: value, 71 | writeable: false 72 | }); 73 | 74 | return obj; 75 | } 76 | 77 | function attachSignatureAssembler(fn, signatureTree) { 78 | addImmutableProperty(fn, 'signatureTree', signatureTree); 79 | 80 | Object.defineProperty(fn, 'signature', { 81 | writeable: false, 82 | get: function () { 83 | return assembler.assembleSignature(fn.signatureTree); 84 | } 85 | }); 86 | 87 | return fn; 88 | } 89 | 90 | function throwOnSignatureError(signatureTree, fn) { 91 | var signatureCheckResult = checker.checkSignature(signatureTree); 92 | var lastIndex = signatureTree.length - 1; 93 | 94 | if (signatureTree.length < 2) { 95 | throw new SyntaxError('Signature must have both input and output types'); 96 | } 97 | 98 | if (signatureTree[0].length < fn.length) { 99 | throw new Error('Signature declaration too short for function with ' + fn.length + ' arguments.'); 100 | } 101 | 102 | if (signatureTree[lastIndex].length > 1) { 103 | throw new SyntaxError('Signature can only have a single output type'); 104 | } 105 | 106 | if (signatureCheckResult !== null) { 107 | var invalidTypes = signatureCheckResult.map(assembler.assembleType); 108 | throw new TypeError("Signature contains invalid types: " + invalidTypes.join(', ')); 109 | } 110 | } 111 | 112 | function signFn(signatureTree, fn) { 113 | attachSignatureAssembler(fn, signatureTree); 114 | return addImmutableProperty(fn, 'signatureTree', signatureTree); 115 | } 116 | 117 | function sign(signature, fn) { 118 | var signatureTree = parser.parseSignature(signature); 119 | 120 | throwOnSignatureError(signatureTree, fn); 121 | 122 | return signFn(signatureTree, fn); 123 | } 124 | 125 | function buildEvaluationError(validationResult, prefixMixin, functionName) { 126 | var expectedType = validationResult[0]; 127 | var value = validationResult[1]; 128 | var valueType = typeof value; 129 | 130 | var errorMessage = functionName + ' expected a ' + prefixMixin + 'value of type ' + 131 | expectedType + ' but got ' + 132 | validationResult[1] + ' of type ' + valueType; 133 | 134 | return errorMessage; 135 | } 136 | 137 | function evaluationErrorFactory(prefix) { 138 | return function throwEvaluationError( 139 | validationResult, 140 | errorBuilder, 141 | args, 142 | signatureTree, 143 | functionName 144 | ) { 145 | 146 | var errorMessage = ''; 147 | 148 | if (typeof errorBuilder === 'function') { 149 | errorMessage = errorBuilder(validationResult, args, signatureTree, functionName); 150 | } else { 151 | errorMessage = buildEvaluationError(validationResult, prefix, functionName); 152 | } 153 | 154 | throw new TypeError(errorMessage); 155 | } 156 | } 157 | 158 | var throwInputError = evaluationErrorFactory(''); 159 | var throwOutputError = evaluationErrorFactory('return '); 160 | 161 | function buildInputErrorMessage(validationResult, args, signatureTree, functionName) { 162 | return buildEvaluationError(validationResult, '', functionName); 163 | } 164 | 165 | function buildOutputErrorMessage(validationResult, args, signatureTree, functionName) { 166 | return buildEvaluationError(validationResult, 'return ', functionName); 167 | } 168 | 169 | function verify(fn, args, environment) { 170 | var result = validator.validateArguments(fn.signatureTree[0], environment)(args); 171 | 172 | if (result !== null) { 173 | throwInputError(result, null, args, fn.signatureTree, getFunctionName(fn)); 174 | } 175 | } 176 | 177 | function enforceArguments(types) { 178 | return function (rawArgs) { 179 | var args = Array.prototype.slice.call(rawArgs, 0); 180 | var parsedTypes = types.map(parser.parseType); 181 | var result = validator.validateArguments(parsedTypes, undefined)(args); 182 | 183 | if (result !== null) { 184 | throwInputError(result, null, args, [parsedTypes], 'Called Function'); 185 | } 186 | 187 | } 188 | } 189 | 190 | function getFunctionName(fn) { 191 | return fn.name === '' ? 'Anonymous' : fn.name; 192 | } 193 | 194 | function processFunction(fn, type, options) { 195 | var signature = type.subtype.join(', ').trim(); 196 | var returnFn = signature !== '' 197 | ? enforce(signature, fn, options) 198 | : fn; 199 | 200 | return returnFn; 201 | } 202 | 203 | function processArg(arg, type, options) { 204 | var cleanType = typeof type === 'object' ? type : {}; 205 | 206 | if (cleanType.type === 'function') { 207 | return processFunction(arg, cleanType, options); 208 | } else { 209 | return arg; 210 | } 211 | } 212 | 213 | function processArgs(args, typeList, options) { 214 | var argResults = []; 215 | var argIndex = 0; 216 | var typeIndex = 0; 217 | 218 | for (argIndex; argIndex < args.length; argIndex++) { 219 | var currentArg = args[argIndex]; 220 | var currentType = undefined 221 | 222 | for (typeIndex; typeIndex < typeList.length; typeIndex++) { 223 | currentType = typeList[typeIndex]; 224 | 225 | if (currentType.typeCheck(currentArg)) { 226 | break; 227 | } 228 | } 229 | 230 | argResults.push(processArg(currentArg, currentType, options)); 231 | } 232 | 233 | return argResults; 234 | } 235 | 236 | function quickSliceFrom(index, values) { 237 | var result = []; 238 | 239 | for (var i = index; i < values.length; i++) { 240 | result.push(values[i]); 241 | } 242 | 243 | return result; 244 | } 245 | 246 | function buildEnforcer(signatureTree, fn, options) { 247 | var functionName = getFunctionName(fn); 248 | 249 | return function () { 250 | var args = quickSliceFrom(0, arguments); 251 | 252 | var environmentTable = typeof signatureTree.environment === 'object' 253 | ? Object.create(signatureTree.environment) 254 | : {}; 255 | 256 | var validationResult = validator.validateArguments(signatureTree[0], environmentTable)(args); 257 | var nextTree = quickSliceFrom(1, signatureTree); 258 | 259 | if (validationResult !== null) { 260 | throwInputError( 261 | validationResult, 262 | options.inputErrorBuilder, 263 | args, 264 | signatureTree, 265 | functionName); 266 | } 267 | 268 | var signatureIsCurried = signatureTree.length > 2; 269 | 270 | var processedArgs = processArgs(args, signatureTree[0], options); 271 | var result = fn.apply(this, processedArgs); 272 | 273 | var isFinalResult = nextTree.length === 1; 274 | 275 | var resultCheckTree = nextTree[0]; 276 | resultCheckTree.dependent = signatureTree[0].dependent; 277 | 278 | var resultValidation = isFinalResult 279 | ? validator.validateArguments(resultCheckTree, environmentTable)([result]) 280 | : null; 281 | 282 | if (resultValidation !== null) { 283 | throwOutputError( 284 | resultValidation, 285 | options.outputErrorBuilder, 286 | processedArgs, 287 | signatureTree, 288 | functionName); 289 | } 290 | 291 | nextTree.environment = environmentTable; 292 | nextTree[0].dependent = signatureTree[0].dependent; 293 | 294 | return signatureIsCurried 295 | ? enforceOnTree(nextTree, result, options) 296 | : result; 297 | }; 298 | } 299 | 300 | function buildEnforceDecorator(enforcer) { 301 | return function enforceDecorator() { 302 | var args = quickSliceFrom(0, arguments); 303 | return enforcer.apply(this, args); 304 | } 305 | } 306 | 307 | function attachProps(fn, enforcedFn) { 308 | var keys = Object.keys(fn); 309 | 310 | keys.reduce(function (enforcedFn, key) { 311 | enforcedFn[key] = fn[key]; 312 | return enforcedFn; 313 | }, enforcedFn); 314 | 315 | return enforcedFn; 316 | } 317 | 318 | function enforceOnTree(signatureTree, fn, options) { 319 | var enforcer = buildEnforcer(signatureTree, fn, options); 320 | var enforceDecorator = buildEnforceDecorator(enforcer); 321 | 322 | enforceDecorator.toString = Function.prototype.toString.bind(fn); 323 | 324 | var signedEnforceDecorator = signFn(signatureTree, enforceDecorator); 325 | 326 | return attachProps(fn, signedEnforceDecorator); 327 | } 328 | 329 | function addTypeCheck(typeDef) { 330 | typeDef.typeCheck = typelog.isTypeOf(typeDef); 331 | return typeDef; 332 | } 333 | 334 | function prepareSubtree(subtree) { 335 | var updatedSubtree = subtree.map(addTypeCheck); 336 | updatedSubtree.dependent = subtree.dependent; 337 | return updatedSubtree; 338 | } 339 | 340 | function prepareSignature(signatureTree) { 341 | return signatureTree.map(prepareSubtree); 342 | } 343 | 344 | function enforce(signature, fn, options) { 345 | var signatureTree = prepareSignature(parser.parseSignature(signature)); 346 | var cleanOptions = typeof options === 'object' && options !== null ? options : {}; 347 | return enforceOnTree(signatureTree, fn, cleanOptions); 348 | } 349 | 350 | function attachPreprocessor(typeCheck, preprocessor) { 351 | if (typeof preprocessor === 'function') { 352 | typeCheck.preprocess = preprocessor; 353 | } 354 | } 355 | 356 | var typeArityPattern = /^([^\{]+)\{([^\}]+)\}$/; 357 | 358 | function getArity(typeName, typeStr) { 359 | var arityStr = typeStr.replace(typeArityPattern, '$2'); 360 | var arityData = arityStr.split(/\,\s*/g); 361 | var min = 0; 362 | var max = Infinity; 363 | 364 | if (arityStr !== typeStr) { 365 | min = parseInt(arityData[0]); 366 | max = arityData.length === 1 ? min : parseInt(arityData[1]); 367 | 368 | if (min > max) { 369 | throw new Error('Error in ' + typeName + ' arity declaration: min cannot be greater than max'); 370 | } 371 | 372 | min = isNaN(min) || min < 0 ? 0 : min; 373 | max = isNaN(max) || max < 0 ? Infinity : max; 374 | } 375 | 376 | return [min, max]; 377 | } 378 | 379 | function getTypeName(typeStr) { 380 | return typeStr.replace(typeArityPattern, '$1').trim(); 381 | } 382 | 383 | function checkTypeArity(typeName, arity, options) { 384 | var optionsIsArray = Object.prototype.toString.call(options) === '[object Array]'; 385 | var errorMessage = null; 386 | 387 | if (optionsIsArray && options.length < arity[0]) { 388 | errorMessage = 'Type ' + typeName + ' requires, at least, ' + arity[0] + ' arguments'; 389 | } else if (optionsIsArray && options.length > arity[1]) { 390 | errorMessage = 'Type ' + typeName + ' accepts, at most, ' + arity[1] + ' arguments'; 391 | } 392 | 393 | if (errorMessage !== null) { 394 | throw new Error(errorMessage); 395 | } 396 | } 397 | 398 | function decorateWithArityCheck(typeName, typeArity, typeCheck) { 399 | return function decoratedTypeCheck(value, options) { 400 | checkTypeArity(typeName, typeArity, options); 401 | 402 | return typeCheck(value, options); 403 | } 404 | } 405 | 406 | function extend(typeStr, typeCheck, preprocessor) { 407 | var typeName = getTypeName(typeStr); 408 | var typeArity = getArity(typeName, typeStr); 409 | var decoratedTypeCheck = decorateWithArityCheck(typeName, typeArity, typeCheck); 410 | 411 | attachPreprocessor(decoratedTypeCheck, preprocessor); 412 | typelog.define(typeName, decoratedTypeCheck); 413 | } 414 | 415 | function subtype(parentTypeName) { 416 | var defineSubtype = typelog.defineSubtypeOf(parentTypeName); 417 | 418 | return function (typeStr, typeCheck, preprocessor) { 419 | var typeName = getTypeName(typeStr); 420 | var typeArity = getArity(typeName, typeStr); 421 | var decoratedTypeCheck = decorateWithArityCheck(typeName, typeArity, typeCheck); 422 | 423 | attachPreprocessor(decoratedTypeCheck, preprocessor); 424 | defineSubtype(typeName, decoratedTypeCheck); 425 | }; 426 | } 427 | 428 | var typeApi = coreTypes( 429 | parser, 430 | extend, 431 | isTypeOf, 432 | typelog.isType, 433 | typelog.isSubtypeOf, 434 | subtype, 435 | alias, 436 | typelog.defineDependentOperatorOn); 437 | 438 | var duckTypesModule = duckTypes( 439 | typelog, 440 | isTypeOf, 441 | parser.parseType, 442 | assembler.assembleType); 443 | 444 | var recursiveTypeModule = recursiveTypes(extend, isTypeOf); 445 | 446 | return { 447 | alias: enforce( 448 | 'aliasName != typeString ' + 449 | ':: aliasName:string, ' + 450 | 'typeString:string ' + 451 | '=> undefined', 452 | alias), 453 | buildInputErrorMessage: enforce( 454 | 'validationResult:tuple<' + 455 | 'expectedType:type, ' + 456 | 'actualValue:*' + 457 | '>, ' + 458 | 'args:array<*>, ' + 459 | 'signatureTree:array>, ' + 460 | 'functionName:string ' + 461 | '=> string', 462 | buildInputErrorMessage 463 | ), 464 | buildOutputErrorMessage: enforce( 465 | 'validationResult:tuple<' + 466 | 'expectedType:type, ' + 467 | 'actualValue:*' + 468 | '>, ' + 469 | 'args:array<*>, ' + 470 | 'signatureTree:array>, ' + 471 | 'functionName:string ' + 472 | '=> string', 473 | buildOutputErrorMessage 474 | ), 475 | classTypeFactory: enforce( 476 | 'class:function, ' + 477 | 'otherProps:[composite, object>] ' + 478 | '=> function', 479 | duckTypesModule.classTypeFactory), 480 | defineClassType: enforce( 481 | 'class:function, ' + 482 | 'otherProps:[composite, object>] ' + 483 | '=> undefined', 484 | duckTypesModule.defineClassType), 485 | defineDuckType: enforce( 486 | 'typeName:string, ' + 487 | 'duckTypeDef:object ' + 488 | '=> undefined', 489 | duckTypesModule.defineDuckType), 490 | defineExactDuckType: enforce( 491 | 'typeName:string, ' + 492 | 'duckTypeDef:object ' + 493 | '=> undefined', 494 | duckTypesModule.defineExactDuckType), 495 | defineDependentOperatorOn: enforce( 496 | 'typeName:string => ' + 497 | 'operator:string, operatorCheck:function<' + 498 | 'valueA:*, ' + 499 | 'valueB:*, ' + 500 | 'typeDefinitionA:[object], ' + 501 | 'typeDefinitionB:[object] ' + 502 | '=> boolean' + 503 | '> ' + 504 | '=> undefined', 505 | typelog.defineDependentOperatorOn), 506 | defineRecursiveType: enforce( 507 | 'typeName:string, ' + 508 | 'iteratorFactory:function, ' + 509 | 'nodeType:type, ' + 510 | 'typePreprocessor:[function] ' + 511 | '=> undefined', 512 | recursiveTypeModule.defineRecursiveType), 513 | duckTypeFactory: enforce( 514 | 'duckTypeDef:object => function', 515 | duckTypesModule.duckTypeFactory), 516 | enforce: enforce( 517 | 'signature:string, ' + 518 | 'functionToEnforce:function, ' + 519 | 'options:[object] ' + 520 | '=> function', 521 | enforce), 522 | enforceArguments: enforce( 523 | 'array => arguments => undefined', 524 | enforceArguments), 525 | exactDuckTypeFactory: enforce( 526 | 'duckTypeDef:object => function', 527 | duckTypesModule.exactDuckTypeFactory), 528 | extend: enforce( 529 | 'typeName:string, ' + 530 | 'typeCheck:function, ' + 531 | 'preprocessor:[function string>] ' + 532 | '=> undefined', 533 | extend), 534 | isRegisteredDuckType: enforce( 535 | 'typeName:string ' + 536 | '=> boolean', 537 | duckTypesModule.isRegisteredDuckType), 538 | isSubtypeOf: enforce( 539 | 'rootTypeName:string ' + 540 | '=> typeNameUnderTest:string ' + 541 | '=> boolean', 542 | typelog.isSubtypeOf), 543 | isType: enforce( 544 | 'typeName:string => boolean', 545 | typelog.isType), 546 | isTypeOf: enforce( 547 | 'typeToCheck:type ' + 548 | '=> value:* ' + 549 | '=> boolean', 550 | isTypeOf), 551 | iterateOn: enforce( 552 | 'propertyKey:string ' + 553 | '=> value:* ' + 554 | '=> undefined ' + 555 | '=> *', 556 | recursiveTypeModule.iterateOn 557 | ), 558 | iterateOnArray: enforce( 559 | 'iterationArray:array ' + 560 | '=> undefined ' + 561 | '=> *', 562 | recursiveTypeModule.iterateOnArray 563 | ), 564 | recursiveTypeFactory: enforce( 565 | 'iteratorFactory:function, ' + 566 | 'nodeType:type ' + 567 | '=> valueToCheck:* ' + 568 | '=> boolean', 569 | recursiveTypeModule.recursiveTypeFactory), 570 | registerTypeLevelMacro: enforce( 571 | 'macro:function => undefined', 572 | parser.registerTypeLevelMacro), 573 | reportDuckTypeErrors: enforce( 574 | 'duckTypeName:string ' + 575 | '=> valueToCheck:* ' + 576 | '=> array>', 577 | duckTypesModule.reportDuckTypeErrors), 578 | sign: enforce( 579 | 'signature:string, functionToSign:function => function', 580 | sign), 581 | subtype: enforce( 582 | 'rootTypeName:string ' + 583 | '=> subtypeName:string, ' + 584 | 'subtypeCheck:function, ' + 585 | 'preprocessor:[function string>] ' + 586 | '=> undefined', 587 | subtype), 588 | typeChain: enforce( 589 | 'typeName:string => string', 590 | typelog.getTypeChain), 591 | verify: enforce( 592 | 'signedFunctionToVerify:function, ' + 593 | 'functionArguments:arguments ' + 594 | '=> undefined', 595 | verify), 596 | verifyValueType: enforce( 597 | 'typeToCheck:type ' + 598 | '=> value:* ' + 599 | '=> result:*', 600 | verifyValueType), 601 | whichType: enforce( 602 | 'typeNames:array => ' + 603 | 'value:* ' + 604 | '=> variant', 605 | typeApi.whichType), 606 | whichVariantType: enforce( 607 | 'variantString:string => ' + 608 | 'value:* ' + 609 | '=> variant', 610 | typeApi.whichVariantType) 611 | }; 612 | } 613 | 614 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 615 | module.exports = signetBuilder; 616 | } -------------------------------------------------------------------------------- /client-index.js: -------------------------------------------------------------------------------- 1 | var signet = (function () { 2 | 'use strict'; 3 | 4 | function buildSignet() { 5 | var assembler = signetAssembler; 6 | var parser = signetParser(); 7 | var registrar = signetRegistrar(); 8 | var checker = signetChecker(registrar); 9 | var typelog = signetTypelog(registrar, parser); 10 | var validator = signetValidator(typelog, assembler); 11 | var duckTypes = signetDuckTypes; 12 | var coreTypes = signetCoreTypes; 13 | var recursiveTypes = signetRecursiveTypes; 14 | 15 | return signetBuilder( 16 | typelog, 17 | validator, 18 | checker, 19 | parser, 20 | assembler, 21 | duckTypes, 22 | coreTypes, 23 | recursiveTypes); 24 | } 25 | 26 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 27 | module.exports = buildSignet; 28 | } 29 | 30 | var signetInstance = buildSignet(); 31 | signetInstance.new = buildSignet; 32 | 33 | return signetInstance; 34 | })(); 35 | 36 | -------------------------------------------------------------------------------- /dist/signet.min.js: -------------------------------------------------------------------------------- 1 | /*! signet 05-02-2024 */ 2 | 3 | function signetParser(){"use strict";function a(a){if("string"!=typeof a)throw new Error("Macro Error: All macros must return a string; got "+a+" of type "+typeof a)}function b(b,c){for(var d=c,e=b.length,f=0;f-1?h(a):-1,c=-1===b?null:a.substring(0,b).trim(),d=a.substring(b+1),e=D(d);return e.name=c,e}function q(a){var b=a.trim().split(/\s+/g);return{operator:b[1],left:b[0],right:b[2]}}function r(a){return a.split(/\,\s*/g).map(q)}function s(a){return","===a}function t(a){return"::"===a}function u(a){var b=y(t,a),c=b.length>1?b.shift():null,d=y(s,b[0]).map(p);return d.dependent=null===c?null:r(c),d}function v(){function a(a){"<"===a&&b.push("<"),">"===a&&b.pop(),"::"===a&&(b.length=0)}var b=[];return{update:a,get length(){return b.length}}}function w(a){return"="===a||"%"===a||":"===a}function x(a){return"%"===a[0]||"=>"===a||"::"===a}function y(a,b){for(var c=[],d="",e="",f=v(),g=0;g"===a}function A(a){return y(z,b(C,a)).map(u)}var B=[],C=[],D=o(i);return{parseSignature:o(A),parseType:p,registerSignatureLevelMacro:d,registerTypeLevelMacro:c}}function signetDuckTypes(a,b,c,d){function e(b,c){var d=y(c),e=v(c);a.defineSubtypeOf("object")(b,d),E[b]=e}function f(a){var c=typeof a;return b("array")(a)?"array":c}function g(a,b){return a.reduce(function(a,c){if("constructor"!==c){var d=b[c],e=f(d);a[c]=e}return a},{})}function h(a){return"object"==typeof a.prototype}function i(a){if(!h(a)){var b="Function defineClassType expected a prototypal object or class, but got a value of type "+typeof a;throw new TypeError(b)}}function j(a,c){Object.keys(c).forEach(function(d){if(b("not")(a[d])){var e="Cannot reassign property "+d+" on duck type object";throw new Error(e)}a[d]=c[d]})}function k(a,c){i(a);var d=a.prototype,e=Object.getOwnPropertyNames(d),f=g(e,d);return b("composite, object>")(c)&&j(f,c),f}function l(a,b){e(a.name,k(a,b))}function m(a,b){return z(k(a,b))}function n(a,b){return"function"==typeof E[b]?E[b](a):a}function o(a,b){return"string"==typeof a[b]?a[b]:a[b].name}function p(a,b){for(var e={},f=0;f, variant>");return{buildDuckTypeErrorChecker:v,classTypeFactory:m,defineClassType:l,defineDuckType:e,defineExactDuckType:C,duckTypeFactory:z,exactDuckTypeFactory:B,isRegisteredDuckType:D,reportDuckTypeErrors:A}}function signetCoreTypes(a,b,c,d,e,f,g,h){"use strict";function i(a){return function(b,c){return!a(b,c)}}function j(a,b){for(var c=Object.keys(b),d=c.length,e=!0,f=0;fb}function w(a,b){return a0&&"*"!==b[0];return P(a)&&(!c||O(a,b))}function R(a){return C(a)||S(a)}function S(a){return Math.floor(a)===a&&a!==1/0}function T(a,b){var d=H(b[0]),e=V(b),f=G(d)||F(d);if(E(d)||f)return!!c(b[0])(a)&&U(f?a.length:a,e);throw new Error("Bounded type only accepts types of number, string, array or subtypes of these.")}function U(a,b){return b.min<=a&&a<=b.max}function V(a){return{min:Number(a[1]),max:Number(a[2])}}function W(a){return 1===a.length?a[0]:a.join(";")}function X(a,b){return null!==a.match(b)}function Y(a){return a.map(c)}function Z(a){return a.join(", ")}function $(a){return!z(a)}function _(a){return"[object RegExp]"===Object.prototype.toString.call(a)}function aa(a,b){var c=a===b?1:0;return e(a)(b)?-1:c}function ba(a,b){var c=0,d=0;for(c;c0}function ma(a,b){return console.warn("Tagged Union is deprecated, use variant instead."),la(a,b)}function na(a,b){return b.reduce(function(b,c){return b&&c(a)},!0)}function oa(a,b){return!b[0](a)}function pa(b){return"function"==typeof b||d(a.parseType(b).type)}function qa(a,b){return a.length===b.length}function ra(a,b){return a.length>b.length}function sa(a,b){return a.length, but got: "+c+"of type "+typeof c);return a===ya(a,c)}function Aa(a){return a instanceof Promise}var Ba=void 0===Number.isNaN?function(a){return a!==a}:function(a){return Number.isNaN(a)},Ca=e("nativeNumber"),Da=e("string"),Ea=e("array");a.registerTypeLevelMacro(function(a){return/^\(\s*\)$/.test(a.trim())?"*":a}),a.registerTypeLevelMacro(function(a){var b=ta("(\\!\\*)");return ua(a.trim(),b,"$1$2not>$4")}),a.registerTypeLevelMacro(function(a){var b=ta("\\?([^\\]]*)");return ua(a.trim(),b,"$1$2variant$4")}),a.registerTypeLevelMacro(function(a){var b=ta("\\^([^\\]]*)");return ua(a.trim(),b,"$1$2not<$3>$4")}),a.registerSignatureLevelMacro(function(a){var b=/(\()((.*\=\>)+(.*))(\))/;return b.test(a)?a.replace(b,"function<$2>"):a});var Fa=/enforceDecorator/gi;return b("boolean{0}",y("boolean")),b("function{0,1}",y("function"),Z),b("enforcedFunction{0,1}",xa),b("nativeNumber",D),b("object{0}",y("object")),b("string{0}",y("string")),b("symbol{0}",y("symbol")),b("undefined{0}",y("undefined")),b("not{1}",oa,Y),b("null{0}",z),b("variant{1,}",la,Y),b("taggedUnion{1,}",ma,Y),b("composite{1,}",na,Y),b("bounded{3}",T),b("promise",Aa),f("nativeNumber")("number{0}",B),f("nativeNumber")("bigint{0}",C),f("nativeNumber")("int{0}",R),f("object")("array{0,}",Q),f("object")("regexp{0}",_),f("nativeNumber")("finiteNumber",A),f("number")("decimalPrecision{1}",za),f("finiteNumber")("finiteInt{0}",S),f("string")("formattedString{1}",X,W),f("array")("tuple{1,}",ka,Y),f("array")("unorderedProduct{1,}",ja),f("object")("arguments{0}",$),f("array")("sequence{1}",I),f("array")("monotoneSequence{1}",L),f("array")("increasingSequence{1}",M),f("array")("decreasingSequence{1}",N),g("leftBounded","bounded<_, _, Infinity>"),g("rightBounded","bounded<_, -Infinity, _>"),g("boundedString{2}","bounded"),g("leftBoundedString{1}","leftBounded"),g("rightBoundedString{1}","rightBounded"),g("boundedNumber{2}","bounded"),g("leftBoundedNumber{1}","leftBounded"),g("rightBoundedNumber{1}","rightBounded"),g("boundedFiniteNumber{2}","bounded"),g("leftBoundedFiniteNumber{1}","leftBounded"),g("rightBoundedFiniteNumber{1}","rightBounded"),g("boundedInt{2}","bounded"),g("leftBoundedInt{1}","leftBounded"),g("rightBoundedInt{1}","rightBounded"),g("boundedFiniteInt{2}","bounded"),g("leftBoundedFiniteInt{1}","leftBounded"),g("rightBoundedFiniteInt{1}","rightBounded"),g("typeValue{0}","variant"),f("typeValue")("type{0}",pa),g("any{0}","*"),g("void{0}","*"),h("nativeNumber")(">",v),h("nativeNumber")("<",w),h("nativeNumber")("=",x),h("nativeNumber")(">=",i(w)),h("nativeNumber")("<=",i(v)),h("nativeNumber")("!=",i(x)),h("int")(">",v),h("int")("<",w),h("int")("=",x),h("int")(">=",i(w)),h("int")("<=",i(v)),h("int")("!=",i(x)),h("string")("=",x),h("string")("!=",i(x)),h("string")("#=",qa),h("string")("#<",sa),h("string")("#>",ra),h("array")("#=",qa),h("array")("#<",sa),h("array")("#>",ra),h("object")("=",k),h("object")("!=",i(k)),h("object")(":>",l),h("object")(":<",m),h("object")(":=",n),h("object")(":!=",i(n)),h("variant")("isTypeOf",o),h("variant")("=:",o),h("variant")("<:",t),h("variant")(">:",u),{whichType:s,whichVariantType:q}}function signetRecursiveTypes(a,b){function c(a,b){for(var c=a(),d=!0;d&&null!==c;)d=b(c),c=a();return d}function d(a,d){var e=b(d);return function b(d){var f=a(d);return e(d)&&c(f,b)}}function e(a){return function(b){var c=h(b[a])?b[a].slice(0):[b[a]];return function(){var a=c.shift();return i(a)?null:a}}}function f(a){var b=a.slice(0);return function(){var a=b.shift();return i(a)?null:a}}function g(b,c,e,f){var g=d(c,e);a(b,g,f)}var h=b("array"),i=b("undefined");return{defineRecursiveType:g,iterateOn:e,iterateOnArray:f,recursiveTypeFactory:d}}function signetBuilder(a,b,c,d,e,f,g,h){"use strict";function i(a){return U.test(a)}function j(a,b){return b.reduce(function(a,b){return a.replace(U,"$1"+b+"$3")},a)}function k(b){var c=a.isTypeOf(b);return function(a){return c(a)}}function l(a){return function(b,c){var e=j(a,c);return k(d.parseType(e))(b)}}function m(a,b){S(a,i(b)?l(b):k(d.parseType(b)))}function n(b){return"string"==typeof b?a.isTypeOf(d.parseType(b)):b}function o(a){var b=n(a);return function(c){if(!b(c))throw new TypeError("Expected value of type "+a+", but got "+String(c)+" of type "+typeof c);return c}}function p(a,b,c){return Object.defineProperty(a,b,{value:c,writeable:!1}),a}function q(a,b){return p(a,"signatureTree",b),Object.defineProperty(a,"signature",{writeable:!1,get:function(){return e.assembleSignature(a.signatureTree)}}),a}function r(a,b){var d=c.checkSignature(a),f=a.length-1;if(a.length<2)throw new SyntaxError("Signature must have both input and output types");if(a[0].length1)throw new SyntaxError("Signature can only have a single output type");if(null!==d){var g=d.map(e.assembleType);throw new TypeError("Signature contains invalid types: "+g.join(", "))}}function s(a,b){return q(b,a),p(b,"signatureTree",a)}function t(a,b){var c=d.parseSignature(a);return r(c,b),s(c,b)}function u(a,b,c){var d=a[0],e=a[1],f=typeof e;return c+" expected a "+b+"value of type "+d+" but got "+a[1]+" of type "+f}function v(a){return function(b,c,d,e,f){var g="";throw g="function"==typeof c?c(b,d,e,f):u(b,a,f),new TypeError(g)}}function w(a,b,c,d){return u(a,"",d)}function x(a,b,c,d){return u(a,"return ",d)}function y(a,c,d){var e=b.validateArguments(a.signatureTree[0],d)(c);null!==e&&V(e,null,c,a.signatureTree,A(a))}function z(a){return function(c){var e=Array.prototype.slice.call(c,0),f=a.map(d.parseType),g=b.validateArguments(f,void 0)(e);null!==g&&V(g,null,e,[f],"Called Function")}}function A(a){return""===a.name?"Anonymous":a.name}function B(a,b,c){var d=b.subtype.join(", ").trim();return""!==d?M(d,a,c):a}function C(a,b,c){var d="object"==typeof b?b:{};return"function"===d.type?B(a,d,c):a}function D(a,b,c){var d=[],e=0,f=0;for(e;e2,k=D(f,a[0],d),l=c.apply(this,k),m=1===i.length,n=i[0];n.dependent=a[0].dependent;var o=m?b.validateArguments(n,g)([l]):null;return null!==o&&W(o,d.outputErrorBuilder,k,a,e),i.environment=g,i[0].dependent=a[0].dependent,j?I(i,l,d):l}}function G(a){return function(){var b=E(0,arguments);return a.apply(this,b)}}function H(a,b){return Object.keys(a).reduce(function(b,c){return b[c]=a[c],b},b),b}function I(a,b,c){var d=F(a,b,c),e=G(d);return e.toString=Function.prototype.toString.bind(b),H(b,s(a,e))}function J(b){return b.typeCheck=a.isTypeOf(b),b}function K(a){var b=a.map(J);return b.dependent=a.dependent,b}function L(a){return a.map(K)}function M(a,b,c){return I(L(d.parseSignature(a)),b,"object"==typeof c&&null!==c?c:{})}function N(a,b){"function"==typeof b&&(a.preprocess=b)}function O(a,b){var c=b.replace(X,"$2"),d=c.split(/\,\s*/g),e=0,f=1/0;if(c!==b){if(e=parseInt(d[0]),f=1===d.length?e:parseInt(d[1]),e>f)throw new Error("Error in "+a+" arity declaration: min cannot be greater than max");e=isNaN(e)||e<0?0:e,f=isNaN(f)||f<0?1/0:f}return[e,f]}function P(a){return a.replace(X,"$1").trim()}function Q(a,b,c){var d="[object Array]"===Object.prototype.toString.call(c),e=null;if(d&&c.lengthb[1]&&(e="Type "+a+" accepts, at most, "+b[1]+" arguments"),null!==e)throw new Error(e)}function R(a,b,c){return function(d,e){return Q(a,b,e),c(d,e)}}function S(b,c,d){var e=P(b),f=O(e,b),g=R(e,f,c);N(g,d),a.define(e,g)}function T(b){var c=a.defineSubtypeOf(b);return function(a,b,d){var e=P(a),f=O(e,a),g=R(e,f,b);N(g,d),c(e,g)}}var U=/([<\;\,]\s*)(_)(\s*[>\;\,])/,V=v(""),W=v("return "),X=/^([^\{]+)\{([^\}]+)\}$/,Y=g(d,S,n,a.isType,a.isSubtypeOf,T,m,a.defineDependentOperatorOn),Z=f(a,n,d.parseType,e.assembleType),$=h(S,n);return{alias:M("aliasName != typeString :: aliasName:string, typeString:string => undefined",m),buildInputErrorMessage:M("validationResult:tuple, args:array<*>, signatureTree:array>, functionName:string => string",w),buildOutputErrorMessage:M("validationResult:tuple, args:array<*>, signatureTree:array>, functionName:string => string",x),classTypeFactory:M("class:function, otherProps:[composite, object>] => function",Z.classTypeFactory),defineClassType:M("class:function, otherProps:[composite, object>] => undefined",Z.defineClassType),defineDuckType:M("typeName:string, duckTypeDef:object => undefined",Z.defineDuckType),defineExactDuckType:M("typeName:string, duckTypeDef:object => undefined",Z.defineExactDuckType),defineDependentOperatorOn:M("typeName:string => operator:string, operatorCheck:function boolean> => undefined",a.defineDependentOperatorOn),defineRecursiveType:M("typeName:string, iteratorFactory:function, nodeType:type, typePreprocessor:[function] => undefined",$.defineRecursiveType),duckTypeFactory:M("duckTypeDef:object => function",Z.duckTypeFactory),enforce:M("signature:string, functionToEnforce:function, options:[object] => function",M),enforceArguments:M("array => arguments => undefined",z),exactDuckTypeFactory:M("duckTypeDef:object => function",Z.exactDuckTypeFactory),extend:M("typeName:string, typeCheck:function, preprocessor:[function string>] => undefined",S),isRegisteredDuckType:M("typeName:string => boolean",Z.isRegisteredDuckType),isSubtypeOf:M("rootTypeName:string => typeNameUnderTest:string => boolean",a.isSubtypeOf),isType:M("typeName:string => boolean",a.isType),isTypeOf:M("typeToCheck:type => value:* => boolean",n),iterateOn:M("propertyKey:string => value:* => undefined => *",$.iterateOn),iterateOnArray:M("iterationArray:array => undefined => *",$.iterateOnArray),recursiveTypeFactory:M("iteratorFactory:function, nodeType:type => valueToCheck:* => boolean",$.recursiveTypeFactory),registerTypeLevelMacro:M("macro:function => undefined",d.registerTypeLevelMacro),reportDuckTypeErrors:M("duckTypeName:string => valueToCheck:* => array>",Z.reportDuckTypeErrors),sign:M("signature:string, functionToSign:function => function",t),subtype:M("rootTypeName:string => subtypeName:string, subtypeCheck:function, preprocessor:[function string>] => undefined",T),typeChain:M("typeName:string => string",a.getTypeChain),verify:M("signedFunctionToVerify:function, functionArguments:arguments => undefined",y),verifyValueType:M("typeToCheck:type => value:* => result:*",o),whichType:M("typeNames:array => value:* => variant",Y.whichType),whichVariantType:M("variantString:string => value:* => variant",Y.whichVariantType)}}var signetAssembler=function(){"use strict";function a(a){return a.subtype&&a.subtype.length>0}function b(b){return a(b)?"<"+b.subtype.join(";")+">":""}function c(a,b){return"string"==typeof a?a+":"+b:b}function d(a,b){return b?"["+a+"]":a}function e(a){return d(a.type+b(a),a.optional)}function f(a){var b=e(a);return c(a.name,b)}function g(a,b){return(""!==a?a+", ":a)+[b.left,b.operator,b.right].join(" ")}function h(a){return a.reduce(g,"")+" :: "}function i(a){var b=a.map(f).join(", ");return(null===a.dependent?"":h(a.dependent))+b}function j(a){return a.map(i).join(" => ")}return{assembleSignature:j,assembleType:f}}();"undefined"!=typeof module&&void 0!==module.exports&&(module.exports=signetAssembler);var signetChecker=function(){"use strict";return function(a){function b(b){try{return"function"==typeof a.get(b.type)}catch(a){return!1}}function c(a,b){return a.concat(b)}function d(a){return function(b){return!a(b)}}function e(a){var e=a.reduce(c,[]).filter(d(b));return e.length>0?e:null}return{checkSignature:e,checkType:b}}}();"undefined"!=typeof module&&void 0!==module.exports&&(module.exports=signetChecker),"undefined"!=typeof module&&void 0!==module.exports&&(module.exports=signetParser);var signetRegistrar=function(){"use strict";return function(){function a(a,b){return typeof b===a}function b(b){return a("string",b)&&null!==b.match(/^[^\(\)\<\>\[\]\:\;\=\,\s]+$/)}function c(c,d){if(!b(c))throw new Error("Invalid type name: "+c);if(!a("undefined",f[c]))throw new Error("Type already registered with name "+c);if(!a("function",d))throw new Error("Type predicate parameter must be a function")}function d(a){var b=f[a];if(void 0===b)throw new Error('The given type "'+a+'" does not exist');return b}function e(a,b){c(a,b),f[a]=b}var f={};return{get:d,set:e}}}();"undefined"!=typeof module&&void 0!==module.exports&&(module.exports=signetRegistrar);var signetTypelog=function(a){"use strict";function b(a){return function(b){return a.optional&&void 0===b}}function c(a){var c=b(a),d=a.predicate;return function(b){return d(b,a.subtype)||c(b)}}function d(a,b,c){Object.defineProperty(a,b,{value:c,writeable:!1})}function e(a,b){return function(c){return a===c||b(c)}}function f(a,b){return function(c,d){return a(c,[])&&b(c,d)}}function g(a,b){return Object.keys(b).reduce(function(a,c){return a[c]=b[c],a},a)}function h(){return!1}function i(a){return"function"==typeof a.isSubtypeOf?a.isSubtypeOf:h}function j(b){var c=a.get(b),h=i(c);return function(i,j){var k=f(c,j),l=e(b,h);g(k,j),d(k,"parentTypeName",b),d(k,"isSubtypeOf",l),a.set(i,k)}}function k(b){try{return"function"==typeof a.get(b)}catch(a){return!1}}function l(b){return m(function(c){return(0,a.get(c).isSubtypeOf)(b)})}function m(a){var b={};return function(c){var d=JSON.stringify(c);return void 0===b[d]&&(b[d]=a(c)),b[d]}}function n(a){var b=p(a);return function(a){return c(b)(a)}}function o(a){return a}function p(b){var c=a.get(b.type),d="function"==typeof c.preprocess?c.preprocess:o,e=g({},b);return e.subtype=d(b.subtype),e.predicate=c,e}function q(b){var c=a.get(b);return void 0!==c.parentTypeName?q(c.parentTypeName)+" -> "+b:b}function r(b){var c=a.get(b);return function(a,b){d(c,a,{operator:a,operation:b})}}function s(b){return function(c){var d=a.get(b);return"object"==typeof d[c]?d[c]:"*"==b?null:s(d.parentTypeName)(c)}}a.set("*",function(){return!0});var t=m(l),u=m(n);return{define:j("*"),defineDependentOperatorOn:r,defineSubtypeOf:j,getDependentOperatorOn:s,getTypeChain:q,isType:k,isTypeOf:u,isSubtypeOf:t}};"undefined"!=typeof module&&void 0!==module.exports&&(module.exports=signetTypelog);var signetValidator=function(){"use strict";function a(a){return a[0]}function b(a){return a.slice(1)}return function(c,d,e){function f(a,b,c){return a.optional&&(c.length>1||void 0===b)}function g(a){return"function"==typeof a.typeCheck?a.typeCheck:c.isTypeOf(a)}function h(c,e){var h=a(c),i=a(e),j=g(h)(i),k=j?b(e):e,l=q(b(c));return j||f(h,i,c)?l(k):[d.assembleType(h),i]}function i(a,b,c,d){var e=null;if(!c.operation(a.value,b.value,a.typeNode,b.typeNode,d)){e=[[a.name,c.operator,b.name].join(" "),[a.name,"=",a.value,"and",b.name,"=",b.value].join(" ")]}return e}function j(){return!1}function k(a,b){var d=c.getDependentOperatorOn(a)(b);return null===d&&(d={operator:b,operation:j}),d}function l(a){function b(a){return f(a)}var d=e.parseType(a),f=c.isTypeOf(d);return b.toString=function(){return"[function typePredicate]"},{name:a,value:b,typeNode:d}}function m(a,b){var d=a[b];return void 0===d&&c.isType(b)&&(d=l(b)),d}function n(a){return function(b,c){b.leftTokens=b.left.split(":"),b.rightTokens=b.right.split(":");var d=b.leftTokens[0],e=a[d],f=b.rightTokens[0],g=m(a,f),h=null,j=void 0!==e&&void 0!==g;if(null===c&&j){h=i(e,g,k(e.typeNode.type,b.operator),b)}return null===h?c:h}}function o(a,b,c){for(var d,e,f=void 0===c?{}:c,g=a.length,h=0;h token !== className) 9 | .join(' '); 10 | } 11 | 12 | function addClassName(className, element) { 13 | element.className = element.className + ' ' + className; 14 | } 15 | 16 | function hasClassName(className, element) { 17 | return element.className.split(' ').indexOf(className) > -1; 18 | } 19 | 20 | function updateView(element) { 21 | if (!hasClassName('hidden', element)) { 22 | addClassName('hidden', element); 23 | removeClassName('shown', element); 24 | } else { 25 | addClassName('shown', element); 26 | removeClassName('hidden', element); 27 | } 28 | } 29 | 30 | 31 | function buildCollapseTextUpdater(displayMessage, hideMessage) { 32 | return function updateCollapseText(linkElement, codeElement) { 33 | if (hasClassName('hidden', codeElement)) { 34 | linkElement.innerText = displayMessage; 35 | } else { 36 | linkElement.innerText = hideMessage; 37 | } 38 | } 39 | } 40 | 41 | const updateCodeSampleText = buildCollapseTextUpdater('Show Code Sample', 'Hide Code Sample'); 42 | const updateDescribeText = buildCollapseTextUpdater('Show Info', 'Hide Info'); 43 | 44 | const codeSampleElements = document.getElementsByClassName('code-sample-wrapper'); 45 | const codeSampleElementArray = Array.prototype.slice.call(codeSampleElements); 46 | 47 | codeSampleElementArray.forEach(function (element) { 48 | const collapseLink = element 49 | .getElementsByClassName('code-expand')[0] 50 | .getElementsByTagName('a')[0]; 51 | 52 | const codeContainer = element 53 | .getElementsByTagName('pre')[0]; 54 | 55 | updateView(codeContainer); 56 | updateCodeSampleText(collapseLink, codeContainer); 57 | 58 | collapseLink.addEventListener('click', function (event) { 59 | event.preventDefault(); 60 | event.stopPropagation(); 61 | 62 | updateView(codeContainer); 63 | updateCodeSampleText(collapseLink, codeContainer); 64 | }); 65 | }); 66 | 67 | const describeElements = document.getElementsByClassName('describe-item'); 68 | const describeElementArray = Array.prototype.slice.call(describeElements); 69 | 70 | describeElementArray.forEach(function (element) { 71 | const collapseLink = element 72 | .getElementsByClassName('describe-link')[0]; 73 | 74 | const contentContainer = element 75 | .getElementsByClassName('describe-collapsible')[0]; 76 | 77 | updateDescribeText(collapseLink, contentContainer); 78 | 79 | collapseLink.addEventListener('click', function (event) { 80 | event.preventDefault(); 81 | event.stopPropagation(); 82 | 83 | updateView(contentContainer); 84 | updateDescribeText(collapseLink, contentContainer); 85 | }); 86 | }); 87 | 88 | const collapseAll = document.getElementsByClassName('collapse-all')[0]; 89 | 90 | if (typeof collapseAll !== 'undefined') { 91 | collapseAll.addEventListener('click', function (event) { 92 | event.preventDefault(); 93 | event.stopPropagation(); 94 | 95 | const openElements = document.getElementsByClassName('shown'); 96 | const openElementArray = Array.prototype.slice.call(openElements); 97 | 98 | openElementArray.forEach(function (element) { 99 | removeClassName('shown', element); 100 | addClassName('hidden', element); 101 | }); 102 | 103 | codeSampleElementArray.forEach(function (element) { 104 | const displayLink = element 105 | .getElementsByClassName('code-expand')[0] 106 | .getElementsByTagName('a')[0]; 107 | 108 | const codeContainer = element 109 | .getElementsByTagName('pre')[0]; 110 | 111 | updateCodeSampleText(displayLink, codeContainer); 112 | }); 113 | 114 | describeElementArray.forEach(function (element) { 115 | const describeLink = element 116 | .getElementsByClassName('describe-link')[0]; 117 | 118 | const contentContainer = element 119 | .getElementsByClassName('describe-collapsible')[0]; 120 | 121 | updateDescribeText(describeLink, contentContainer); 122 | }); 123 | }); 124 | } 125 | 126 | })(); 127 | -------------------------------------------------------------------------------- /docs/api/assets/doc-style.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | 50 | /********** End reset code **********/ 51 | 52 | body { 53 | font-family: Helvetica, Arial, sans-serif; 54 | font-size: 18px; 55 | } 56 | 57 | h1 { 58 | font-weight: bold; 59 | font-size: 2.2rem; 60 | line-height: 3.2rem; 61 | margin: 0.8rem 0 0.8rem 0; 62 | border-bottom: 1px solid #ccc; 63 | } 64 | 65 | h3 { 66 | font-weight: bold; 67 | font-size: 1.2rem; 68 | } 69 | 70 | ul { 71 | margin: 1rem 0 1rem 0; 72 | } 73 | 74 | li { 75 | margin-left: 15px; 76 | line-height: 1.8rem; 77 | } 78 | 79 | pre, code { 80 | font-family: "Courier New", Courier, monospace; 81 | padding: 0 !important; 82 | margin: 0 !important; 83 | } 84 | 85 | strong { 86 | font-weight: bold; 87 | } 88 | 89 | .content { 90 | padding: 30px; 91 | } 92 | 93 | .filename { 94 | font-family: "Courier New", Courier, monospace; 95 | } 96 | 97 | header { 98 | display: block; 99 | margin: 0; 100 | padding: 15px; 101 | background-color: #662b02; 102 | color: #fff; 103 | } 104 | 105 | header > a { 106 | text-decoration: none; 107 | } 108 | 109 | header > a > span { 110 | display: inline-block; 111 | text-decoration: none; 112 | color: #fff; 113 | } 114 | 115 | header > a:hover > #library-name { 116 | text-decoration: underline; 117 | } 118 | 119 | 120 | header > a > #library-name { 121 | font-size: 1.8rem; 122 | padding-right: 10px; 123 | margin-right: 10px; 124 | border-right: 1px solid #fff; 125 | } 126 | 127 | header > a > #subtitle { 128 | font-size: 1.2rem; 129 | } 130 | 131 | .hidden { 132 | display: none; 133 | } 134 | 135 | .collapse-link { 136 | font-size: 12px; 137 | } -------------------------------------------------------------------------------- /docs/api/assets/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-meta { 16 | color: #969896; 17 | } 18 | 19 | .hljs-string, 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-strong, 23 | .hljs-emphasis, 24 | .hljs-quote { 25 | color: #df5000; 26 | } 27 | 28 | .hljs-keyword, 29 | .hljs-selector-tag, 30 | .hljs-type { 31 | color: #a71d5d; 32 | } 33 | 34 | .hljs-literal, 35 | .hljs-symbol, 36 | .hljs-bullet, 37 | .hljs-attribute { 38 | color: #0086b3; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name { 43 | color: #63a35c; 44 | } 45 | 46 | .hljs-tag { 47 | color: #333333; 48 | } 49 | 50 | .hljs-title, 51 | .hljs-attr, 52 | .hljs-selector-id, 53 | .hljs-selector-class, 54 | .hljs-selector-attr, 55 | .hljs-selector-pseudo { 56 | color: #795da3; 57 | } 58 | 59 | .hljs-addition { 60 | color: #55a532; 61 | background-color: #eaffea; 62 | } 63 | 64 | .hljs-deletion { 65 | color: #bd2c00; 66 | background-color: #ffecec; 67 | } 68 | 69 | .hljs-link { 70 | text-decoration: underline; 71 | } 72 | -------------------------------------------------------------------------------- /docs/api/assets/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"};return{aliases:["ts"],k:r,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:["self",e.CLCM,e.CBCM]}]}]}],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/%/,r:0},{bK:"constructor",e:/\{/,eE:!0,c:["self",{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}]},{b:/module\./,k:{built_in:"module"},r:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}}); -------------------------------------------------------------------------------- /docs/api/details/SignetMacros.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Signet -- Mochadoc Test Documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Signet 13 | Mochadoc-Generated Test Documents 14 |
15 | 16 |
17 |

Signet Macros

18 |
19 | Collapse All 20 |
21 |
    22 |
  • 23 |

    type-level macros

    24 |
    25 |
      26 |
        27 |
      • 28 | should support option type macro 29 |
      • 30 |
      • File location: ./test/macros.test.js
      • 31 |
      • 32 |
        33 | 34 |
        35 |
         36 |             
         37 |         function () {
         38 |             assert.equal(signet.isTypeOf('?string')(undefined), true);
         39 |             assert.equal(signet.isTypeOf('?string')('foo'), true);
         40 |             assert.equal(signet.isTypeOf('?string')({}), false);
         41 |         }
         42 |             
         43 |         
        44 |
      • 45 |
      46 |
        47 |
      • 48 | should support defined type macro 49 |
      • 50 |
      • File location: ./test/macros.test.js
      • 51 |
      • 52 |
        53 | 54 |
        55 |
         56 |             
         57 |         function () {
         58 |             assert.equal(signet.isTypeOf('!*')(undefined), false);
         59 |             assert.equal(signet.isTypeOf('!*')(null), false);
         60 |             assert.equal(signet.isTypeOf('!*')({}), true);
         61 |         }
         62 |             
         63 |         
        64 |
      • 65 |
      66 |
        67 |
      • 68 | should support an empty parentheses "any" type macro 69 |
      • 70 |
      • File location: ./test/macros.test.js
      • 71 |
      • 72 |
        73 | 74 |
        75 |
         76 |             
         77 |         function () {
         78 |             assert.equal(signet.isTypeOf('()')('foo'), true);
         79 |             assert.doesNotThrow(signet.enforce('() => undefined', function () { }));
         80 |         }
         81 |             
         82 |         
        83 |
      • 84 |
      85 |
        86 |
      • 87 | should support a not macro 88 |
      • 89 |
      • File location: ./test/macros.test.js
      • 90 |
      • 91 |
        92 | 93 |
        94 |
         95 |             
         96 |         function () {
         97 |             assert.equal(signet.isTypeOf('^string')('foo'), false);
         98 |             assert.equal(signet.isTypeOf('^string')(null), true);
         99 |         }
        100 |             
        101 |         
        102 |
      • 103 |
      104 |
    105 |
  • 106 |
  • 107 |

    Signature-level Macros

    108 |
    109 |
      110 |
        111 |
      • 112 | should parse function definition with nested function definition 113 |
      • 114 |
      • File location: ./test/macros.test.js
      • 115 |
      • 116 |
        117 | 118 |
        119 |
        120 |             
        121 |         function () {
        122 |             var doStuff = signet.sign('(* => boolean) => array', () => []);
        123 | 
        124 |             assert.equal(doStuff.signature, 'function<* => boolean> => array');
        125 |         }
        126 |             
        127 |         
        128 |
      • 129 |
      130 |
    131 |
  • 132 |
  • 133 |

    Macro handling

    134 |
    135 |
      136 |
        137 |
      • 138 | should properly sign a function using macros 139 |
      • 140 |
      • File location: ./test/macros.test.js
      • 141 |
      • 142 |
        143 | 144 |
        145 |
        146 |             
        147 |         function () {
        148 |             var expectedValue = 'something:[not<variant<undefined, null>>], somethingElse:[variant<undefined;null;string>], aFunction:function<* => *> => null';
        149 |             var testFn = signet.enforce(
        150 |                 'something:[!*], somethingElse:[?string], aFunction:(* => *) => null',
        151 |                 () => null
        152 |             );
        153 | 
        154 |             assert.equal(testFn.signature, expectedValue);
        155 |         }
        156 |             
        157 |         
        158 |
      • 159 |
      160 |
    161 |
  • 162 |
163 |
164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/api/details/SignetTypes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Signet -- Mochadoc Test Documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Signet 13 | Mochadoc-Generated Test Documents 14 |
15 | 16 |
17 |

Signet Types

18 |
19 | Collapse All 20 |
21 |
    22 |
  • 23 |

    Preregistered Types

    24 |
    25 |
      26 |
        27 |
      • 28 | should automatically register the * type 29 |
      • 30 |
      • File location: ./test/types.test.js
      • 31 |
      • 32 |
        33 | 34 |
        35 |
         36 |             
         37 |         function () {
         38 |             assert.equal(signet.isTypeOf('*')('foo'), true);
         39 |         }
         40 |             
         41 |         
        42 |
      • 43 |
      44 |
        45 |
      • 46 | should have all core JS types preregistered 47 |
      • 48 |
      • File location: ./test/types.test.js
      • 49 |
      • 50 |
        51 | 52 |
        53 |
         54 |             
         55 |         function () {
         56 |             assert.equal(signet.isTypeOf('boolean')(false), true);
         57 |             assert.equal(signet.isTypeOf('function')(addBuilder()), true);
         58 |             assert.equal(signet.isTypeOf('number')(17), true);
         59 |             assert.equal(signet.isTypeOf('object')({}), true);
         60 |             assert.equal(signet.isTypeOf('string')('foo'), true);
         61 |             assert.equal(signet.isTypeOf('symbol')(Symbol()), true);
         62 |             assert.equal(signet.isTypeOf('undefined')(undefined), true);
         63 |         }
         64 |             
         65 |         
        66 |
      • 67 |
      68 |
        69 |
      • 70 | should have common subtypes preregistered 71 |
      • 72 |
      • File location: ./test/types.test.js
      • 73 |
      • 74 |
        75 | 76 |
        77 |
         78 |             
         79 |         function () {
         80 |             assert.equal(signet.isTypeOf('null')(null), true);
         81 |             assert.equal(signet.isTypeOf('array')([]), true);
         82 |             assert.equal(signet.isTypeOf('array<*>')([1, 2, 'foo']), true);
         83 |             assert.equal(signet.isTypeOf('array<int>')([1, 2, 'foo']), false);
         84 | 
         85 |             assert.equal(signet.isTypeOf('int')(5), true);
         86 |             assert.equal(signet.isTypeOf('int')(5.3), false);
         87 |         }
         88 |             
         89 |         
        90 |
      • 91 |
      92 |
        93 |
      • 94 | should preregister algebraic types 95 |
      • 96 |
      • File location: ./test/types.test.js
      • 97 |
      • 98 |
        99 | 100 |
        101 |
        102 |             
        103 |         function () {
        104 |             assert.equal(signet.isTypeOf('tuple<int; formattedString<^\\d+(\\%;)?\\D*$>; boolean>')([123, '1234;foo', false]), true);
        105 |             assert.equal(signet.isTypeOf('tuple<int; formattedString<^\\d+(\\%;)?\\D*$>; boolean>')([123, '1234;33', false]), false);
        106 |             assert.equal(signet.isTypeOf('tuple<int; formattedString<^\\d+(\\%;)?\\D*$>; boolean>')([123, '1234;foo', false, 'hooray!']), false);
        107 | 
        108 |             assert.equal(signet.isTypeOf('variant<int; string>')(10), true);
        109 |             assert.equal(signet.isTypeOf('variant<int; string>')('I am a string'), true);
        110 |             assert.equal(signet.isTypeOf('variant<int; string>')(null), false);
        111 | 
        112 |             assert.equal(signet.isTypeOf('taggedUnion<int; string>')(null), false);
        113 |         }
        114 |             
        115 |         
        116 |
      • 117 |
      118 |
        119 |
      • 120 | should support an unordered product type 121 |
      • 122 |
      • File location: ./test/types.test.js
      • 123 |
      • 124 |
        125 | 126 |
        127 |
        128 |             
        129 |         function () {
        130 |             var isUnorderedProduct = signet.isTypeOf('unorderedProduct<number; int; object; array; string>');
        131 | 
        132 |             assert.equal(isUnorderedProduct([1, 2, 3, 4]), false); //too short
        133 |             assert.equal(isUnorderedProduct([1, 2, 3, 4, 5, 6]), false); //too long
        134 |             assert.equal(isUnorderedProduct([2.5, 'foo', {}, 1.7, []]), false); //bad type
        135 |             assert.equal(isUnorderedProduct([1, 2.5, 'foo', [], {}]), true);
        136 |             assert.equal(isUnorderedProduct([2.5, 'foo', {}, 1, []]), true);
        137 |         }
        138 |             
        139 |         
        140 |
      • 141 |
      142 |
        143 |
      • 144 | should have a not operator to describe exclusive types 145 |
      • 146 |
      • File location: ./test/types.test.js
      • 147 |
      • 148 |
        149 | 150 |
        151 |
        152 |             
        153 |         function () {
        154 |             assert.equal(signet.isTypeOf('not<null>')('foo'), true);
        155 |             assert.equal(signet.isTypeOf('not<null>')(null), false);
        156 |         }
        157 |             
        158 |         
        159 |
      • 160 |
      161 |
        162 |
      • 163 | should support a composition operator 164 |
      • 165 |
      • File location: ./test/types.test.js
      • 166 |
      • 167 |
        168 | 169 |
        170 |
        171 |             
        172 |         function () {
        173 |             assert.equal(signet.isTypeOf('composite<not<null>, object>')({}), true);
        174 |             assert.equal(signet.isTypeOf('composite<not<null>, object>')(undefined), false);
        175 |         }
        176 |             
        177 |         
        178 |
      • 179 |
      180 |
        181 |
      • 182 | should verify type types against existing known types 183 |
      • 184 |
      • File location: ./test/types.test.js
      • 185 |
      • 186 |
        187 | 188 |
        189 |
        190 |             
        191 |         function () {
        192 |             assert.equal(signet.isTypeOf('type')(function () { }), true);
        193 |             assert.equal(signet.isTypeOf('type')('variant'), true);
        194 |             assert.equal(signet.isTypeOf('type')('badType'), false);
        195 |         }
        196 |             
        197 |         
        198 |
      • 199 |
      200 |
        201 |
      • 202 | should preregister sequence types 203 |
      • 204 |
      • File location: ./test/types.test.js
      • 205 |
      • 206 |
        207 | 208 |
        209 |
        210 |             
        211 |         function () {
        212 |             assert.equal(signet.isTypeOf('sequence<int>')([1, 2, 3, 4]), true);
        213 |             assert.equal(signet.isTypeOf('sequence<int>')([1, 2, 3.5, 4]), false);
        214 |             assert.throws(signet.isTypeOf('sequence<boolean>').bind(null, []), 'A sequence may only be comprised of numbers, strings or their subtypes.');
        215 | 
        216 |             assert.equal(signet.isTypeOf('monotoneSequence<number>')([1, 2.5, 3, 4.7]), true);
        217 |             assert.equal(signet.isTypeOf('monotoneSequence<string>')(['d', 'c', 'b', 'a']), true);
        218 |             assert.equal(signet.isTypeOf('monotoneSequence<int>')([1]), true);
        219 |             assert.equal(signet.isTypeOf('monotoneSequence<int>')([1, 2, -1, 5]), false);
        220 | 
        221 |             assert.equal(signet.isTypeOf('increasingSequence<number>')([1, 2.5, 3, 4.7]), true, 'Not an increasing sequence of int');
        222 |             assert.equal(signet.isTypeOf('increasingSequence<string>')(['d', 'c', 'b', 'a']), false, 'Is an increasing sequence of string');
        223 |             assert.equal(signet.isTypeOf('increasingSequence<int>')([1]), true, 'Not an increasing sequence of one value');
        224 |             assert.equal(signet.isTypeOf('increasingSequence<int>')([1, 2, -1, 5]), false, 'Is an increasing sequence of int with a negative');
        225 | 
        226 |             assert.equal(signet.isTypeOf('decreasingSequence<number>')([1, 2.5, 3, 4.7]), false, 'Not an increasing sequence of int');
        227 |             assert.equal(signet.isTypeOf('decreasingSequence<string>')(['d', 'c', 'b', 'a']), true, 'Is an increasing sequence of string');
        228 |             assert.equal(signet.isTypeOf('decreasingSequence<int>')([1]), true, 'Not an increasing sequence of one value');
        229 |             assert.equal(signet.isTypeOf('decreasingSequence<int>')([1, 2, -1, 5]), false, 'Is an increasing sequence of int with a negative');
        230 |         }
        231 |             
        232 |         
        233 |
      • 234 |
      235 |
        236 |
      • 237 | should have registered bounded types 238 |
      • 239 |
      • File location: ./test/types.test.js
      • 240 |
      • 241 |
        242 | 243 |
        244 |
        245 |             
        246 |         function () {
        247 |             assert.equal(signet.isTypeOf('bounded<int, 1, 5>')(3), true);
        248 |             assert.equal(signet.isTypeOf('bounded<number, 1, 5>')(5.1), false);
        249 |             assert.equal(signet.isTypeOf('bounded<int, 1, 5>')(0), false);
        250 | 
        251 |             assert.equal(signet.isTypeOf('leftBounded<number, 0>')(0), true);
        252 |             assert.equal(signet.isTypeOf('leftBounded<number, 0>')(1), true);
        253 |             assert.equal(signet.isTypeOf('leftBounded<number, 0>')(-1), false);
        254 | 
        255 |             assert.equal(signet.isTypeOf('rightBounded<number, 0>')(0), true);
        256 |             assert.equal(signet.isTypeOf('rightBounded<number, 0>')(-1), true);
        257 |             assert.equal(signet.isTypeOf('rightBounded<number, 0>')(1), false);
        258 | 
        259 |             assert.equal(signet.isTypeOf('rightBounded<int, 5>')(1.3), false);
        260 | 
        261 |             assert.equal(signet.isTypeOf('boundedInt<1; 5>')(3), true);
        262 |             assert.equal(signet.isTypeOf('boundedInt<1; 5>')(3.1), false);
        263 |             assert.equal(signet.isTypeOf('boundedInt<1; 5>')(6), false);
        264 |             assert.equal(signet.isTypeOf('boundedInt<1; 5>')(0), false);
        265 | 
        266 |             assert.equal(signet.isTypeOf('leftBoundedInt<0>')(0), true);
        267 |             assert.equal(signet.isTypeOf('leftBoundedInt<0>')(1), true);
        268 |             assert.equal(signet.isTypeOf('leftBoundedInt<0>')(-1), false);
        269 |             assert.equal(signet.isTypeOf('leftBoundedInt<0>')(), false);
        270 | 
        271 |             assert.equal(signet.isTypeOf('rightBoundedInt<0>')(0), true);
        272 |             assert.equal(signet.isTypeOf('rightBoundedInt<0>')(-1), true);
        273 |             assert.equal(signet.isTypeOf('rightBoundedInt<0>')(1), false);
        274 | 
        275 |             assert.equal(signet.isTypeOf('boundedString<2; 15>')('hello'), true);
        276 |             assert.equal(signet.isTypeOf('boundedString<2; 15>')(''), false);
        277 |             assert.equal(signet.isTypeOf('boundedString<2; 15>')('this is a long string which should fail'), false);
        278 |         }
        279 |             
        280 |         
        281 |
      • 282 |
      283 |
        284 |
      • 285 | should support formatted strings 286 |
      • 287 |
      • File location: ./test/types.test.js
      • 288 |
      • 289 |
        290 | 291 |
        292 |
        293 |             
        294 |         function () {
        295 |             assert.equal(signet.isTypeOf('formattedString<^\\d+(\\%;)?\\d*$>')('123;45'), true);
        296 |             assert.equal(signet.isTypeOf('formattedString<^\\d+(\\%;)?\\d*$>')('Not numbers'), false);
        297 |             assert.doesNotThrow(signet.isTypeOf.bind(null, 'formattedString<:>'))
        298 |         }
        299 |             
        300 |         
        301 |
      • 302 |
      303 |
        304 |
      • 305 | should verify enforced functions 306 |
      • 307 |
      • File location: ./test/types.test.js
      • 308 |
      • 309 |
        310 | 311 |
        312 |
        313 |             
        314 |         function () {
        315 |             const goodEnforcedFunction = signet.enforce('* => *', () => null);
        316 |             const badEnforcedFunction = signet.enforce('* => null', () => null);
        317 | 
        318 |             assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(goodEnforcedFunction), true);
        319 |             assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(badEnforcedFunction), false);
        320 |             assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(() => null), false);
        321 |         }
        322 |             
        323 |         
        324 |
      • 325 |
      326 |
        327 |
      • 328 | should pre-register signet type aliases 329 |
      • 330 |
      • File location: ./test/types.test.js
      • 331 |
      • 332 |
        333 | 334 |
        335 |
        336 |             
        337 |         function () {
        338 |             assert.equal(signet.isTypeOf('void')(undefined), true);
        339 |             assert.equal(signet.isTypeOf('any')('anything'), true);
        340 |         }
        341 |             
        342 |         
        343 |
      • 344 |
      345 |
    346 |
  • 347 |
348 |
349 | 350 | 351 | 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Signet -- Mochadoc Test Documentation 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Signet 13 | Mochadoc-Generated Test Documents 14 |
15 | 16 |
17 |

Signet

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /grunt/concat.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "separator": "\n\n" 4 | }, 5 | "dist": { 6 | "src": [ 7 | "node_modules/signet-assembler/index.js", 8 | "node_modules/signet-checker/index.js", 9 | "node_modules/signet-parser/index.js", 10 | "node_modules/signet-registrar/index.js", 11 | "node_modules/signet-typelog/index.js", 12 | "node_modules/signet-validator/index.js", 13 | "bin/duckTypes.js", 14 | "bin/coreTypes.js", 15 | "bin/recursiveTypes.js", 16 | "bin/signet.js", 17 | "client-index.js" 18 | ], 19 | "dest": "dist/signet.js" 20 | } 21 | } -------------------------------------------------------------------------------- /grunt/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": ["./bin/*.js"] 3 | } -------------------------------------------------------------------------------- /grunt/mocha-test.js: -------------------------------------------------------------------------------- 1 | var fork = require('child_process').fork; 2 | var path = require('path'); 3 | 4 | module.exports = function () { 5 | var done = this.async(); 6 | var cwd = process.cwd(); 7 | 8 | var options = { 9 | cwd: cwd 10 | }; 11 | 12 | var testCommand = [cwd, 'node_modules', 'mocha', 'bin', 'mocha'].join(path.sep); 13 | var testFiles = ['./test/*.test.js']; 14 | 15 | 16 | fork(testCommand, testFiles, options) 17 | .on('exit', function (err, stdout, stderr) { 18 | done(err); 19 | }); 20 | 21 | }; -------------------------------------------------------------------------------- /grunt/uglify.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist": { 3 | "options": { 4 | "banner": "/*! <%= pkg.name %> <%= grunt.template.today('dd-mm-yyyy') %> */\n", 5 | "compress": true, 6 | "mangle": true, 7 | "sourceMap": true, 8 | "sourceMapIncludeSources": false 9 | }, 10 | "files": { 11 | "dist/signet.min.js": ["./dist/signet.js"] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assembler = require('signet-assembler'); 4 | var checkerBuilder = require('signet-checker'); 5 | var signetParser = require('signet-parser'); 6 | var registrarBuilder = require('signet-registrar'); 7 | var typelogBuilder = require('signet-typelog'); 8 | var validatorBuilder = require('signet-validator'); 9 | var signetBuilder = require('./bin/signet'); 10 | var duckTypes = require('./bin/duckTypes'); 11 | var coreTypes = require('./bin/coreTypes'); 12 | var recursiveTypes = require('./bin/recursiveTypes'); 13 | 14 | module.exports = function () { 15 | 16 | var parser = signetParser(); 17 | var registrar = registrarBuilder(); 18 | var checker = checkerBuilder(registrar); 19 | var typelog = typelogBuilder(registrar, parser); 20 | var validator = validatorBuilder(typelog, assembler); 21 | 22 | return signetBuilder( 23 | typelog, 24 | validator, 25 | checker, 26 | parser, 27 | assembler, 28 | duckTypes, 29 | coreTypes, 30 | recursiveTypes); 31 | 32 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signet", 3 | "version": "7.1.2", 4 | "description": "Signet type library", 5 | "main": "index.js", 6 | "types": "./signet.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/signetjs/signet.git" 10 | }, 11 | "scripts": { 12 | "test": "grunt test" 13 | }, 14 | "quokka": { 15 | "plugins": [ 16 | "quokka-mocha-bdd", 17 | "quokka-signet-explorer", 18 | "quokka-prerun" 19 | ], 20 | "quokka-mocha-bdd": { 21 | "interface": "bdd" 22 | }, 23 | "quokka-prerun": { 24 | "prerunCommands": [ 25 | "grunt build" 26 | ] 27 | } 28 | }, 29 | "keywords": [ 30 | "signet", 31 | "types", 32 | "signatures", 33 | "type", 34 | "checking" 35 | ], 36 | "author": "Chris Stead (http://www.chrisstead.com/)", 37 | "license": "MPL-2.0", 38 | "dependencies": { 39 | "signet-assembler": "2.1.0", 40 | "signet-checker": "1.1.0", 41 | "signet-parser": "^5.5.0", 42 | "signet-registrar": "2.0.0", 43 | "signet-typelog": "^1.5.1", 44 | "signet-validator": "^3.1.0" 45 | }, 46 | "devDependencies": { 47 | "chai": "^3.5.0", 48 | "eslint": "^3.19.0", 49 | "grunt": "^1.0.1", 50 | "grunt-contrib-concat": "^1.0.1", 51 | "grunt-contrib-uglify": "^2.0.0", 52 | "grunt-eslint": "^19.0.0", 53 | "mocha": "^3.1.2", 54 | "quokka-mocha-bdd": "^1.1.0", 55 | "quokka-prerun": "^1.0.0", 56 | "quokka-signet-explorer": "^1.0.1", 57 | "sinon": "^2.3.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /signet.d.ts: -------------------------------------------------------------------------------- 1 | type type = string | function; 2 | type expectedType = type; 3 | type actualValue = any; 4 | type arguments = array | object; 5 | 6 | class SignetApi { 7 | declare alias(aliasName: string, typeString: string): undefined; 8 | declare buildInputErrorMessage(validationResult: [expectedType, actualValue], args: array, signatureTree: array>): string; 9 | declare buildOutputErrorMessage(validationResult: [expectedType, actualValue], args: array, signatureTree: array>): string; 10 | declare duckTypeFactory(duckTypeDef: object): function; 11 | declare defineDuckType(typeName: string, duckTypeDef: object): undefined; 12 | declare defineExactDuckType(typeName: string, duckTypeDef: object): undefined; 13 | declare defineDependentOperatorOn(typeName: string, operator: string, operatorCheck: (valueA: any, valueB: any, typeDefinitionA: ?object, typeDefinitionB: ?object) => boolean): undefined; 14 | declare defineRecursiveType(typeName: string, iteratorFactory: function, nodeType: type, typePreprocessor: ?function): undefined; 15 | declare enforce(signature: string, functionToEnforce: function, options: ?object): function; 16 | declare exactDuckTypeFactory(duckTypeDef: object): function; 17 | declare isRegisteredDuckType(typeName: string): boolean; 18 | declare isSubtypeOf(rootTypeName: string): (typeNameUnderTest: string) => boolean; 19 | declare isType(typeName: string): boolean; 20 | declare isTypeOf(typeToCheck: type): (value: any) => boolean; 21 | declare iterateOn(iterationArray: array): () => any; 22 | declare recursiveTypeFactory(iteratorFactory: function, nodeType: type): (valueToCheck: any) => boolean; 23 | declare registerTypeLevelMacro(macro: function): undefined; 24 | declare reportDuckTypeErrors(duckTypeName: string): (valueToCheck: any) => array<[string, string, *]>; 25 | declare sign(signature: string, functionToSign: function): function; 26 | declare subtype(rootTypeName: string): (subtypeName: string, subtypeCheck: function, preprocessor: ?(string) => string) => undefined; 27 | declare typeChain(typeName: string): string; 28 | declare verify(signedFunctionToVerify:function, functionArguments:arguments): undefined; 29 | declare verifyValueType(typeToCheck:type): (value: any) => any; 30 | declare whichType(typeNames:array): (value: any) => string | null; 31 | declare whichVariantType(variantString:string): (value: any) => string | null; 32 | } 33 | 34 | export = signet; 35 | 36 | declare function signet(): SignetApi; 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/macros.test.js: -------------------------------------------------------------------------------- 1 | var signetBuilder = require('../index'); 2 | 3 | var assert = require('chai').assert; 4 | var timerFactory = require('./timer'); 5 | 6 | describe('Signet Macros', function () { 7 | 8 | var signet; 9 | var timer; 10 | 11 | beforeEach(function () { 12 | signet = signetBuilder(); 13 | 14 | timer = timerFactory(); 15 | timer.setMaxAcceptableTime(3); 16 | timer.start(); 17 | }); 18 | 19 | afterEach(function () { 20 | timer.stop(); 21 | timer.report(); 22 | }); 23 | 24 | describe('type-level macros', function () { 25 | 26 | it('should support option type macro', function () { 27 | assert.equal(signet.isTypeOf('?string')(undefined), true); 28 | assert.equal(signet.isTypeOf('?string')('foo'), true); 29 | assert.equal(signet.isTypeOf('?string')({}), false); 30 | }); 31 | 32 | it('should support defined type macro', function () { 33 | assert.equal(signet.isTypeOf('!*')(undefined), false); 34 | assert.equal(signet.isTypeOf('!*')(null), false); 35 | assert.equal(signet.isTypeOf('!*')({}), true); 36 | }); 37 | 38 | it('should support an empty parentheses "any" type macro', function () { 39 | assert.equal(signet.isTypeOf('()')('foo'), true); 40 | assert.doesNotThrow(signet.enforce('() => undefined', function () { })); 41 | }); 42 | 43 | it('should support a not macro', function () { 44 | assert.equal(signet.isTypeOf('^string')('foo'), false); 45 | assert.equal(signet.isTypeOf('^string')(null), true); 46 | }); 47 | 48 | }); 49 | 50 | describe('Signature-level Macros', function () { 51 | 52 | it('should parse function definition with nested function definition', function () { 53 | var doStuff = signet.sign('(* => boolean) => array', () => []); 54 | 55 | assert.equal(doStuff.signature, 'function<* => boolean> => array'); 56 | }); 57 | 58 | }); 59 | 60 | describe('Macro handling', function () { 61 | 62 | it('should properly sign a function using macros', function () { 63 | var expectedValue = 'something:[not>], somethingElse:[variant], aFunction:function<* => *> => null'; 64 | var testFn = signet.enforce( 65 | 'something:[!*], somethingElse:[?string], aFunction:(* => *) => null', 66 | () => null 67 | ); 68 | 69 | assert.equal(testFn.signature, expectedValue); 70 | }); 71 | 72 | }); 73 | 74 | }); -------------------------------------------------------------------------------- /test/signet.test.js: -------------------------------------------------------------------------------- 1 | var signetBuilder = require('../index'); 2 | // var signetBuilder = require('../dist/signet'); 3 | var signetParser = require('signet-parser'); 4 | 5 | var assert = require('chai').assert; 6 | var timerFactory = require('./timer'); 7 | var sinon = require('sinon'); 8 | 9 | describe('Signet API', function () { 10 | 11 | var parser; 12 | var signet; 13 | var timer; 14 | 15 | function addBuilder() { 16 | return function (a, b) { 17 | return a + b; 18 | } 19 | } 20 | 21 | beforeEach(function () { 22 | parser = signetParser(); 23 | signet = signetBuilder(); 24 | 25 | timer = timerFactory(); 26 | timer.setMaxAcceptableTime(3); 27 | timer.start(); 28 | }); 29 | 30 | afterEach(function () { 31 | timer.stop(); 32 | timer.report(); 33 | }); 34 | 35 | describe('isTypeOf', function () { 36 | 37 | it('should verify against an ad-hoc type', function () { 38 | function is5(value) { 39 | return value === 5; 40 | } 41 | 42 | assert.equal(signet.isTypeOf(is5)(5), true); 43 | assert.equal(signet.isTypeOf(is5)(6), false); 44 | }); 45 | 46 | }); 47 | 48 | describe('sign', function () { 49 | 50 | it('should sign a function', function () { 51 | var expectedSignature = 'A < B :: A:number, B:number => number'; 52 | var signedAdd = signet.sign(expectedSignature, addBuilder()); 53 | var expectedTree = parser.parseSignature(expectedSignature); 54 | 55 | assert.equal(JSON.stringify(signedAdd.signatureTree), JSON.stringify(expectedTree)); 56 | assert.equal(signedAdd.signature, expectedSignature); 57 | }); 58 | 59 | it('should throw an error if signature contains a bad type', function () { 60 | var fnUnderTest = signet.sign.bind(null, 'number, foo => bar', addBuilder()); 61 | var expectedMessage = "Signature contains invalid types: foo, bar"; 62 | 63 | assert.throws(fnUnderTest, expectedMessage); 64 | }); 65 | 66 | it('should throw an error if signature does not satisfy all declared arguments', function () { 67 | var fnUnderTest = signet.sign.bind(null, 'number => number', addBuilder()); 68 | var expectedMessage = 'Signature declaration too short for function with 2 arguments'; 69 | 70 | assert.throws(fnUnderTest, expectedMessage); 71 | }); 72 | 73 | it('should throw error if signature has no output type', function () { 74 | var fnUnderTest = signet.sign.bind(null, 'number, number', addBuilder()); 75 | var expectedMessage = 'Signature must have both input and output types'; 76 | 77 | assert.throws(fnUnderTest, expectedMessage); 78 | }); 79 | 80 | it('should throw error if signature has multiple output types', function () { 81 | var fnUnderTest = signet.sign.bind(null, 'number, number => number, number', addBuilder()); 82 | var expectedMessage = 'Signature can only have a single output type'; 83 | 84 | assert.throws(fnUnderTest, expectedMessage); 85 | }); 86 | 87 | }); 88 | 89 | describe('enforce', function () { 90 | 91 | describe('Core Behaviors', function () { 92 | 93 | it('tuple should produce reliable signatures', function () { 94 | const expectedSignature = 'tuple<*;*> => *'; 95 | const testFn = signet.enforce(expectedSignature, () => null); 96 | 97 | assert.equal(testFn.signature, expectedSignature); 98 | }); 99 | 100 | it('should wrap an enforced function with an appropriate enforcer', function () { 101 | var originalAdd = addBuilder(); 102 | var add = signet.enforce('number, number => number', originalAdd); 103 | 104 | assert.equal(add.toString(), originalAdd.toString()); 105 | }); 106 | 107 | it('should enforce a function with a correct argument count', function () { 108 | var add = signet.enforce('number, number => number', addBuilder()); 109 | var expectedMessage = 'Anonymous expected a value of type number but got 6 of type string'; 110 | 111 | assert.throws(add.bind(null, 5, '6'), expectedMessage); 112 | }); 113 | 114 | it('should enforce a function return value', function () { 115 | var add = signet.enforce('number, number => number', function (a, b) { 116 | (a, b); 117 | return true; 118 | }); 119 | 120 | var expectedMessage = 'Anonymous expected a return value of type number but got true of type boolean'; 121 | 122 | assert.throws(add.bind(null, 3, 4), expectedMessage); 123 | }); 124 | 125 | it('should return result from enforced function', function () { 126 | var add = signet.enforce('number, number => number', addBuilder()); 127 | 128 | assert.equal(add(3, 4), 7); 129 | }); 130 | 131 | it('should not throw on unfulfilled optional int argument in a higher-order function containing a variant type', function () { 132 | function slice(start, end) { (end) } 133 | 134 | var enforcedSlice = signet.enforce('int, [int] => *', slice); 135 | 136 | assert.doesNotThrow(function () { 137 | enforcedSlice(5); 138 | }); 139 | }); 140 | 141 | it('should enforce a curried function properly', function () { 142 | function add(a) { 143 | return function (b) { 144 | (a, b); 145 | return 'bar'; 146 | } 147 | } 148 | 149 | var curriedAdd = signet.enforce('number => number => number', add); 150 | 151 | assert.throws(curriedAdd.bind(null, 'foo')); 152 | assert.throws(curriedAdd(5).bind(null, 'foo')); 153 | assert.throws(curriedAdd(5).bind(null, 6)); 154 | }); 155 | 156 | }); 157 | 158 | describe('Custom Errors', function () { 159 | 160 | it('should throw a custom error on bad input', function () { 161 | var add = signet.enforce('number, number => number', function (a, b) { 162 | (a, b); 163 | return true; 164 | }, { 165 | inputErrorBuilder: function (validationResult, args, signatureTree) { 166 | return 'This is a custom input error!' + validationResult.toString() + args.toString() + signatureTree.toString(); 167 | } 168 | }); 169 | 170 | var expectedMessage = 'This is a custom input error!number,no3,no[object Object],[object Object],[object Object]'; 171 | 172 | assert.throws(add.bind(null, 3, 'no'), expectedMessage); 173 | }); 174 | 175 | it('should throw a default error on bad input with core builder', function () { 176 | var add = signet.enforce('number, number => number', function (a, b) { 177 | (a, b); 178 | return true; 179 | }, { 180 | inputErrorBuilder: function (validationResult, args, signatureTree, functionName) { 181 | return signet.buildInputErrorMessage(validationResult, args, signatureTree, functionName); 182 | } 183 | }); 184 | 185 | var expectedMessage = 'Anonymous expected a value of type number but got no of type string'; 186 | 187 | assert.throws(add.bind(null, 3, 'no'), expectedMessage); 188 | }); 189 | 190 | it('should throw a default error on bad output with core builder', function () { 191 | var add = signet.enforce('number, number => number', function (a, b) { 192 | (a, b); 193 | return true; 194 | }, { 195 | outputErrorBuilder: function (validationResult, args, signatureTree, functionName) { 196 | return signet.buildOutputErrorMessage(validationResult, args, signatureTree, functionName); 197 | } 198 | }); 199 | 200 | var expectedMessage = 'Anonymous expected a return value of type number but got true of type boolean'; 201 | 202 | assert.throws(add.bind(null, 3, 4), expectedMessage); 203 | }); 204 | 205 | it('should throw a custom error on bad output', function () { 206 | var add = signet.enforce('number, number => number', function (a, b) { 207 | (a, b); 208 | return true; 209 | }, { 210 | outputErrorBuilder: function (validationResult, args, signatureTree) { 211 | return 'This is a custom output error!' + validationResult.toString() + args.toString() + signatureTree.toString(); 212 | } 213 | }); 214 | 215 | var expectedMessage = 'This is a custom output error!number,true3,4[object Object],[object Object],[object Object]'; 216 | 217 | assert.throws(add.bind(null, 3, 4), expectedMessage); 218 | }); 219 | 220 | }); 221 | 222 | describe('Dependent Type Operator Support', function () { 223 | 224 | it('should properly check symbolic dependent types', function () { 225 | function orderedProperly(a, b) { 226 | return a > b; 227 | } 228 | 229 | var enforcedFn = signet.enforce('A > B :: A:number, B:number => boolean', orderedProperly); 230 | 231 | function testWith(a, b) { 232 | return function () { 233 | return enforcedFn(a, b); 234 | }; 235 | } 236 | 237 | assert.throws(testWith(5, 6), 'orderedProperly expected a value of type A > B but got A = 5 and B = 6 of type string'); 238 | assert.equal(testWith(7, 3)(), true); 239 | }); 240 | 241 | it('should properly check symbolic type dependencies', function () { 242 | function testFnFactory() { 243 | return function (a, b) { 244 | a + b; 245 | return a; 246 | }; 247 | } 248 | 249 | assert.throws(signet.enforce( 250 | 'A <: B :: A:variant, B:variant => number', 251 | testFnFactory()).bind(null, 2.2, 3), 252 | 'Anonymous expected a value of type A <: B but got A = 2.2 and B = 3 of type string'); 253 | assert.throws(signet.enforce( 254 | 'A < B, B > C :: A:int, B:int, C:int => number', 255 | testFnFactory()).bind(null, 5, 6, 7), 256 | 'Anonymous expected a value of type B > C but got B = 6 and C = 7 of type string'); 257 | 258 | assert.doesNotThrow(signet.enforce( 259 | 'A <: B :: A:variant, B:variant => number', 260 | testFnFactory()).bind(null, 5, 6)); 261 | assert.doesNotThrow(signet.enforce( 262 | 'A < B, B < C :: A:int, B:int, C:int => number', 263 | testFnFactory()).bind(null, 5, 6, 7)); 264 | }); 265 | 266 | }); 267 | 268 | describe('Object and Constructor Support', function () { 269 | 270 | it('should properly enforce constructors', function () { 271 | var testMethodSpy = sinon.spy(); 272 | 273 | var MyObj = signet.enforce( 274 | 'a:int, b:string => undefined', 275 | function (a, b) { 276 | this.testMethod(a, b); 277 | } 278 | ); 279 | 280 | MyObj.prototype.testMethod = testMethodSpy; 281 | new MyObj(5, 'foo'); 282 | 283 | var result = JSON.stringify(testMethodSpy.args[0]); 284 | var expectedResult = JSON.stringify([5, 'foo']); 285 | 286 | assert.equal(testMethodSpy.callCount, 1); 287 | assert.equal(result, expectedResult); 288 | 289 | assert.throws( 290 | function () { return new MyObj('foo', 5); }, 291 | 'Anonymous expected a value of type a:int but got foo of type string' 292 | ); 293 | }); 294 | 295 | it('should properly enforce object methods', function () { 296 | function MyObj(a) { 297 | this.a = a; 298 | } 299 | 300 | MyObj.prototype = { 301 | testMethod: signet.enforce( 302 | 'b:int => result:int', 303 | function (b) { 304 | return this.a + b; 305 | } 306 | ) 307 | } 308 | 309 | var objInstance = new MyObj(6); 310 | 311 | assert.equal(objInstance.testMethod(7), 13); 312 | assert.throws( 313 | objInstance.testMethod.bind(objInstance, '7'), 314 | 'Anonymous expected a value of type b:int but got 7 of type string' 315 | ); 316 | 317 | }); 318 | 319 | }); 320 | 321 | describe('Function Properties', function () { 322 | 323 | it('should preserve properties on enforced function', function () { 324 | function adder(a, b) { 325 | return a + b; 326 | } 327 | 328 | adder.myProp = () => 'yay!'; 329 | 330 | const add = signet.enforce( 331 | 'number, number => number', 332 | adder 333 | ); 334 | 335 | assert.equal(add.myProp(), 'yay!'); 336 | }); 337 | 338 | }); 339 | 340 | describe('Higher-order Function Support', function () { 341 | 342 | it('should support a cross-execution environment table', function () { 343 | const addIncreasing = signet.enforce( 344 | 'a < b, b < sum :: a:int => b:int => sum:int', 345 | a => b => a - b 346 | ); 347 | 348 | 349 | assert.throws(addIncreasing(5).bind(null, 4), 'Anonymous expected a value of type a < b but got a = 5 and b = 4 of type string'); 350 | assert.throws(addIncreasing(5).bind(null, 6), 'Anonymous expected a return value of type b < sum but got b = 6 and sum = -1 of type string'); 351 | }); 352 | 353 | it('should enforce passed functions when a signature is provided', function () { 354 | const testFn = signet.enforce( 355 | 'function<* => boolean> => * => boolean', 356 | function (fn) { return () => fn(); }); 357 | 358 | function badFn() { return 'foo'; } 359 | 360 | assert.throws(testFn(badFn), 'badFn expected a return value of type boolean but got foo of type string'); 361 | }); 362 | 363 | it('should should pass options along to sub-enforcement', function () { 364 | const options = { 365 | outputErrorBuilder: function (validationResult, args, signatureTree) { 366 | return 'This is a custom output error!' + validationResult.toString() + args.toString() + signatureTree.toString(); 367 | } 368 | }; 369 | 370 | const testFn = signet.enforce( 371 | 'function<* => boolean> => * => string', 372 | function (fn) { return () => fn(); }, 373 | options); 374 | 375 | 376 | function badFn() { return 'foo'; } 377 | 378 | assert.throws(testFn(badFn), 'This is a custom output error!boolean,foo[object Object],[object Object]'); 379 | }); 380 | 381 | it('should not throw when function type is declared with constructor argument', function () { 382 | function doStuff() { return []; } 383 | signet.enforce('function<*, * => string, boolean => boolean> => array', doStuff); 384 | 385 | assert.doesNotThrow(doStuff.bind(null, function () { })); 386 | }); 387 | 388 | }); 389 | 390 | }); 391 | 392 | describe('extend', function () { 393 | 394 | it('should register a new type', function () { 395 | signet.extend('foo', function (value) { return value === 'foo'; }); 396 | 397 | assert.equal(signet.isType('foo'), true); 398 | assert.equal(signet.isTypeOf('foo')('foo'), true); 399 | }); 400 | 401 | it('should handle type arity up front', function () { 402 | signet.extend('myTestType0', function () { }); 403 | signet.extend('myTestType1{1}', function () { }); 404 | signet.extend('myTestType1OrMore{1,}', function () { }); 405 | signet.extend('myTestType2To5{2, 5}', function () { }); 406 | 407 | assert.doesNotThrow(signet.isTypeOf.bind(null, 'myTestType0<1, 2, 3>')); 408 | assert.throws( 409 | signet.isTypeOf('myTestType1<1, 2, 3>').bind(null, 'foo'), 410 | 'Type myTestType1 accepts, at most, 1 arguments'); 411 | assert.throws( 412 | signet.isTypeOf('myTestType1').bind(null, 'foo'), 413 | 'Type myTestType1 requires, at least, 1 arguments'); 414 | 415 | assert.doesNotThrow(signet.isTypeOf('myTestType1OrMore<1, 2, 3>').bind(null, 'foo')); 416 | assert.throws( 417 | signet.isTypeOf('myTestType1OrMore').bind(null, 'foo'), 418 | 'Type myTestType1OrMore requires, at least, 1 arguments'); 419 | 420 | assert.doesNotThrow(signet.isTypeOf('myTestType2To5<1, 2, 3>').bind(null, 'foo')); 421 | assert.throws( 422 | signet.isTypeOf('myTestType2To5').bind(null, 'foo'), 423 | 'Type myTestType2To5 requires, at least, 2 arguments'); 424 | assert.throws( 425 | signet.isTypeOf('myTestType2To5<1, 2, 3, 4, 5, 6>').bind(null, 'foo'), 426 | 'Type myTestType2To5 accepts, at most, 5 arguments'); 427 | 428 | assert.throws( 429 | signet.extend.bind(null, 'myTestTypeBroken{5, 1}', function () { }), 430 | 'Error in myTestTypeBroken arity declaration: min cannot be greater than max'); 431 | }); 432 | 433 | }); 434 | 435 | describe('subtype', function () { 436 | 437 | it('should register a subtype', function () { 438 | signet.subtype('number')('intFoo', function (value) { return Math.floor(value) === value; }); 439 | 440 | assert.equal(signet.isSubtypeOf('number')('intFoo'), true); 441 | assert.equal(signet.isTypeOf('intFoo')(15), true); 442 | }); 443 | 444 | }); 445 | 446 | describe('alias', function () { 447 | 448 | it('should allow aliasing of types by other names', function () { 449 | signet.alias('foo', 'string'); 450 | 451 | assert.equal(signet.isTypeOf('foo')('bar'), true); 452 | assert.equal(signet.isTypeOf('foo')(5), false); 453 | }); 454 | 455 | it('should partially apply a type value', function () { 456 | signet.alias('testTuple', 'tuple<_; _>'); 457 | signet.alias('testPartialTuple', 'testTuple'); 458 | 459 | assert.equal(signet.isTypeOf('testTuple')([[], {}]), true); 460 | assert.equal(signet.isTypeOf('testPartialTuple')([5, 'foo']), true); 461 | assert.equal(signet.isTypeOf('testPartialTuple')([5, 6]), false); 462 | }); 463 | 464 | }); 465 | 466 | describe('verify', function () { 467 | 468 | it('should allow function argument verification inside a function body', function () { 469 | function test(a, b) { 470 | (a, b); 471 | signet.verify(test, arguments); 472 | } 473 | 474 | signet.sign('string, number => undefined', test); 475 | 476 | assert.throws(test.bind(5, 'five')); 477 | }); 478 | 479 | }); 480 | 481 | describe('enforceArguments', function () { 482 | 483 | it('throws an error if arguments do not match requirement', function () { 484 | function test(a) { 485 | signet.enforceArguments(['a:string'])(arguments); 486 | } 487 | 488 | assert.throws(test.bind(null, 5)); 489 | }); 490 | 491 | it('verifies arguments and skips unfulfilled optional arguments', function () { 492 | 493 | function test(a, b, c) { 494 | signet.enforceArguments(['a: string', 'b: [number]', 'c: string'])(arguments); 495 | } 496 | 497 | assert.doesNotThrow(test.bind(null, 'foo', 'bar')); 498 | }); 499 | 500 | }); 501 | 502 | describe('typeChain', function () { 503 | 504 | it('should return correct type chains', function () { 505 | const arrayTypeChain = signet.typeChain('array'); 506 | const numberTypeChain = signet.typeChain('number'); 507 | 508 | assert.equal(arrayTypeChain, '* -> object -> array'); 509 | assert.equal(numberTypeChain, '* -> nativeNumber -> number'); 510 | }); 511 | 512 | }); 513 | 514 | describe('duckTypeFactory', function () { 515 | 516 | it('should duck type check an object', function () { 517 | var isMyObj = signet.duckTypeFactory({ 518 | foo: 'string', 519 | bar: 'int', 520 | baz: 'array' 521 | }); 522 | 523 | signet.subtype('object')('myObj', isMyObj); 524 | 525 | assert.equal(signet.isTypeOf('myObj')({ foo: 55 }), false); 526 | assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [] }), true); 527 | }); 528 | 529 | it('should return false if value is not duck-type verifiable', function () { 530 | var isMyObj = signet.duckTypeFactory({ 531 | foo: 'string', 532 | bar: 'int', 533 | baz: 'array' 534 | }); 535 | 536 | assert.equal(isMyObj(null), false); 537 | }); 538 | 539 | it('should produce a recursively defined type', function() { 540 | var isMyObj = signet.duckTypeFactory({ 541 | _something: { 542 | anotherThing: 'string' 543 | }, 544 | foo: 'string', 545 | bar: 'int', 546 | baz: 'array' 547 | }) 548 | 549 | signet.subtype('object')('myObj', isMyObj) 550 | 551 | var testObject = { 552 | _something: { 553 | anotherThing: 'yay!' 554 | }, 555 | foo: 'blah', 556 | bar: 127, 557 | baz: [] 558 | } 559 | 560 | assert.equal(signet.isTypeOf('myObj')(testObject), true); 561 | }) 562 | 563 | }); 564 | 565 | describe('exactDuckTypeFactory', function () { 566 | 567 | it('should check and exact duck type on an object', function () { 568 | var isMyObj = signet.exactDuckTypeFactory({ 569 | foo: 'string', 570 | bar: 'int', 571 | baz: 'array' 572 | }); 573 | 574 | signet.subtype('object')('myExactObj', isMyObj); 575 | 576 | assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [], quux: '' }), false); 577 | assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [] }), true); 578 | }); 579 | 580 | it('should check and exact duck type on an object', function () { 581 | var isMyObj = signet.exactDuckTypeFactory({ 582 | foo: 'string', 583 | bar: 'int', 584 | baz: 'array' 585 | }); 586 | 587 | signet.subtype('object')('myExactObj', isMyObj); 588 | 589 | assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [], quux: '' }), false); 590 | assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [] }), true); 591 | }); 592 | 593 | }); 594 | 595 | describe('defineDuckType', function () { 596 | 597 | it('should allow duck types to be defined directly', function () { 598 | signet.defineDuckType('myObj', { 599 | foo: 'string', 600 | bar: 'int', 601 | baz: 'array' 602 | }); 603 | 604 | assert.equal(signet.isTypeOf('myObj')({ foo: 55 }), false); 605 | assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [] }), true); 606 | }); 607 | 608 | it('should allow reporting of duck type errors', function () { 609 | signet.defineDuckType('aTestThingy', { 610 | quux: '!*' 611 | }); 612 | 613 | signet.defineDuckType('myObj', { 614 | foo: 'string', 615 | bar: 'int', 616 | baz: 'array', 617 | deeperType: 'aTestThingy' 618 | }); 619 | 620 | var result = signet.reportDuckTypeErrors('myObj')({ foo: 55, bar: 'bad value', baz: null, deeperType: {} }); 621 | var expected = '[["foo","string",55],["bar","int","bad value"],["baz","array",null],["deeperType","aTestThingy",[["quux","not>",null]]]]'; 622 | 623 | assert.equal(JSON.stringify(result), expected); 624 | assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [], deeperType: { quux: 'something' } }), true); 625 | }); 626 | 627 | }); 628 | 629 | describe('defineClassType', function () { 630 | 631 | it('verifies a type based on provided class', function () { 632 | class MyClass { 633 | constructor() {} 634 | 635 | test() {} 636 | 637 | test1() {} 638 | } 639 | 640 | signet.defineClassType(MyClass); 641 | 642 | const myInstance = new MyClass(); 643 | 644 | assert.isTrue(signet.isTypeOf('MyClass')(myInstance)); 645 | assert.isFalse(signet.isTypeOf('MyClass')({})); 646 | }); 647 | 648 | 649 | it('allows for extra properties to be defined', function () { 650 | class MyClass { 651 | constructor() { 652 | this.foo = 'bar'; 653 | this.someInt = 1234; 654 | } 655 | } 656 | 657 | signet.defineClassType(MyClass, { foo: 'string', someInt: 'int' }); 658 | 659 | const myInstance = new MyClass(); 660 | 661 | assert.isTrue(signet.isTypeOf('MyClass')(myInstance)); 662 | assert.isFalse(signet.isTypeOf('MyClass')({})); 663 | }); 664 | 665 | it('throws an error when on attempt to override existing property', function () { 666 | class MyClass { 667 | constructor() { 668 | this.someInt = 1234; 669 | } 670 | 671 | foo() {} 672 | } 673 | 674 | const classTypeDefiner = () => signet.defineClassType(MyClass, { foo: 'string', someInt: 'int' }); 675 | 676 | assert.throws(classTypeDefiner); 677 | }); 678 | }); 679 | 680 | describe('classTypeFactory', function () { 681 | 682 | it('verifies a type based on provided class', function () { 683 | class MyClass { 684 | constructor() {} 685 | 686 | test() {} 687 | 688 | test1() {} 689 | } 690 | 691 | const isMyClass = signet.classTypeFactory(MyClass); 692 | 693 | const myInstance = new MyClass(); 694 | 695 | assert.isTrue(isMyClass(myInstance)); 696 | assert.isFalse(isMyClass({})); 697 | }); 698 | 699 | 700 | it('allows for extra properties to be defined', function () { 701 | class MyClass { 702 | constructor() { 703 | this.foo = 'bar'; 704 | this.someInt = 1234; 705 | } 706 | } 707 | 708 | const isMyClass = signet.classTypeFactory(MyClass, { foo: 'string', someInt: 'int' }); 709 | 710 | const myInstance = new MyClass(); 711 | 712 | assert.isTrue(isMyClass(myInstance)); 713 | assert.isFalse(isMyClass({})); 714 | }); 715 | 716 | it('throws an error when on attempt to override existing property', function () { 717 | class MyClass { 718 | constructor() { 719 | this.someInt = 1234; 720 | } 721 | 722 | foo() {} 723 | } 724 | 725 | const classTypeBuilder = () => signet.classTypeFactory(MyClass, { foo: 'string', someInt: 'int' }); 726 | 727 | assert.throws(classTypeBuilder); 728 | }); 729 | }); 730 | 731 | describe('reportDuckTypeErrors', function () { 732 | it('should return duck type error on bad object value', function () { 733 | signet.defineDuckType('duckTest', {}); 734 | let checkDuckTest = signet.reportDuckTypeErrors('duckTest'); 735 | 736 | const nullCheck = checkDuckTest(null); 737 | const intCheck = checkDuckTest(55); 738 | const stringCheck = checkDuckTest('foo'); 739 | 740 | assert.equal(JSON.stringify(nullCheck), '[["badDuckTypeValue","object",null]]'); 741 | assert.equal(JSON.stringify(intCheck), '[["badDuckTypeValue","object",55]]'); 742 | assert.equal(JSON.stringify(stringCheck), '[["badDuckTypeValue","object","foo"]]'); 743 | }); 744 | 745 | }); 746 | 747 | describe('isRegisteredDuckType', function () { 748 | 749 | it('should allow querying of registered duck types', function () { 750 | signet.defineDuckType('duckFoo', {}); 751 | 752 | assert.equal(signet.isRegisteredDuckType('duckFoo'), true); 753 | assert.equal(signet.isRegisteredDuckType('duckBar'), false); 754 | }); 755 | 756 | }); 757 | 758 | describe('whichVariantType', function () { 759 | 760 | it('should get variant type of value', function () { 761 | var getValueType = signet.whichVariantType('variant'); 762 | 763 | assert.equal(getValueType('foo'), 'string'); 764 | assert.equal(getValueType(17), 'int'); 765 | assert.equal(getValueType(17.5), null); 766 | }); 767 | 768 | }); 769 | 770 | describe('verifyValueType', function () { 771 | 772 | it('should return value when it matches type correctly', function () { 773 | var stringValue = 'foo'; 774 | var stringResult = signet.verifyValueType('string')(stringValue); 775 | 776 | var boundedIntValue = 5; 777 | var boundedIntResult = signet.verifyValueType('leftBoundedInt<4>')(boundedIntValue); 778 | 779 | assert.equal(stringResult, stringValue); 780 | assert.equal(boundedIntResult, boundedIntValue); 781 | }); 782 | 783 | it('should throw an error if the value is of incorrect type', function () { 784 | var verifyStringValue = signet.verifyValueType('string'); 785 | var verifyBoundedIntValue = signet.verifyValueType('leftBoundedInt<4>'); 786 | 787 | assert.throws(() => verifyStringValue({})); 788 | assert.throws(() => verifyBoundedIntValue(-3)); 789 | }); 790 | 791 | }); 792 | 793 | describe('iterateOn and recursiveTypeFactory', function () { 794 | 795 | function setImmutableValue(obj, key, value) { 796 | Object.defineProperty(obj, key, { 797 | writeable: false, 798 | value: value 799 | }); 800 | } 801 | 802 | function cons(value, list) { 803 | const newNode = {}; 804 | 805 | setImmutableValue(newNode, 'value', value); 806 | setImmutableValue(newNode, 'next', list); 807 | 808 | return newNode; 809 | } 810 | 811 | it('should allow easy creation of a recursive type', function () { 812 | 813 | const isListNode = signet.duckTypeFactory({ 814 | value: 'int', 815 | next: 'composite, object>' 816 | }); 817 | 818 | const iterableFactory = signet.iterateOn('next'); 819 | const isIntList = signet.recursiveTypeFactory(iterableFactory, isListNode); 820 | 821 | const testList = cons(1, cons(2, cons(3, cons(4, cons(5, null))))); 822 | 823 | assert.equal(isIntList(testList), true); 824 | assert.equal(isIntList({ value: 1 }), false); 825 | assert.equal(isIntList('blerg'), false); 826 | }); 827 | 828 | it('should properly recurse through a binary tree with left and right values', function () { 829 | const isBinaryTreeNode = signet.duckTypeFactory({ 830 | value: 'int', 831 | left: 'composite<^array, object>', 832 | right: 'composite<^array, object>', 833 | }); 834 | 835 | function isOrderedNode(node) { 836 | return isBinaryTreeNode(node) 837 | && ((node.left === null || node.right === null) 838 | || (node.value > node.left.value 839 | && node.value <= node.right.value)); 840 | } 841 | 842 | signet.subtype('object')('orderedBinaryTreeNode', isOrderedNode); 843 | 844 | function iteratorFactory(value) { 845 | var iterable = []; 846 | 847 | iterable = value.left !== null ? iterable.concat([value.left]) : iterable; 848 | iterable = value.right !== null ? iterable.concat([value.right]) : iterable; 849 | 850 | return signet.iterateOnArray(iterable); 851 | } 852 | 853 | signet.defineRecursiveType('orderedBinaryTree', iteratorFactory, 'orderedBinaryTreeNode'); 854 | 855 | const isOrderedIntTree = signet.isTypeOf('orderedBinaryTree'); 856 | 857 | const goodBinaryTree = { 858 | value: 0, 859 | left: { 860 | value: -1, 861 | left: null, 862 | right: null 863 | }, 864 | right: { 865 | value: 1, 866 | left: { 867 | value: 1, 868 | left: null, 869 | right: null 870 | }, 871 | right: null 872 | } 873 | }; 874 | 875 | const badTree = { 876 | value: 0, 877 | left: { 878 | value: -1, 879 | left: null, 880 | right: null 881 | }, 882 | right: { 883 | value: -3, 884 | left: { 885 | value: 1, 886 | left: null, 887 | right: null 888 | }, 889 | right: null 890 | } 891 | }; 892 | 893 | const malformedTree = { 894 | value: 0, 895 | left: null 896 | }; 897 | 898 | assert.equal(isOrderedIntTree(goodBinaryTree), true); 899 | assert.equal(isOrderedIntTree(badTree), false); 900 | assert.equal(isOrderedIntTree(malformedTree), false); 901 | 902 | }); 903 | 904 | }); 905 | 906 | }); -------------------------------------------------------------------------------- /test/timer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function timerFactory () { 4 | var max = 0; 5 | var total = 0; 6 | var startTime = 0; 7 | 8 | 9 | function start() { 10 | startTime = process.hrtime(); 11 | } 12 | 13 | function stop() { 14 | total += process.hrtime(startTime)[1] / Math.pow(10, 6); 15 | startTime = 0; 16 | } 17 | 18 | function getTotal() { 19 | return total; 20 | } 21 | 22 | function reset() { 23 | total = 0; 24 | } 25 | 26 | function report() { 27 | console.log('Run time: %dms', total); 28 | 29 | if(total > max) { 30 | console.log('Long run detected: %dms', total); 31 | } 32 | } 33 | 34 | function setMaxAcceptableTime (maxMs) { 35 | max = maxMs; 36 | } 37 | 38 | return { 39 | getTotal: getTotal, 40 | reset: reset, 41 | report: report, 42 | setMaxAcceptableTime: setMaxAcceptableTime, 43 | start: start, 44 | stop: stop 45 | }; 46 | } 47 | 48 | module.exports = timerFactory; -------------------------------------------------------------------------------- /test/types.test.js: -------------------------------------------------------------------------------- 1 | var signetBuilder = require('../index'); 2 | 3 | var assert = require('chai').assert; 4 | var timerFactory = require('./timer'); 5 | 6 | describe('Signet Types', function () { 7 | 8 | var signet; 9 | var timer; 10 | 11 | function addBuilder() { 12 | return function (a, b) { 13 | return a + b; 14 | } 15 | } 16 | 17 | beforeEach(function () { 18 | signet = signetBuilder(); 19 | 20 | timer = timerFactory(); 21 | timer.setMaxAcceptableTime(3); 22 | timer.start(); 23 | }); 24 | 25 | afterEach(function () { 26 | timer.stop(); 27 | timer.report(); 28 | }); 29 | 30 | describe('Preregistered Types', function () { 31 | 32 | it('should automatically register the * type', function () { 33 | assert.equal(signet.isTypeOf('*')('foo'), true); 34 | }); 35 | 36 | it('should have all core JS types preregistered', function () { 37 | assert.equal(signet.isTypeOf('boolean')(false), true); 38 | assert.equal(signet.isTypeOf('function')(addBuilder()), true); 39 | assert.equal(signet.isTypeOf('number')(17), true); 40 | assert.equal(signet.isTypeOf('object')({}), true); 41 | assert.equal(signet.isTypeOf('string')('foo'), true); 42 | assert.equal(signet.isTypeOf('symbol')(Symbol()), true); 43 | assert.equal(signet.isTypeOf('undefined')(undefined), true); 44 | }); 45 | 46 | it('should have common subtypes preregistered', function () { 47 | assert.equal(signet.isTypeOf('null')(null), true); 48 | assert.equal(signet.isTypeOf('array')([]), true); 49 | assert.equal(signet.isTypeOf('array<*>')([1, 2, 'foo']), true); 50 | assert.equal(signet.isTypeOf('array')([1, 2, 'foo']), false); 51 | 52 | assert.equal(signet.isTypeOf('int')(5), true); 53 | assert.equal(signet.isTypeOf('int')(5.3), false); 54 | }); 55 | 56 | it('should preregister algebraic types', function () { 57 | assert.equal(signet.isTypeOf('tuple; boolean>')([123, '1234;foo', false]), true); 58 | assert.equal(signet.isTypeOf('tuple; boolean>')([123, '1234;33', false]), false); 59 | assert.equal(signet.isTypeOf('tuple; boolean>')([123, '1234;foo', false, 'hooray!']), false); 60 | 61 | assert.equal(signet.isTypeOf('variant')(10), true); 62 | assert.equal(signet.isTypeOf('variant')('I am a string'), true); 63 | assert.equal(signet.isTypeOf('variant')(null), false); 64 | 65 | assert.equal(signet.isTypeOf('taggedUnion')(null), false); 66 | }); 67 | 68 | it('should support native promises', function () { 69 | var isPromise = signet.isTypeOf('promise'); 70 | 71 | assert.equal(isPromise(new Promise(() => null, () => null)), true); 72 | }); 73 | 74 | it('should support an unordered product type', function () { 75 | var isUnorderedProduct = signet.isTypeOf('unorderedProduct'); 76 | 77 | assert.equal(isUnorderedProduct([1, 2, 3, 4]), false); //too short 78 | assert.equal(isUnorderedProduct([1, 2, 3, 4, 5, 6]), false); //too long 79 | assert.equal(isUnorderedProduct([2.5, 'foo', {}, 1.7, []]), false); //bad type 80 | assert.equal(isUnorderedProduct([1, 2.5, 'foo', [], {}]), true); 81 | assert.equal(isUnorderedProduct([2.5, 'foo', {}, 1, []]), true); 82 | }); 83 | 84 | it('should have a not operator to describe exclusive types', function () { 85 | assert.equal(signet.isTypeOf('not')('foo'), true); 86 | assert.equal(signet.isTypeOf('not')(null), false); 87 | }); 88 | 89 | it('should support a composition operator', function () { 90 | assert.equal(signet.isTypeOf('composite, object>')({}), true); 91 | assert.equal(signet.isTypeOf('composite, object>')(undefined), false); 92 | }); 93 | 94 | it('should verify type types against existing known types', function () { 95 | assert.equal(signet.isTypeOf('type')(function () { }), true); 96 | assert.equal(signet.isTypeOf('type')('variant'), true); 97 | assert.equal(signet.isTypeOf('type')('badType'), false); 98 | }); 99 | 100 | it('should preregister sequence types', function () { 101 | assert.equal(signet.isTypeOf('sequence')([1, 2, 3, 4]), true); 102 | assert.equal(signet.isTypeOf('sequence')([1, 2, 3.5, 4]), false); 103 | assert.throws(signet.isTypeOf('sequence').bind(null, []), 'A sequence may only be comprised of numbers, strings or their subtypes.'); 104 | 105 | assert.equal(signet.isTypeOf('monotoneSequence')([1, 2.5, 3, 4.7]), true); 106 | assert.equal(signet.isTypeOf('monotoneSequence')(['d', 'c', 'b', 'a']), true); 107 | assert.equal(signet.isTypeOf('monotoneSequence')([1]), true); 108 | assert.equal(signet.isTypeOf('monotoneSequence')([1, 2, -1, 5]), false); 109 | 110 | assert.equal(signet.isTypeOf('increasingSequence')([1, 2.5, 3, 4.7]), true, 'Not an increasing sequence of int'); 111 | assert.equal(signet.isTypeOf('increasingSequence')(['d', 'c', 'b', 'a']), false, 'Is an increasing sequence of string'); 112 | assert.equal(signet.isTypeOf('increasingSequence')([1]), true, 'Not an increasing sequence of one value'); 113 | assert.equal(signet.isTypeOf('increasingSequence')([1, 2, -1, 5]), false, 'Is an increasing sequence of int with a negative'); 114 | 115 | assert.equal(signet.isTypeOf('decreasingSequence')([1, 2.5, 3, 4.7]), false, 'Not an increasing sequence of int'); 116 | assert.equal(signet.isTypeOf('decreasingSequence')(['d', 'c', 'b', 'a']), true, 'Is an increasing sequence of string'); 117 | assert.equal(signet.isTypeOf('decreasingSequence')([1]), true, 'Not an increasing sequence of one value'); 118 | assert.equal(signet.isTypeOf('decreasingSequence')([1, 2, -1, 5]), false, 'Is an increasing sequence of int with a negative'); 119 | }); 120 | 121 | it('should have registered bounded types', function () { 122 | assert.equal(signet.isTypeOf('bounded')(null), false); 123 | 124 | assert.equal(signet.isTypeOf('bounded')(3), true); 125 | assert.equal(signet.isTypeOf('bounded')(5.1), false); 126 | assert.equal(signet.isTypeOf('bounded')(0), false); 127 | 128 | assert.equal(signet.isTypeOf('leftBounded')(0), true); 129 | assert.equal(signet.isTypeOf('leftBounded')(1), true); 130 | assert.equal(signet.isTypeOf('leftBounded')(-1), false); 131 | 132 | assert.equal(signet.isTypeOf('rightBounded')(0), true); 133 | assert.equal(signet.isTypeOf('rightBounded')(-1), true); 134 | assert.equal(signet.isTypeOf('rightBounded')(1), false); 135 | 136 | assert.equal(signet.isTypeOf('rightBounded')(1.3), false); 137 | 138 | assert.equal(signet.isTypeOf('boundedInt<1; 5>')(3), true); 139 | assert.equal(signet.isTypeOf('boundedInt<1; 5>')(3.1), false); 140 | assert.equal(signet.isTypeOf('boundedInt<1; 5>')(6), false); 141 | assert.equal(signet.isTypeOf('boundedInt<1; 5>')(0), false); 142 | 143 | assert.equal(signet.isTypeOf('leftBoundedInt<0>')(0), true); 144 | assert.equal(signet.isTypeOf('leftBoundedInt<0>')(1), true); 145 | assert.equal(signet.isTypeOf('leftBoundedInt<0>')(-1), false); 146 | assert.equal(signet.isTypeOf('leftBoundedInt<0>')(), false); 147 | 148 | assert.equal(signet.isTypeOf('rightBoundedInt<0>')(0), true); 149 | assert.equal(signet.isTypeOf('rightBoundedInt<0>')(-1), true); 150 | assert.equal(signet.isTypeOf('rightBoundedInt<0>')(1), false); 151 | 152 | assert.equal(signet.isTypeOf('boundedString<2; 15>')('hello'), true); 153 | assert.equal(signet.isTypeOf('boundedString<2; 15>')(''), false); 154 | assert.equal(signet.isTypeOf('boundedString<2; 15>')('this is a long string which should fail'), false); 155 | }); 156 | 157 | it('should support formatted strings', function () { 158 | assert.equal(signet.isTypeOf('formattedString<^\\d+(\\%;)?\\d*$>')('123;45'), true); 159 | assert.equal(signet.isTypeOf('formattedString<^\\d+(\\%;)?\\d*$>')('Not numbers'), false); 160 | assert.doesNotThrow(signet.isTypeOf.bind(null, 'formattedString<:>')) 161 | }); 162 | 163 | it('should verify enforced functions', function () { 164 | const goodEnforcedFunction = signet.enforce('* => *', () => null); 165 | const badEnforcedFunction = signet.enforce('* => null', () => null); 166 | 167 | assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(goodEnforcedFunction), true); 168 | assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(badEnforcedFunction), false); 169 | assert.equal(signet.isTypeOf('enforcedFunction<* => *>')(() => null), false); 170 | }); 171 | 172 | it('should pre-register signet type aliases', function () { 173 | assert.equal(signet.isTypeOf('void')(undefined), true); 174 | assert.equal(signet.isTypeOf('any')('anything'), true); 175 | }); 176 | 177 | it('should support decimal precision number types', function() { 178 | assert.isTrue(signet.isTypeOf('decimalPrecision<2>')(12.23)); 179 | assert.isTrue(signet.isTypeOf('decimalPrecision<4>')(3.45)); 180 | assert.isFalse(signet.isTypeOf('decimalPrecision<2>')(1.2345)); 181 | 182 | assert.throws(signet.isTypeOf('decimalPrecision<1.23>').bind(null, 123)); 183 | assert.throws(signet.isTypeOf('decimalPrecision<-5>').bind(null, 123)); 184 | }); 185 | 186 | }); 187 | }); --------------------------------------------------------------------------------