├── .babelrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── AssertGenerator.js ├── CodeGenerator.js ├── CommentConverter.js ├── jsdoc-to-assert.js └── tag │ ├── AllLiteral.js │ ├── Expression.js │ ├── FunctionType.js │ ├── NameExpression.js │ ├── NonNullableType.js │ ├── NullableType.js │ ├── RecordType.js │ ├── RestType.js │ ├── TypeApplication.js │ └── UnionType.js └── test ├── Attachment.js ├── babel-test.js ├── create-asserts-test.js ├── hasInstance-test.js ├── helper └── TestCodeGenerator.js └── mocha.opts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "env": { 6 | "development": { 7 | "presets": [ 8 | "power-assert" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Node.gitignore 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Debug log from npm 32 | npm-debug.log 33 | 34 | 35 | ### https://raw.github.com/github/gitignore/608690d6b9a78c2a003affc792e49a84905b3118/Global/JetBrains.gitignore 36 | 37 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 38 | 39 | *.iml 40 | 41 | ## Directory-based project format: 42 | .idea/ 43 | # if you remove the above rule, at least ignore the following: 44 | 45 | # User-specific stuff: 46 | # .idea/workspace.xml 47 | # .idea/tasks.xml 48 | # .idea/dictionaries 49 | 50 | # Sensitive or high-churn files: 51 | # .idea/dataSources.ids 52 | # .idea/dataSources.xml 53 | # .idea/sqlDataSources.xml 54 | # .idea/dynamic.xml 55 | # .idea/uiDesigner.xml 56 | 57 | # Gradle: 58 | # .idea/gradle.xml 59 | # .idea/libraries 60 | 61 | # Mongo Explorer plugin: 62 | # .idea/mongoSettings.xml 63 | 64 | ## File-based project format: 65 | *.ipr 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | 84 | 85 | /lib 86 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: "stable" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsdoc-to-assert [![Build Status](https://travis-ci.org/azu/jsdoc-to-assert.svg?branch=master)](https://travis-ci.org/azu/jsdoc-to-assert) 2 | 3 | Convert JSDoc to `assert` that runtime assert. 4 | 5 | Easy to use it with Babel. 6 | 7 | - [azu/babel-plugin-jsdoc-to-assert: Babel plugin for jsdoc-to-assert.](https://github.com/azu/babel-plugin-jsdoc-to-assert "azu/babel-plugin-jsdoc-to-assert: Babel plugin for jsdoc-to-assert.") 8 | 9 | ## Example 10 | 11 | jsdoc-to-assert create detection expression string: 12 | 13 | ```js 14 | Array.isArray(x) && x.every(function (item) { 15 | return typeof item === 'number'; 16 | }); 17 | ``` 18 | 19 | from following JSDoc comment: 20 | 21 | ```js 22 | /** 23 | * @param {number[]} x 24 | */ 25 | ``` 26 | 27 | ## Installation 28 | 29 | npm install jsdoc-to-assert 30 | 31 | ## Usage 32 | 33 | ```js 34 | import {AssertGenerator, CommentConverter, CodeGenerator} from "jsdoc-to-assert"; 35 | ``` 36 | 37 | ### AssertGenerator class 38 | 39 | Create assertion AST from comment nodes. 40 | 41 | /** 42 | * @typedef {Object} AssertGeneratorOptions 43 | * @property {Function} generator 44 | */ 45 | const defaultOptions = { 46 | Generator: CodeGenerator 47 | }; 48 | /** 49 | * 50 | * @param {Array} comments AST's comment nodes. it should be BlockComment 51 | * @param {AssertGeneratorOptions} options 52 | * @returns {Array} array of assertion 53 | */ 54 | static createAsserts(comments, options = {}); 55 | /** 56 | * @param tagNode tagNode is defined by doctorin 57 | * @param {CodeGenerator} Generator 58 | * @return {string|undefined} return assertion code string 59 | * Reference https://esdoc.org/tags.html#type-syntax 60 | * https://github.com/eslint/doctrine/blob/master/test/parse.js 61 | */ 62 | static createAssertFromTag(tagNode, Generator = CodeGenerator); 63 | 64 | ### CommentConverter class 65 | 66 | /** 67 | * Parse comment nodes which is provided by JavaScript parser like esprima, babylon 68 | * and return assertions code strings. 69 | * This is mutable function. 70 | * @param {Array} comment 71 | * @param {AssertGeneratorOptions} [options] 72 | * @returns {string[]} 73 | */ 74 | static toAsserts(comment, options); 75 | 76 | ### CodeGenerator class 77 | 78 | Assertion code generator class 79 | 80 | /** 81 | * @param commentTagNode commentTagNode is doctrine tag node 82 | */ 83 | constructor(commentTagNode); 84 | /** 85 | * wrap assert function 86 | * @param {string} expression 87 | * @returns {string} 88 | */ 89 | assert(expression) { 90 | return `console.assert(${expression},'${expression}');`; 91 | } 92 | 93 | ## Tests 94 | 95 | npm test 96 | 97 | ## Realated 98 | 99 | - FlowType: [codemix/babel-plugin-typecheck: Static and runtime type checking for JavaScript in the form of a Babel plugin.](https://github.com/codemix/babel-plugin-typecheck) 100 | - TypeScript: [Proposal: Run-time Type Checks · Issue #7607 · Microsoft/TypeScript](https://github.com/Microsoft/TypeScript/issues/7607 "Proposal: Run-time Type Checks · Issue #7607 · Microsoft/TypeScript") 101 | 102 | Comment to assert 103 | 104 | - [azu/comment-to-assert: convert single line comment to assert.](https://github.com/azu/comment-to-assert) 105 | - [azu/power-doctest: JavaScript: doctest + power-assert.](https://github.com/azu/power-doctest) 106 | 107 | ## Contributing 108 | 109 | 1. Fork it! 110 | 2. Create your feature branch: `git checkout -b my-new-feature` 111 | 3. Commit your changes: `git commit -am 'Add some feature'` 112 | 4. Push to the branch: `git push origin my-new-feature` 113 | 5. Submit a pull request :D 114 | 115 | ## License 116 | 117 | MIT 118 | 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsdoc-to-assert", 3 | "repository": { 4 | "type": "git", 5 | "url": "git+https://github.com/azu/jsdoc-to-assert.git" 6 | }, 7 | "author": "azu", 8 | "email": "azuciao@gmail.com", 9 | "homepage": "https://github.com/azu/jsdoc-to-assert", 10 | "license": "MIT", 11 | "files": [ 12 | "src/", 13 | "lib/" 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/azu/jsdoc-to-assert/issues" 17 | }, 18 | "version": "2.8.0", 19 | "description": "Convert comment to assert. runtime assert", 20 | "main": "lib/jsdoc-to-assert.js", 21 | "directories": { 22 | "test": "test" 23 | }, 24 | "scripts": { 25 | "build": "cross-env NODE_ENV=production babel src --out-dir lib --source-maps", 26 | "watch": "babel src --out-dir lib --watch --source-maps", 27 | "prepublish": "npm run --if-present build", 28 | "test": "mocha" 29 | }, 30 | "keywords": [ 31 | "ast", 32 | "jsdoc", 33 | "assert", 34 | "testing" 35 | ], 36 | "devDependencies": { 37 | "ast-equal": "^1.0.2", 38 | "babel-cli": "^6.6.5", 39 | "babel-generator": "^6.7.2", 40 | "babel-preset-es2015": "^6.6.0", 41 | "babel-preset-power-assert": "^1.0.0", 42 | "babel-register": "^6.7.2", 43 | "babel-template": "^6.7.0", 44 | "babel-traverse": "^6.7.3", 45 | "babel-types": "^6.7.2", 46 | "babylon": "^6.7.0", 47 | "cross-env": "^3.1.4", 48 | "escodegen": "^1.8.0", 49 | "esprima": "^3.1.1", 50 | "estraverse": "^4.2.0", 51 | "mocha": "^3.1.2", 52 | "power-assert": "^1.3.1" 53 | }, 54 | "dependencies": { 55 | "doctrine": "^2.0.0", 56 | "object-assign": "^4.1.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/AssertGenerator.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const ObjectAssign = require("object-assign"); 4 | import {AllLiteral} from "./tag/AllLiteral"; 5 | import {FunctionType} from "./tag/FunctionType"; 6 | import {NameExpression} from "./tag/NameExpression"; 7 | import {NonNullableType} from "./tag/NonNullableType"; 8 | import {NullableType} from "./tag/NullableType"; 9 | import {RecordType} from "./tag/RecordType"; 10 | import {RestType} from "./tag/RestType"; 11 | import {TypeApplication} from "./tag/TypeApplication"; 12 | import {UnionType} from "./tag/UnionType"; 13 | import CodeGenerator from "./CodeGenerator"; 14 | /** 15 | * @typedef {Object} AssertGeneratorOptions 16 | * @property {Function} generator 17 | */ 18 | const defaultOptions = { 19 | Generator: CodeGenerator 20 | }; 21 | export default class AssertGenerator { 22 | /** 23 | * 24 | * @param {Array} comments AST's comment nodes. it should be BlockComment 25 | * @param {AssertGeneratorOptions} options 26 | * @returns {Array} array of assertion 27 | */ 28 | static createAsserts(comments, options = {}) { 29 | if (comments == null) { 30 | return []; 31 | } 32 | const Generator = options.Generator || defaultOptions.Generator; 33 | const isNotEmpty = (tag) => { 34 | return tag != null; 35 | }; 36 | // primitive 37 | const createTag = (tag) => { 38 | return AssertGenerator.createAssertFromTag(tag, Generator); 39 | }; 40 | return comments.tags.map(createTag).filter(isNotEmpty); 41 | } 42 | 43 | /** 44 | * create assertions from @type 45 | * @param {string} name name is variable name 46 | * @param {Array} comments AST's comment nodes. it should be BlockComment 47 | * @param {AssertGeneratorOptions} options 48 | * @returns {Array} array of assertion 49 | */ 50 | static createTypeAsserts(name, comments, options = {}) { 51 | if (comments == null) { 52 | return []; 53 | } 54 | const Generator = options.Generator || defaultOptions.Generator; 55 | const isNotEmpty = (tag) => { 56 | return tag != null; 57 | }; 58 | // primitive 59 | const createTag = (tag) => { 60 | return AssertGenerator.createAssertFromTypeTag(name, tag, Generator); 61 | }; 62 | return comments.tags.map(createTag).filter(isNotEmpty); 63 | } 64 | 65 | /** 66 | * create assertion string from `typeNode` and `name`. 67 | * @param {string} [name] variable name 68 | * @param {{title:string, description: ?string, type:Object}} typeNode 69 | * @param {CodeGenerator} Generator 70 | * @returns {string|undefined} 71 | */ 72 | static createAssertFromTypeTag(name, typeNode, Generator = CodeGenerator) { 73 | if (name === undefined) { 74 | return; 75 | } 76 | const title = typeNode.title; 77 | // @returns etc... are not param 78 | if (title !== "type") { 79 | return; 80 | } 81 | // @param x - x have not type 82 | if (typeNode.type == null) { 83 | return; 84 | } 85 | 86 | const node = ObjectAssign({}, { 87 | name 88 | }, typeNode); 89 | const generator = new Generator(node); 90 | const tagType = typeNode.type.type; 91 | switch (tagType) { 92 | case "NameExpression": 93 | return NameExpression(node, generator); 94 | case "AllLiteral": 95 | return AllLiteral(node, generator); 96 | case "FunctionType": 97 | return FunctionType(node, generator); 98 | case "RecordType": 99 | return RecordType(node, generator); 100 | case "UnionType": 101 | return UnionType(node, generator); 102 | case "TypeApplication": 103 | return TypeApplication(node, generator); 104 | case "RestType": 105 | return RestType(node, generator); 106 | case "NullableType": 107 | return NullableType(node, generator); 108 | case "NonNullableType": 109 | return NonNullableType(node, generator); 110 | default: 111 | return; 112 | } 113 | } 114 | 115 | /** 116 | * @param tagNode tagNode is defined by doctrine 117 | * @param {CodeGenerator} Generator 118 | * @return {string|undefined} return assertion code string 119 | * Reference https://esdoc.org/tags.html#type-syntax 120 | * https://github.com/eslint/doctrine/blob/master/test/parse.js 121 | */ 122 | static createAssertFromTag(tagNode, Generator = CodeGenerator) { 123 | const title = tagNode.title; 124 | // @returns etc... are not param 125 | if (title !== "param") { 126 | return; 127 | } 128 | // @param x - x have not type 129 | if (tagNode.type == null) { 130 | return; 131 | } 132 | const generator = new Generator(tagNode); 133 | const tagType = tagNode.type.type; 134 | switch (tagType) { 135 | case "NameExpression": 136 | return NameExpression(tagNode, generator); 137 | case "AllLiteral": 138 | return AllLiteral(tagNode, generator); 139 | case "FunctionType": 140 | return FunctionType(tagNode, generator); 141 | case "RecordType": 142 | return RecordType(tagNode, generator); 143 | case "UnionType": 144 | return UnionType(tagNode, generator); 145 | case "TypeApplication": 146 | return TypeApplication(tagNode, generator); 147 | case "RestType": 148 | return RestType(tagNode, generator); 149 | case "NullableType": 150 | return NullableType(tagNode, generator); 151 | case "NonNullableType": 152 | return NonNullableType(tagNode, generator); 153 | default: 154 | return; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/CodeGenerator.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | function trimSpaceEachLine(text) { 4 | return text 5 | .split("\n") 6 | .filter(line => line != null) 7 | .map(line => line.trim()) 8 | .join("\n"); 9 | } 10 | export default class CodeGenerator { 11 | /** 12 | * @param commentTagNode commentTagNode is doctrine tag node 13 | */ 14 | constructor(commentTagNode) { 15 | } 16 | 17 | /** 18 | * wrap assert function 19 | * @param {string} expression 20 | * @returns {string} 21 | */ 22 | assert(expression) { 23 | if (expression.indexOf(",") > 0) { 24 | throw new Error("should not contain ,"); 25 | } 26 | // TODO: more safe using AST? 27 | const trimmedExpression = trimSpaceEachLine(expression); 28 | const noSpaceExpression = trimmedExpression.replace(/\n/g, "\\n"); 29 | return `console.assert(${trimmedExpression}, 'Invalid JSDoc: ${noSpaceExpression}');`; 30 | } 31 | } -------------------------------------------------------------------------------- /src/CommentConverter.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const doctrine = require('doctrine'); 4 | import AssertGenerator from "./AssertGenerator" 5 | export default class CommentConverter { 6 | /** 7 | * Parse @param comment nodes which is provided by JavaScript parser like esprima, babylon 8 | * and return assertions code strings. 9 | * This is mutable function. 10 | * @param {Object} comment 11 | * @param {AssertGeneratorOptions} [options] 12 | * @returns {string[]} 13 | */ 14 | static toAsserts(comment, options) { 15 | /* 16 | TODO: sloppy is not support 17 | It mean that optional param just ignored. 18 | */ 19 | try { 20 | const commentData = doctrine.parse(comment.value, {unwrap: true}); 21 | return AssertGenerator.createAsserts(commentData, options); 22 | } catch (error) { 23 | error.message = "jsdoc-to-assert: JSDoc Parse Error:\n" + error.message; 24 | throw error; 25 | } 26 | } 27 | 28 | /** 29 | * Parse @type comment nodes which is provided by JavaScript parser like esprima, babylon 30 | * and return assertions code strings. 31 | * This is mutable function. 32 | * @param {string} variableName 33 | * @param {Object} comment 34 | * @param {AssertGeneratorOptions} [options] 35 | * @returns {string[]} 36 | */ 37 | static toTypeAsserts(variableName, comment, options) { 38 | try { 39 | const commentData = doctrine.parse(comment.value, {unwrap: true}); 40 | return AssertGenerator.createTypeAsserts(variableName, commentData, options); 41 | } catch (error) { 42 | error.message = "jsdoc-to-assert: JSDoc Parse Error:\n" + error.message; 43 | throw error; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/jsdoc-to-assert.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import AssertGenerator from "./AssertGenerator" 4 | import CommentConverter from "./CommentConverter"; 5 | import CodeGenerator from "./CodeGenerator"; 6 | module.exports.AssertGenerator = AssertGenerator; 7 | module.exports.CommentConverter = CommentConverter; 8 | module.exports.CodeGenerator = CodeGenerator; -------------------------------------------------------------------------------- /src/tag/AllLiteral.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | /* 4 | @param {*} x 5 | */ 6 | /** 7 | * @return {string} 8 | */ 9 | export function AllLiteral(tag, CodeGenerator) { 10 | return CodeGenerator.assert(`(typeof ${tag.name} !== "undefined")`) 11 | } -------------------------------------------------------------------------------- /src/tag/Expression.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | /** 4 | * @return {string} 5 | */ 6 | export function Expression(tagName, typeValue) { 7 | // https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler#record-type 8 | // > myObject that has a value of **any** type. 9 | if (typeValue === null) { 10 | return `typeof ${tagName} !== "undefined"`; 11 | } 12 | if (typeValue.type && typeValue.type === "NullableType") { 13 | // recursion 14 | const otherExpression = Expression(tagName, typeValue.expression); 15 | return `(${tagName} == null || ${otherExpression})`; 16 | } else if (typeValue.type && typeValue.type === "NonNullableType") { 17 | // recursion 18 | const otherExpression = Expression(tagName, typeValue.expression); 19 | return `(${tagName} != null && ${otherExpression})`; 20 | } else { 21 | const expectedType = typeofName(typeValue.name); 22 | if (expectedType == null) { 23 | const expectedName = typeValue.name; 24 | // Can not handle Object.Property type like @param {Custom.Type} 25 | if (/\w+\.\w+/.test(expectedName)) { 26 | return "true"; 27 | } 28 | // if right-hand(expectedName) is undefined, return true 29 | // if right-hand is not function, return true 30 | // if right-hand is function && left-hand(tagName) instanceof right-hand(expectedName) 31 | // expectation, if left-hand is Array, use Array.isArray 32 | if (expectedName === "Array") { 33 | return `Array.isArray(${tagName})`; 34 | } 35 | return `( 36 | typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof ${expectedName} !== "undefined" && typeof ${expectedName}[Symbol.hasInstance] === "function" ? 37 | ${expectedName}[Symbol.hasInstance](${tagName}) : 38 | typeof ${expectedName} === "undefined" || typeof ${expectedName} !== "function" || ${tagName} instanceof ${expectedName} 39 | )`; 40 | } else { 41 | return `typeof ${tagName} === "${expectedType}"`; 42 | } 43 | } 44 | } 45 | // typeof 46 | export function typeofName(nodeTypeName) { 47 | switch (nodeTypeName) { 48 | case "Object": 49 | case "object": 50 | return "object"; 51 | case "function": 52 | case "Function": 53 | return "function"; 54 | case "string": 55 | case "String": 56 | return "string"; 57 | case "number": 58 | case "Number": 59 | return "number"; 60 | case "boolean": 61 | case "Boolean": 62 | return "boolean"; 63 | case "undefined": 64 | return "undefined"; 65 | case "symbol": 66 | case "Symbol": 67 | return "symbol"; 68 | default: 69 | return; 70 | } 71 | } -------------------------------------------------------------------------------- /src/tag/FunctionType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | // @param {Function} 4 | 5 | /** 6 | * @return {string} 7 | */ 8 | export function FunctionType(tag, CodeGenerator) { 9 | return CodeGenerator.assert(`typeof ${tag.name} === "function"`); 10 | } -------------------------------------------------------------------------------- /src/tag/NameExpression.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import {Expression} from "./Expression"; 4 | /** 5 | * @return {string} 6 | */ 7 | export function NameExpression(tag, CodeGenerator) { 8 | const expression = Expression(tag.name, tag.type); 9 | return CodeGenerator.assert(expression); 10 | } 11 | -------------------------------------------------------------------------------- /src/tag/NonNullableType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import {Expression} from "./Expression"; 4 | /** 5 | * @return {string} 6 | */ 7 | export function NonNullableType(tag, CodeGenerator) { 8 | const expression = Expression(tag.name, tag.type); 9 | return CodeGenerator.assert(expression); 10 | } -------------------------------------------------------------------------------- /src/tag/NullableType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import {Expression} from "./Expression"; 4 | /** 5 | * @return {string} 6 | */ 7 | export function NullableType(tag, CodeGenerator) { 8 | const expression = Expression(tag.name, tag.type); 9 | return CodeGenerator.assert(expression); 10 | } 11 | -------------------------------------------------------------------------------- /src/tag/RecordType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | // * @param {{foo: ?number, bar: string}} x - this is object param. 4 | // * @param {{foo, bar}} x - this is object param. 5 | import {Expression} from "./Expression"; 6 | /** 7 | * @return {string} 8 | */ 9 | export function RecordType(tag, CodeGenerator) { 10 | const fields = tag.type.fields; 11 | const isFiledType = filed => filed.type === "FieldType"; 12 | // It is {{}}-self 13 | const recordTypeAssertion = `typeof ${tag.name} !== "undefined"`; 14 | // These are {{ xxx }} 15 | const propertyAssertions = fields.filter(isFiledType).map(field => { 16 | const fieldPath = `${tag.name}.${field.key}`; 17 | return Expression(fieldPath, field.value); 18 | }); 19 | // join && 20 | const expression = [recordTypeAssertion].concat(propertyAssertions).join(" && "); 21 | return CodeGenerator.assert(expression); 22 | } -------------------------------------------------------------------------------- /src/tag/RestType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import {Expression} from "./Expression"; 4 | // @param {...number} tag 5 | 6 | /* 7 | { 8 | title: 'param', 9 | description: 'this is spread param.', 10 | type: 11 | { type: 'RestType', 12 | expression: 13 | { 14 | type: 'NameExpression', name: 'number' 15 | } 16 | }, 17 | name: 'x' } 18 | */ 19 | /** 20 | * @param tag 21 | * @param CodeGenerator 22 | * @returns {undefined|string} 23 | */ 24 | export function RestType(tag, CodeGenerator) { 25 | const expectedType = tag.type.expression.name; 26 | if (tag.type.type !== "RestType") { 27 | return; 28 | } 29 | const expression = tag.type.expression; 30 | if (typeof expression === "object") { 31 | const createGenericsAssert = () => { 32 | // or 33 | const expectedType = Expression("item", expression); 34 | // Array. 35 | // => (String || Number) 36 | return `function(item){ return (${expectedType}); }`; 37 | }; 38 | const expressionString = `${tag.name}.every(${createGenericsAssert()})`; 39 | return CodeGenerator.assert(`Array.isArray(${tag.name}) && ${expressionString}`); 40 | } 41 | } -------------------------------------------------------------------------------- /src/tag/TypeApplication.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | import {Expression} from "./Expression"; 4 | // @param {Array.} 5 | // @param {Object} 6 | /** 7 | * @return {string|undefined} 8 | */ 9 | export function TypeApplication(tag, CodeGenerator) { 10 | const expectedType = tag.type.expression.name; 11 | if (expectedType === "Array") { 12 | const applications = tag.type.applications; 13 | if (applications) { 14 | const createGenericsAssert = () => { 15 | // or 16 | const expressions = applications.map(application => { 17 | return Expression("item", application); 18 | }); 19 | // Array. 20 | // => (String || Number) 21 | const expression = expressions.join(" || "); 22 | return `function(item){ return (${expression}); }`; 23 | }; 24 | const expression = `${tag.name}.every(${createGenericsAssert()})`; 25 | return CodeGenerator.assert(`Array.isArray(${tag.name}) && ${expression}`); 26 | } else { 27 | return CodeGenerator.assert(`Array.isArray(${tag.name})`); 28 | } 29 | } else if (expectedType === "Object") { 30 | const applications = tag.type.applications; 31 | if (applications && applications.length === 2) { 32 | const expression = Expression(`${tag.name}`, {name: "object"}); 33 | const itemExpression = Expression(`${tag.name}[key]`, applications[1]); 34 | return CodeGenerator.assert(`${expression} && Object.keys(${tag.name}).every(function(key) { return (${itemExpression}); })`); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/tag/UnionType.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | // @param {string|number} x 4 | import {Expression} from "./Expression"; 5 | /** 6 | * @return {string} 7 | */ 8 | export function UnionType(tag, CodeGenerator) { 9 | const elements = tag.type.elements; 10 | const expression = elements.map(element => { 11 | return Expression(tag.name, element); 12 | }).join(" || "); 13 | return CodeGenerator.assert(expression); 14 | } 15 | -------------------------------------------------------------------------------- /test/Attachment.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const esprima = require('esprima'); 4 | const estraverse = require('estraverse'); 5 | const escodegen = require("escodegen"); 6 | const doctrine = require('doctrine'); 7 | import AssertGenerator from "../src/AssertGenerator" 8 | export default class Attachment { 9 | 10 | /** 11 | * FunctionDeclaration to FunctionDeclaration 12 | * This is mutable function. 13 | * @param node 14 | * @param comment 15 | * @returns {Object} 16 | */ 17 | static FunctionDeclaration(node, comment) { 18 | /* 19 | TODO: sloppy is not support 20 | It mean that optional param just ignored. 21 | */ 22 | const commentData = doctrine.parse(comment.value, {unwrap: true}); 23 | const assertsAST = AssertGenerator.createAsserts(commentData); 24 | const body = node.body.body; 25 | body.unshift(...assertsAST); 26 | return node; 27 | } 28 | 29 | /** 30 | * @param {Array} comment 31 | * @returns {string} 32 | */ 33 | static FunctionDeclarationString(comment) { 34 | var commentData = doctrine.parse(comment.value, {unwrap: true}); 35 | var assertsAST = AssertGenerator.createAsserts(commentData); 36 | return escodegen.generate({ 37 | type: esprima.Syntax.Program, 38 | body: assertsAST 39 | }); 40 | } 41 | 42 | 43 | /** 44 | * AST to AST 45 | * mutable function 46 | * @param {Object} AST 47 | * @returns {Object} 48 | */ 49 | static toASTFromAST(AST) { 50 | const replaceWalk = function replaceWalk(node) { 51 | switch (node.type) { 52 | case esprima.Syntax.FunctionDeclaration: 53 | if (node.leadingComments && node.leadingComments.length === 1) { 54 | const comment = node.leadingComments[0]; 55 | if (comment.type === 'Block') { 56 | return Attachment.FunctionDeclaration(node, comment); 57 | } 58 | } 59 | break; 60 | default: 61 | break; 62 | } 63 | }; 64 | return estraverse.replace(AST, { 65 | enter: function (node) { 66 | const r = replaceWalk(node); 67 | if (r) { 68 | return r; 69 | } 70 | } 71 | }); 72 | } 73 | 74 | /** 75 | * Code to AST 76 | * @param {string} content 77 | */ 78 | static toASTFromCode(content) { 79 | const tree = esprima.parse(content.toString(), {attachComment: true, loc: true}); 80 | return Attachment.toASTFromAST(tree); 81 | } 82 | } -------------------------------------------------------------------------------- /test/babel-test.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const assert = require("power-assert"); 4 | import {parse} from "babylon"; 5 | import traverse from "babel-traverse"; 6 | import generate from "babel-generator"; 7 | import template from "babel-template"; 8 | import CommentConverter from "../src/CommentConverter"; 9 | import TestGenerator from "./helper/TestCodeGenerator"; 10 | describe("with babel", function () { 11 | it("should convert string", function () { 12 | const code = ` 13 | /** 14 | * @param {number} param - this is a param. 15 | * @param {string} b - this is a param. 16 | * @param {string[]} [c] - this is a param. 17 | */ 18 | function myFunc(param, b, c){} 19 | `; 20 | 21 | const AST = parse(code); 22 | const MyVisitor = { 23 | FunctionDeclaration(path) { 24 | const node = path.node; 25 | if (node.leadingComments && node.leadingComments.length === 1) { 26 | const comment = node.leadingComments[0]; 27 | if (comment.type === 'CommentBlock') { 28 | const assertions = CommentConverter.toAsserts(comment).join("\n"); 29 | const buildAssert = template(assertions)(); 30 | path.get("body").unshiftContainer("body", buildAssert); 31 | } 32 | } 33 | } 34 | }; 35 | 36 | traverse(AST, { 37 | enter(path) { 38 | path.traverse(MyVisitor); 39 | } 40 | }); 41 | 42 | 43 | const result = generate(AST, {}, code); 44 | assert.equal(result.code, ` 45 | /** 46 | * @param {number} param - this is a param. 47 | * @param {string} b - this is a param. 48 | * @param {string[]} [c] - this is a param. 49 | */ 50 | function myFunc(param, b, c) { 51 | console.assert(typeof param === "number", 'Invalid JSDoc: typeof param === "number"'); 52 | console.assert(typeof b === "string", 'Invalid JSDoc: typeof b === "string"'); 53 | }`); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/create-asserts-test.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const assert = require("power-assert"); 4 | const esprima = require("esprima"); 5 | const doctrine = require("doctrine"); 6 | const astEqual = require("ast-equal").default; 7 | import AssertGenerator from "../src/AssertGenerator"; 8 | const {createAsserts, createAssertFromTag, createAssertFromTypeTag} = AssertGenerator; 9 | import TestGenerator from "./helper/TestCodeGenerator"; 10 | function parse(commentValue) { 11 | return doctrine.parse(commentValue, {unwrap: true}); 12 | } 13 | 14 | 15 | function pickTag(commentValue) { 16 | const results = parse(commentValue); 17 | console.assert(results != null); 18 | console.assert(results.tags.length > 0); 19 | return results.tags[0]; 20 | } 21 | function createAssertion(jsdoc) { 22 | return createAssertFromTag(pickTag(jsdoc), TestGenerator); 23 | } 24 | 25 | function createAssertionTypeNode(name, jsdoc) { 26 | return createAssertFromTypeTag(name, pickTag(jsdoc), TestGenerator); 27 | } 28 | 29 | describe("create-assert", function() { 30 | describe("#createAssertFromTypeTag", function() { 31 | context("when correct type", function() { 32 | it("should return assertion string", function() { 33 | const result = createAssertionTypeNode("value", `/** 34 | * @type {string} 35 | */`); 36 | assert(typeof result === "string"); 37 | assert.equal(result, `typeof value === "string"`); 38 | }); 39 | }); 40 | context("when pass null", function() { 41 | it("should return []", function() { 42 | const result = createAssertFromTypeTag(); 43 | assert(result === undefined); 44 | }); 45 | }); 46 | }); 47 | context("when pass null", function() { 48 | it("should return []", function() { 49 | const results = createAsserts(null); 50 | console.assert(Array.isArray(results)); 51 | console.assert(results.length === 0); 52 | }); 53 | }); 54 | context("when pass multiple param", function() { 55 | it("should not throw error", function() { 56 | const jsdoc = `/** 57 | * matchAll function inspired String.prototype.matchAll 58 | * @param {string} text 59 | * @param {RegExp} regExp 60 | * @returns {MatchAllGroup[]} 61 | * @see reference https://github.com/tc39/String.prototype.matchAll 62 | */`; 63 | const assertions = createAsserts(parse(jsdoc)); 64 | assert(assertions.length, 2); 65 | }); 66 | it("should not contain line break in each assertions", function() { 67 | const jsdoc = `/** 68 | * matchAll function inspired String.prototype.matchAll 69 | * @param {string} text 70 | * @param {RegExp} regExp 71 | * @param {{ 72 | 73 | foo: String 74 | 75 | }} obj 76 | * @returns {MatchAllGroup[]} 77 | * @see reference https://github.com/tc39/String.prototype.matchAll 78 | */`; 79 | const assertions = createAsserts(parse(jsdoc)); 80 | assertions.forEach(assertion => { 81 | try { 82 | esprima.parse(assertion); 83 | } catch (error) { 84 | console.log(assertion); 85 | throw error; 86 | } 87 | }); 88 | }); 89 | }); 90 | context("when pass no-typed param", function() { 91 | it("should ignore ", function() { 92 | 93 | const jsdoc = `/** 94 | * @param x 95 | */`; 96 | const assertion = createAssertion(jsdoc); 97 | assert(assertion == null); 98 | }); 99 | }); 100 | context("when pass @return", function() { 101 | it("should ignore ", function() { 102 | 103 | const jsdoc = `/** 104 | * @returns {Array} 105 | */`; 106 | const assertion = createAssertion(jsdoc); 107 | assert(assertion == null); 108 | }); 109 | }); 110 | context("when pass jsdoc", function() { 111 | it("should return array", function() { 112 | const jsdoc = ` 113 | /** 114 | * Adds three numbers. 115 | * 116 | * @param {number} x First number. 117 | */ 118 | `; 119 | const results = createAsserts(parse(jsdoc)); 120 | console.assert(Array.isArray(results)); 121 | console.assert(results.length === 1); 122 | }); 123 | }); 124 | context("when pass primitive type", function() { 125 | it("should return assert typeof number", function() { 126 | const jsdoc = `/** 127 | * @param {number} x 128 | */`; 129 | const assertion = createAssertion(jsdoc); 130 | astEqual(assertion, `typeof x === "number"`); 131 | }); 132 | it("should return assert typeof string", function() { 133 | const jsdoc = `/** 134 | * @param {string} x 135 | */`; 136 | const assertion = createAssertion(jsdoc); 137 | astEqual(assertion, `typeof x === "string"`); 138 | }); 139 | it("should return assert typeof boolean", function() { 140 | const jsdoc = `/** 141 | * @param {boolean} x 142 | */`; 143 | const assertion = createAssertion(jsdoc); 144 | astEqual(assertion, `typeof x === "boolean"`); 145 | }); 146 | it("should return assert typeof function", function() { 147 | const jsdoc = `/** 148 | * @param {Function} x 149 | */`; 150 | const assertion = createAssertion(jsdoc); 151 | astEqual(assertion, `typeof x === "function"`); 152 | }); 153 | it("should return assert typeof function", function() { 154 | const jsdoc = `/** 155 | * @param {function} x 156 | */`; 157 | const assertion = createAssertion(jsdoc); 158 | astEqual(assertion, `typeof x === "function"`); 159 | }); 160 | it("should return assert typeof object", function() { 161 | const jsdoc = `/** 162 | * @param {Object} x 163 | */`; 164 | const assertion = createAssertion(jsdoc); 165 | astEqual(assertion, `typeof x === "object"`); 166 | }); 167 | }); 168 | context("When pass all type", function() { 169 | it("should return assert AllLiteral ", function() { 170 | const jsdoc = `/** 171 | * @param {*} x - this is ArrayType param. 172 | */`; 173 | const assertion = createAssertion(jsdoc); 174 | astEqual(assertion, `typeof x !== "undefined"`); 175 | }); 176 | }); 177 | context("when pass RegExp", function() { 178 | 179 | it("should return assert typeof nullable", function() { 180 | const jsdoc = `/** 181 | * @param {RegExp} x - this is RegExp. 182 | */`; 183 | const assertion = createAssertion(jsdoc); 184 | astEqual(assertion, ` 185 | typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof RegExp !== "undefined" && typeof RegExp[Symbol.hasInstance] === "function" ? 186 | RegExp[Symbol.hasInstance](x) : 187 | typeof RegExp === 'undefined' || typeof RegExp !== 'function' || x instanceof RegExp 188 | `); 189 | }); 190 | }); 191 | context("when pass Custom Object", function() { 192 | it("should return assert typeof nullable", function() { 193 | const A = {}; 194 | const jsdoc = `/** 195 | * @param {CustomType} x - this is ArrayType param. 196 | */`; 197 | const assertion = createAssertion(jsdoc); 198 | astEqual(assertion, ` 199 | typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof CustomType !== "undefined" && typeof CustomType[Symbol.hasInstance] === "function" ? 200 | CustomType[Symbol.hasInstance](x) : 201 | typeof CustomType === 'undefined' || typeof CustomType !== 'function' || x instanceof CustomType 202 | `); 203 | }); 204 | }); 205 | context("when pass Object.Property type", function() { 206 | it("should return assert typeof nullable", function() { 207 | const A = {}; 208 | const jsdoc = `/** 209 | * @param {Object.Property} x - this is ArrayType param. 210 | */`; 211 | const assertion = createAssertion(jsdoc); 212 | astEqual(assertion, "true"); 213 | }); 214 | }); 215 | context("when pass Array only", function() { 216 | it("should return Array.isArray(x)", function() { 217 | const jsdoc = `/** 218 | * @param {Array} x - this is ArrayType param. 219 | */`; 220 | const assertion = createAssertion(jsdoc); 221 | astEqual(assertion, `Array.isArray(x)`); 222 | }); 223 | }); 224 | context("when pass *[]", function() { 225 | it("should return Array.isArray(x)", function() { 226 | const jsdoc = `/** 227 | * @param {*[]} x 228 | */`; 229 | const assertion = createAssertion(jsdoc); 230 | astEqual(assertion, `Array.isArray(x) && x.every(function (item) { 231 | return typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof undefined !== "undefined" && typeof undefined[Symbol.hasInstance] === "function" ? 232 | undefined[Symbol.hasInstance](item) : 233 | typeof undefined === 'undefined' || typeof undefined !== 'function' || item instanceof undefined; 234 | });`); 235 | }); 236 | }); 237 | context("when pass number[]", function() { 238 | it("should return Array.isArray(array) && check every type", function() { 239 | const jsdoc = `/** 240 | * @param {number[]} x - this is ArrayType param. 241 | */`; 242 | const assertion = createAssertion(jsdoc); 243 | astEqual(assertion, `Array.isArray(x) && x.every(function (item) { 244 | return typeof item === 'number'; 245 | });`); 246 | }); 247 | }); 248 | context("when pass CustomType[]", function() { 249 | it("should return Array.isArray(array) && check every type", function() { 250 | const jsdoc = `/** 251 | * @param {CustomType[]} x - this is ArrayType param. 252 | */`; 253 | const assertion = createAssertion(jsdoc); 254 | astEqual(assertion, `Array.isArray(x) && x.every(function (item) { 255 | return typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof CustomType !== "undefined" && typeof CustomType[Symbol.hasInstance] === "function" ? 256 | CustomType[Symbol.hasInstance](item) : 257 | typeof CustomType === 'undefined' || typeof CustomType !== 'function' || item instanceof CustomType; 258 | });`); 259 | }); 260 | }); 261 | context("when pass nullable", function() { 262 | it("should return assert typeof nullable", function() { 263 | const jsdoc = `/** 264 | * @param {?number} x - this is nullable param. 265 | */`; 266 | const assertion = createAssertion(jsdoc); 267 | astEqual(assertion, `(x == null || typeof x === "number")`); 268 | }); 269 | }); 270 | context("when pass NonNullableType", function() { 271 | it("should return assert typeof NonNullableType", function() { 272 | const jsdoc = `/** 273 | * @param {!number} x - this is non-nullable param. 274 | */`; 275 | const assertion = createAssertion(jsdoc); 276 | astEqual(assertion, `(x != null && typeof x === "number")`); 277 | }); 278 | }); 279 | context("when pass callback function", function() { 280 | it("should return assert typeof funtion", function() { 281 | const jsdoc = `/** 282 | * @param {function(foo: number, bar: string): boolean} x - this is function param. 283 | */`; 284 | const assertion = createAssertion(jsdoc); 285 | astEqual(assertion, `typeof x === "function"`); 286 | }); 287 | 288 | }); 289 | context("when pass optional primitive?", function() { 290 | // ignore 291 | }); 292 | context("when pass union type", function() { 293 | it("should return assert expression", function() { 294 | const jsdoc = `/** 295 | * @param {number|string} x - this is union param. 296 | */`; 297 | const assertion = createAssertion(jsdoc); 298 | astEqual(assertion, `(typeof x === "number" || typeof x === "string")`); 299 | }); 300 | }); 301 | context("when pass ...number", function() { 302 | it("should return Array.isArray(param) && check every type", function() { 303 | const jsdoc = ` 304 | /** 305 | * @param {...number} x - this is spread param. 306 | */`; 307 | 308 | const assertion = createAssertion(jsdoc); 309 | astEqual(assertion, `Array.isArray(x) && x.every(function (item) { 310 | return typeof item === 'number'; 311 | })`); 312 | }); 313 | }); 314 | context("when pass RecordType", function() { 315 | it("should assert multiple types ", function() { 316 | const jsdoc = `/** 317 | * @param {{SubscriptionId,Data}} data 318 | */`; 319 | const assertion = createAssertion(jsdoc); 320 | astEqual(assertion, `typeof data !== 'undefined' && typeof data.SubscriptionId !== 'undefined' && typeof data.Data !== 'undefined';`); 321 | }); 322 | it("should assert foo.bar as NullableType ", function() { 323 | const jsdoc = `/** 324 | * @param {{foo: ?number, bar: string}} x - this is object param. 325 | */`; 326 | const assertion = createAssertion(jsdoc); 327 | astEqual(assertion, `typeof x !== 'undefined' && (x.foo == null || typeof x.foo === 'number') && typeof x.bar === 'string';`); 328 | }); 329 | it("should return assert foo field with &&", function() { 330 | const jsdoc = `/** 331 | * @param {{foo: number, bar: string}} x - this is object param. 332 | */`; 333 | const assertion = createAssertion(jsdoc); 334 | astEqual(assertion, `typeof x !== 'undefined' && typeof x.foo === 'number' && typeof x.bar === 'string';`); 335 | }); 336 | it("should return assert Custom field with &&", function() { 337 | const jsdoc = `/** 338 | * @param {{foo: number, bar: RegExp}} x - this is object param. 339 | */`; 340 | const assertion = createAssertion(jsdoc); 341 | astEqual(assertion, `typeof x !== 'undefined' && typeof x.foo === 'number' && (typeof Symbol === 'function' && typeof Symbol.hasInstance === 'symbol' && typeof RegExp !== 'undefined' && typeof RegExp[Symbol.hasInstance] === 'function' ? RegExp[Symbol.hasInstance](x.bar) : typeof RegExp === 'undefined' || typeof RegExp !== 'function' || x.bar instanceof RegExp);`); 342 | }); 343 | }); 344 | context("When pass Array.", function() { 345 | it("should return Array.isArray(x) && check every type", function() { 346 | const jsdoc = `/** 347 | * @param {Array.} x - this is Array param. 348 | */`; 349 | const assertion = createAssertion(jsdoc); 350 | astEqual(assertion, `Array.isArray(x) && x.every(function (item) { 351 | return typeof item === 'string'; 352 | });`); 353 | }); 354 | }); 355 | context("When pass Object", function() { 356 | it("should check object itself and every object value", function() { 357 | const jsdoc = `/** 358 | * @param {Object} x - this is Object param with string keys and number values. 359 | */`; 360 | const assertion = createAssertion(jsdoc); 361 | astEqual(assertion, `typeof x === 'object' && Object.keys(x).every(function (key) { 362 | return typeof x[key] === 'number'; 363 | });`); 364 | }); 365 | }); 366 | }); -------------------------------------------------------------------------------- /test/hasInstance-test.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | const assert = require("power-assert"); 4 | 5 | const notUse_hasInstance = {}; 6 | 7 | function createCustomTypeAssertFunction(CustomType) { 8 | return function(value) { 9 | return ( 10 | typeof Symbol === "function" && typeof Symbol.hasInstance === "symbol" && typeof CustomType !== "undefined" && typeof CustomType[Symbol.hasInstance] === "function" ? 11 | CustomType[Symbol.hasInstance](value) : 12 | notUse_hasInstance 13 | ); 14 | }; 15 | } 16 | 17 | describe("hasInstance", function() { 18 | if (typeof Symbol !== "function" || typeof Symbol.hasInstance !== "symbol") { 19 | it.skip("Symbol.hasInstance is not supported in this environment", function() {}); 20 | return; 21 | } 22 | 23 | context("class", function() { 24 | it("should work same as 'instanceof CustomType'", function() { 25 | class CustomType {} 26 | 27 | const assertFunc = createCustomTypeAssertFunction(CustomType); 28 | 29 | assert(assertFunc(new CustomType()) === true); 30 | assert(assertFunc(new Int8Array(0)) === false); 31 | }); 32 | }); 33 | context("object with [Symbol.hasInstance]", function() { 34 | it("should return [Symbol.hasInstance]() value", function() { 35 | const EvenNumber = { 36 | [Symbol.hasInstance](value) { 37 | return value % 2 === 0; 38 | } 39 | }; 40 | 41 | const assertFunc = createCustomTypeAssertFunction(EvenNumber); 42 | 43 | assert(assertFunc(100) === true); 44 | assert(assertFunc(101) === false); 45 | }); 46 | }); 47 | context("object without [Symbol.hasInstance]", function() { 48 | it("should not use [Symbol.hasInstance]()", function() { 49 | const assertFunc = createCustomTypeAssertFunction({}); 50 | 51 | assert(assertFunc(100) === notUse_hasInstance); 52 | assert(assertFunc(200) === notUse_hasInstance); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/helper/TestCodeGenerator.js: -------------------------------------------------------------------------------- 1 | // LICENSE : MIT 2 | "use strict"; 3 | // simple echo 4 | export default class TestGenerator { 5 | /** 6 | * wrap assert function 7 | * @param {string} expression 8 | * @returns {string} 9 | */ 10 | assert(expression) { 11 | return expression; 12 | } 13 | } -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register --------------------------------------------------------------------------------