├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Makefile ├── README.md ├── TODO.md ├── docs ├── POST-only200lines.md └── videoScript │ ├── NOTES.md │ ├── isOdd-image.js │ ├── isOdd-jsdoced.js │ ├── isOdd.js │ ├── script-1.md │ ├── script-2.md │ └── script-jsdoced-in-30sec.md ├── examples ├── babelrc │ ├── .babelrc │ ├── README.md │ ├── isOdd.js │ └── sample-jsx-react.js ├── browser-sourcemaps-build.js ├── browser-sourcemaps-build.js.map ├── browser-sourcemaps.html ├── browser-sourcemaps.js ├── eslint │ ├── .eslintrc │ ├── README.md │ └── isOdd.js ├── gulpfile │ ├── README.md │ ├── build │ │ ├── .gitignore │ │ ├── nicelib-debug.js │ │ ├── nicelib-debug.js.map │ │ ├── nicelib-jsdoced.js │ │ ├── nicelib-min.js │ │ └── nicelib.js │ ├── gulpfile.js │ ├── package.json │ └── src │ │ ├── browser-sourcemaps.js │ │ └── isOdd.js ├── sample-es2015.js ├── sample-es6-arrowfunction-explicitreturn.js ├── sample-es6-arrowfunction-implicitreturn.js ├── sample-eslint-jsdoc.js ├── sample-functiondeclaration.js ├── sample-functionexpression.js ├── sample-functionreturnnested.js ├── sample-jsx-react.js ├── sample-property-target.js ├── sample-property.js ├── test-es6-proxy.js └── testbed.js ├── package.json ├── plugin-jsdoced-function.js ├── plugin-jsdoced-property.js ├── test ├── test-es6-arrowfunction.js ├── test-functiondeclaration.js ├── test-functionexpression.js └── test-jsx-function.js └── vendor └── jsdocParse.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.2.6-dev 2 | - implemented reporting error as exception optional 3 | - console.assert() 4 | - throw new TypeError() 5 | - added options 6 | - minimalReturn : true/false - if true, try not to put ```return``` into a block statement (default). if false, always put a block statement 7 | - errorType : 'assert'/'exception' - if equal to 'assert', errors are notified by ```console.assert()```. 8 | if equal to 'exception', notify errors by ```throw TypeError()``` 9 | - started plugin-jsdoced-property - try to apply type check for property too - still beta 10 | - setup services related to github 11 | - gitter.im for chat 12 | - npm badge for version 13 | - travis for testing 14 | 15 | # v1.2.5 16 | - added examples for eslintrc 17 | - added gulpfile examples 18 | - renamed repo into babel-plugin-jsdoced 19 | 20 | # v1.2.4 21 | - removed silly debug log 22 | 23 | # v1.2.3 24 | - wrote example for eslint jsdoc - http://eslint.org/docs/rules/valid-jsdoc 25 | - wrote example for .babelrc 26 | - clean up code generation 27 | - only to add block scope ```{}``` only when necessary 28 | - useful mainly for return e.g. ```if(cond) return true;``` 29 | 30 | # v1.2.1 31 | - wrote test and examples for react jsx 32 | - wrote test and examples for es2015 arrow function 33 | 34 | # v1.1.0 35 | - wrote a test suite with mocha 36 | - support for es2015 arrow function 37 | 38 | # v1.0.0 39 | - Support for function return 40 | - fix in function expression to support functionCall syntax 41 | 42 | # v0.7.0 43 | - Support for function expression 44 | 45 | # v0.6.0 46 | - function arguments are now checked based on the jsdoc of this function 47 | - works only on function declaration 48 | 49 | # v0.5.0 50 | - first version 51 | - not workable, barely parsing jsdoc 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | sample-jsx-react: 2 | babel --presets=react --plugins ../plugin-jsdoced-function.js ./examples/sample-jsx-react.js 3 | 4 | sample-property: 5 | babel --plugins ../plugin-jsdoced-property.js examples/sample-property.js 6 | # node-debug --cli babel --plugins ../plugin-jsdoced-property.js examples/sample-property.js 7 | 8 | sample-eslint-jsdoc: 9 | eslint --rule '{valid-jsdoc:["error", {"requireReturn": false}]}' examples/sample-eslint-jsdoc.js 10 | 11 | sample-es6-proxy: 12 | node --harmony_proxies examples/sample-es6-proxy.js 13 | 14 | sample-functionreturnnested: 15 | babel --plugins ../plugin-jsdoced-function.js ./examples/sample-functionreturnnested.js 16 | 17 | sample-es2015: 18 | babel --presets=es2015 -plugins ../plugin-jsdoced-function.js ./examples/sample-es2015.js 19 | 20 | sample-es6-arrowfunction-implicitreturn: 21 | babel --plugins ../plugin-jsdoced-function.js ./examples/sample-es6-arrowfunction-implicitreturn.js 22 | 23 | sample-es6-arrowfunction-explicitreturn: 24 | babel --plugins ../plugin-jsdoced-function.js ./examples/sample-es6-arrowfunction-explicitreturn.js 25 | 26 | sample-functiondeclaration: 27 | babel --plugins ../plugin-jsdoced-function.js ./examples/sample-functiondeclaration.js 28 | 29 | sample-functionexpression: 30 | babel --plugins ../plugin-jsdoced-function.js ./examples/sample-functionexpression.js 31 | 32 | browser-sourcemaps: 33 | babel --plugins ../plugin-jsdoced-function.js --source-maps=true ./examples/browser-sourcemaps.js -o ./examples/browser-sourcemaps-build.js 34 | @echo Now goto http://127.0.0.1:8080/examples/browser-sourcemaps.html 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-jsdoced 2 | 3 | 4 | 5 | a babel plugin which use jsdoc to implement strong typing in javascript 6 | It implements the principle of [jsdoced javascript](http://jsdocedjs.org) as a babel plugin. 7 | It implements strong typing for functions in javascript. 8 | The type of the function arguments and of the return value are checked according to your [jsdoc](http://usejsdoc.org/). 9 | It is working in browser and node.js. it has source maps. 10 | 11 | On top of it it supports [es6 arrow functions](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) too! 12 | If you use react, don't worry it works with jsx without trouble. 13 | 14 | Check it out! [How to install it](https://github.com/jeromeetienne/babel-plugin-jsdoced#installation) 15 | 16 | 17 | [![Chat](https://badges.gitter.im/jeromeetienne/babel-plugin-jsdoced.svg)](https://gitter.im/jeromeetienne/babel-plugin-jsdoced?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 18 | [![npm version](https://badge.fury.io/js/babel-plugin-jsdoced.svg)](https://badge.fury.io/js/babel-plugin-jsdoced) 19 | [![Build](https://travis-ci.org/jeromeetienne/babel-plugin-jsdoced.svg?branch=master)](https://travis-ci.org/jeromeetienne/babel-plugin-jsdoced) 20 | 21 | ## What is JSDoced Javascript ? 22 | Here is a [Youtube video "JSDoced JS in 1min"](https://youtu.be/W-cdPCNxNJ8), it worth the whole minute :) 23 | 24 | or in a single image 25 | 26 | ![jsdoced javascript in a single image](https://cloud.githubusercontent.com/assets/252962/14639163/e53f682a-0632-11e6-9a06-33b577118e53.jpg) 27 | 28 | 29 | # Features 30 | - no dependancies 31 | - works in node.js and browser 32 | - support source maps 33 | - works with es2015 and react/jsx 34 | - even works with new es6 arrow function 35 | - easily integrable in modern js tools. browserify/webpack/gulp you name it 36 | 37 | # Use cases 38 | - Improve your tests 39 | - jsdoced javascript will detect a new kind of errors. 40 | - So this is an additional level of testing 41 | - better detect those bugs before your users see them :) 42 | - Debug build for library 43 | - any library provide a minified version and a normal version 44 | - You can provide a jsdoced version 45 | - this debug version contains additional code to help debug. 46 | - thus people can use this one when developping or testing their own project 47 | - and hopefully detect errors earlier. 48 | - Specially interesting when you are learning a library. because you are more likely to do mistakes. 49 | better for your users :) 50 | - see [/examples/gulpfile](https://github.com/jeromeetienne/babel-plugin-jsdoced/tree/master/examples/gulpfile) for a example on how to that with gulp 51 | 52 | ## Installation 53 | 54 | ```sh 55 | $ npm install babel-plugin-jsdoced 56 | ``` 57 | 58 | ## Usage 59 | 60 | ### Via `.babelrc` (Recommended) 61 | 62 | **.babelrc** 63 | 64 | ```js 65 | { 66 | "plugins": ["jsdoced"] 67 | } 68 | ``` 69 | 70 | With es6 71 | 72 | ```js 73 | { 74 | "presets": ["es2015"], 75 | "plugins": ["jsdoced"] 76 | } 77 | ``` 78 | 79 | ### Via CLI 80 | 81 | ```sh 82 | $ babel --plugins jsdoced script.js 83 | ``` 84 | 85 | With es6 86 | 87 | ```sh 88 | $ babel --presets=es2015 --plugins jsdoced ./sample-es6.js 89 | ``` 90 | 91 | ### Via Node API 92 | 93 | ```javascript 94 | require("babel-core").transform("code", { 95 | plugins: ["jsdoced"] 96 | }); 97 | ``` 98 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - test with property and proxy 2 | - create another plugin, and use it only in the makefile for now 3 | - plugin-jsdoced-property.js 4 | - property: make a sample of each jsdoc case. 5 | - DONE to rename babel-plugin-jsdoced. simpler to read, and type. 6 | - DONE generate console.assert or throw on options 7 | - DONE put types2Conditions into jsdoc parse 8 | - DONE make option reachable via usual babel plugins 9 | - DONE implement travis tests - https://github.com/babel/babel/blob/master/README.md 10 | 11 | --- 12 | - TODO complete the clean return 13 | - what about the following rules. if parent got a single child. 14 | - BUG: following code make parser crash 15 | ```/** 16 | * blabla 17 | */ 18 | class Point { 19 | } 20 | ``` 21 | - DONE how to handle the function without return at all. aka the one returning undefined 22 | - what if undefined is allowed ? what if undefined is not allowed ? 23 | - ANSWER: let eslint handle it. 24 | - write something on how to use it with webpack 25 | - Webpack plugin for Babel - https://github.com/babel/babel-loader 26 | --- 27 | - how to test on a large project ? 28 | --- 29 | - DONE test with es6 and with jsx 30 | - DONE test arrow Function 31 | - with implicit return 32 | - with explicit return 33 | 34 | - DONE implement testing 35 | - use mocha 36 | - link with npm test 37 | - see about triggering github auto test 38 | - should i write a presets ? es2015-jsdoced, react-jsdoced ... for configuration easyness ? 39 | - DONE clean up node_modules 40 | - DONE see how it works in a browser 41 | - DONE what about sourceMap 42 | - DONE check if the plugin name is the proper one 43 | - DONE see how it support es6 44 | 45 | - test the nested function with return value and jsdoc 46 | ``` 47 | /** 48 | * @return {Number} - super return value 49 | */ 50 | function bla(){ 51 | return 3 + (function(){ 52 | return 'ffff' 53 | })() 54 | } 55 | ``` 56 | 57 | ## ES2015 Support 58 | - see with the arrow Function 59 | - with and without implicit return 60 | 61 | 62 | ## TODO 63 | - options to support 64 | - see https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-plugin-options 65 | - if no @return specified, assume it MUST return undefined 66 | - options 67 | - noParamCheck=true 68 | - noReturnCheck 69 | - Check if the number of parameters is not specified in 70 | - warn if the jsdoc doesnt match the number of parameters or the return type 71 | -------------------------------------------------------------------------------- /docs/POST-only200lines.md: -------------------------------------------------------------------------------- 1 | ### How to implement function strong typing in 200lines of javascript. 2 | 3 | - [babel jsdoced plugin](https://github.com/jeromeetienne/babel-plugin-transform-jsdoced/blob/master/transform-jsdoced.js) is only 200lines long 4 | 5 | 6 | i love javascript so much :) 7 | 8 | A few time ago, i had this idea of ["jsdoced javascript"](http://jsdocedjs.org/), 9 | aka using jsdoc comment to improve javascript. 10 | 11 | 12 | 13 | 14 | ### Short intro on babel itself 15 | - [babel](https://babeljs.io) is javascript compiler. 16 | - It is widely used to compile 17 | [es6/es2015](https://babeljs.io/docs/plugins/preset-es2015/) 18 | and 19 | [jsx react](https://babeljs.io/docs/plugins/preset-react/). 20 | - It started a year ago as a [6to5](https://github.com/6to5), an transpiler to convert es2015 to javascript. 21 | - Then it changed its name to babel and became more generic. 22 | 23 | - But under the hood, it has a very modular architecture. 24 | - Since version 6, it doesn't provide any plugin by default. 25 | - If you want to do es2015, you install it separately. 26 | -------------------------------------------------------------------------------- /docs/videoScript/NOTES.md: -------------------------------------------------------------------------------- 1 | ### Notes 2 | - ala crazy javascript guy. 3 | - find a good simple demo to show how it works. 4 | - wow it works even on es6. 5 | 6 | ### TODO 7 | - find a better way to display the stack 8 | - record again without the talk in comment ? 9 | - include it at the editing stage 10 | -------------------------------------------------------------------------------- /docs/videoScript/isOdd-image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | * @return {Boolean} true if it is odd, false otherwise 5 | */ 6 | function isOdd(n){ 7 | return n % 2 ? true : false 8 | } 9 | 10 | // Let's use this function 11 | isOdd(0) 12 | // => false ... all cool. 13 | isOdd(1) 14 | // => true ... all cool. 15 | isOdd('foobar') 16 | // => false .. So the wrong argument is not detected in javascript 17 | // ... 18 | // But in jsdoced javascript, it returns 19 | // => AssertionError: Invalid type for Params 0 n 20 | 21 | // Cool no ? 22 | -------------------------------------------------------------------------------- /docs/videoScript/isOdd-jsdoced.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | */ 5 | function isOdd(n) { 6 | console.assert(typeof n === 'number', "Invalid type for Params 0 n"); 7 | 8 | if (n % 2) { 9 | console.log(n + ' is odd'); 10 | } else { 11 | console.log(n + ' is even'); 12 | } 13 | } 14 | 15 | // Let's use this function 16 | isOdd(0); 17 | isOdd(1); 18 | isOdd('foobar'); 19 | -------------------------------------------------------------------------------- /docs/videoScript/isOdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | */ 5 | function isOdd(n){ 6 | if( n % 2 ){ 7 | console.log(n+' is odd') 8 | }else{ 9 | console.log(n+' is even') 10 | } 11 | } 12 | 13 | // Let's use this function 14 | isOdd(0) 15 | isOdd(1) 16 | isOdd('foobar') 17 | -------------------------------------------------------------------------------- /docs/videoScript/script-1.md: -------------------------------------------------------------------------------- 1 | # Script 2 | - javascript is cool 3 | - javascript is everywhere 4 | - javascript is hype! 5 | - definitly hype. 6 | 7 | - but javascript got issues, lets admit it 8 | - one of them is strong typing. 9 | - when you call a function with the wrong of argument, javascript tells you nothing. 10 | - you are kept in the dark. 11 | 12 | - "jsdoced javascript" has been designed to help on this issue. 13 | - the principle is simple. we extract the information found in your jsdoc. 14 | - and use it to check the arguments you pass to a function. 15 | - so with jsdoced javascript, when you call a function with the wrong arguments 16 | - you get warned immediatly. 17 | 18 | - you better catch those bugs before your users see them, hey ? :) 19 | 20 | NOT COMPLETED. 21 | -------------------------------------------------------------------------------- /docs/videoScript/script-2.md: -------------------------------------------------------------------------------- 1 | ### script 1 2 | - Hello everybody, 3 | - i would like to show you one funny javascript trick. 4 | - it is a babel plugin, and it is less than 200 lines. 5 | - let me shortly explain the problem im trying to solve. 6 | - First lets take a function 7 | 8 | ```javascript 9 | function isOdd(n){ 10 | return n % 1 ? true : false 11 | } 12 | ``` 13 | 14 | - Lets use this function 15 | 16 | ```javascript 17 | isOdd(0) // => false 18 | isOdd(1) // => true 19 | ``` 20 | 21 | - now javascript is not strong typed. 22 | - What if we call 23 | 24 | ```javascript 25 | isOdd('foobar') // => false 26 | ``` 27 | 28 | - We pass the wrong argument, we pass a string instead of a Number 29 | - but javascript is telling you nothing. you 30 | 31 | - then what if we add a classical jsdoc comment. 32 | - and then write a babel plugin for it. 33 | - It will use jsdoc information to detect this kind of mistake 34 | - let me tell you more about this. 35 | 36 | - First lets add this jsdoc comment 37 | 38 | ```javascript 39 | /** 40 | * Test if it is odd 41 | * @param {Number} n - a number 42 | * @return {Boolean} true if it is odd, false otherwise 43 | */ 44 | function isOdd(n){ 45 | return n % 2 ? true : false 46 | } 47 | ``` 48 | 49 | - now we compile it using our babel plugin 50 | 51 | ```sh 52 | $ babel --plugins transform-jsdoced isOdd.js 53 | ``` 54 | 55 | - now let try again to call it with a String 56 | 57 | ``` 58 | isOdd('foobar') // => AssertionError: Invalid type for Params 0 n 59 | ``` 60 | 61 | - Cool no ?!?! 62 | -------------------------------------------------------------------------------- /docs/videoScript/script-jsdoced-in-30sec.md: -------------------------------------------------------------------------------- 1 | # jsdoced javascript in 30sec 2 | - better in a gif 3 | - similar in spirit as isOdd-image.js 4 | - all a terminal cast, all is done in real time and i talk in the comment 5 | 6 | # Process 7 | - first movie recorded with quicktime 8 | - edited or not ? YES with imovie 9 | - no sound 10 | 11 | # Scripts Demo - synopsys 12 | - show the isOdd function 13 | - add a jsdoc 14 | - then use the function in plain javascript 15 | - then compile in jsdoced javascript 16 | - then use it in jsdoced javascript 17 | 18 | # Script Demo - details 19 | - atom: "// Let's write a function" 20 | - atom: "// It will test if an number is odd or even" 21 | - atom: Here write the function 22 | ``` 23 | function isOdd(n){ 24 | if( n % 2 ){ 25 | console.log(n, 'is odd') 26 | }else{ 27 | console.log(n, 'is even') 28 | } 29 | } 30 | ``` 31 | - atom: go back up, remove the 2 old comment lines 32 | - atom: "// Now let's add JSDoc to it" 33 | - atom: Here write the JSDoc 34 | ``` 35 | /** 36 | * test if a number is odd or even 37 | * @param {Number} n - value to test 38 | */ 39 | ``` 40 | - atom: go back up, remove the comment line 41 | - atom: go down 42 | ``` 43 | // Let's check this function with valid arguments 44 | isOdd(0) 45 | isOdd(1) 46 | // Now let's check an invalid argument with a string. 47 | isOdd('foo') 48 | ``` 49 | 50 | - term: '# Now lets run this program' 51 | - term: type the following, dont forget to highlight results, when commenting them 52 | ``` 53 | $ node isOdd.js 54 | 0 'is even' 55 | 1 'is odd' 56 | foo is even 57 | $ # 0 is even, all good 58 | $ 59 | $ # 1 is odd, still all good 60 | $ 61 | $ # foo is even !?!?! 62 | $ # plain javascript doesn't detect the error 63 | ``` 64 | 65 | - term: clear screen via ctrl-l 66 | 67 | ``` 68 | $ # Now let's use babel to compile isOdd.js in jsdoced javascript 69 | $ # We compile isOdd.js with babel 70 | $ babel --plugins transform-jsdoced isOdd.js -o isOdd-jsdoced.js 71 | ``` 72 | 73 | ``` 74 | $ node isOdd-jsdoced.js 75 | 0 'is even' 76 | 1 'is odd' 77 | 78 | assert.js:89 79 | throw new assert.AssertionError({ 80 | ^ 81 | AssertionError: Invalid type for Params 0 n 82 | at Console.assert (console.js:93:23) 83 | at isOdd (/Users/jerome/webwork/babel-plugin-transform-jsdoced/docs/tmp/isOdd-jsdoced.js:7:11) 84 | at Object. (/Users/jerome/webwork/babel-plugin-transform-jsdoced/docs/tmp/isOdd-jsdoced.js:24:1) 85 | at Module._compile (module.js:409:26) 86 | at Object.Module._extensions..js (module.js:416:10) 87 | at Module.load (module.js:343:32) 88 | at Function.Module._load (module.js:300:12) 89 | at Function.Module.runMain (module.js:441:10) 90 | at startup (node.js:139:18) 91 | at node.js:968:3 92 | ``` 93 | 94 | ``` 95 | $ # 96 | -------------------------------------------------------------------------------- /examples/babelrc/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jsdoced"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/babelrc/README.md: -------------------------------------------------------------------------------- 1 | This directory shows a .babelrc using babel-plugin-jsdoced 2 | -------------------------------------------------------------------------------- /examples/babelrc/isOdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | */ 5 | function isOdd(n){ 6 | if( n % 2 ){ 7 | console.log(n+' is odd') 8 | }else{ 9 | console.log(n+' is even') 10 | } 11 | } 12 | 13 | // Let's use this function 14 | isOdd(0) 15 | isOdd(1) 16 | isOdd('foobar') 17 | -------------------------------------------------------------------------------- /examples/babelrc/sample-jsx-react.js: -------------------------------------------------------------------------------- 1 | /** @jsx dom */ 2 | 3 | var { dom } = require("deku"); 4 | 5 | /** 6 | * @param {String} className - super class 7 | * @return {ReactElement} element 8 | */ 9 | function testFunction(className){ 10 | return
11 | 12 |

{[user.firstName, user.lastName].join(' ')}

13 |
; 14 | } -------------------------------------------------------------------------------- /examples/browser-sourcemaps-build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2) { 8 | console.assert(typeof myString1 === 'string', "Invalid type for argument 0 myString1"); 9 | console.assert(typeof myString2 === 'string', "Invalid type for argument 1 myString2"); 10 | 11 | var _returnValue = myString1 + myString2; 12 | 13 | console.assert(typeof _returnValue === 'string', "Invalid type for return value"); 14 | return _returnValue; 15 | } 16 | 17 | console.log('return is ', myFunctionDeclaration('foo', 'bar')); 18 | 19 | //# sourceMappingURL=browser-sourcemaps-build.js.map -------------------------------------------------------------------------------- /examples/browser-sourcemaps-build.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["browser-sourcemaps.js"],"names":[],"mappings":";;;;;;AAMA,SAAS,qBAAT,CAA+B,SAA/B,EAA0C,SAA1C,EAAoD;;;;qBACrC,YAAU,SAAV,CADqC;;;;CAApD;;AAKA,QAAQ,GAAR,CAAY,YAAZ,EAA0B,sBAAsB,KAAtB,EAA6B,KAA7B,CAA1B","file":"browser-sourcemaps-build.js","sourcesContent":["/**\n * This is a super function\n * @param {String} myString1 - a super string\n * @param {String} myString2 - a super string\n * @return {String} - super returned string\n */\nfunction myFunctionDeclaration(myString1, myString2){\n return myString1+myString2;\n}\n\n\nconsole.log('return is ', myFunctionDeclaration('foo', 'bar'))\n \n"]} -------------------------------------------------------------------------------- /examples/browser-sourcemaps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/browser-sourcemaps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2){ 8 | return myString1+myString2; 9 | } 10 | 11 | 12 | console.log('return is ', myFunctionDeclaration('foo', 'bar')) 13 | 14 | -------------------------------------------------------------------------------- /examples/eslint/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "valid-jsdoc" :[ "error", { 4 | "requireReturn": false 5 | }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/eslint/README.md: -------------------------------------------------------------------------------- 1 | this is an examples of [.eslintrc](http://eslint.org/docs/user-guide/configuring) 2 | 3 | It is a good way to check your source, especially to check if your jsdoc comment. 4 | 5 | If you run [atom](https://atom.io), you can even install packages to check it live. 6 | For example (linter-eslint)[https://atom.io/packages/linter-eslint]. 7 | -------------------------------------------------------------------------------- /examples/eslint/isOdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | */ 5 | function isOdd(n){ 6 | if( n % 2 ){ 7 | console.log(n+' is odd') 8 | }else{ 9 | console.log(n+' is even') 10 | } 11 | } 12 | 13 | // Let's use this function 14 | isOdd(0) 15 | isOdd(1) 16 | isOdd('foobar') 17 | -------------------------------------------------------------------------------- /examples/gulpfile/README.md: -------------------------------------------------------------------------------- 1 | This is an example of using babel-plugin-jsdoced in a gulpfile.js. 2 | This gulpfile.js is for a library ```nicelib.js```. It is a fake library 3 | built up for the example. All the source of the library is ```/src```. 4 | 5 | This gulpfile.js will build 3 versions of the library: 6 | 7 | - nicelib.js - the library itself, just a concat of the whole ```src/``` 8 | - nicelib-debug.js - the library compiled thru jsdoced - good for 9 | - nicelib-min.js - the library minified, good for production 10 | 11 | 12 | 13 | 14 | ### Notes on eslint 15 | It makes clever use of eslint during the build-debug process. 16 | It will use [eslint-jsdoc](http://eslint.org/docs/rules/valid-jsdoc) 17 | to check jsdoc comments matches the code. 18 | Thus if there inconsistencies detected, you will be warned immediatly. 19 | -------------------------------------------------------------------------------- /examples/gulpfile/build/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeromeetienne/babel-plugin-jsdoced/30f27d38a26ee9f7531c4bb82b65c557f22d8d28/examples/gulpfile/build/.gitignore -------------------------------------------------------------------------------- /examples/gulpfile/build/nicelib-debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2) { 8 | console.assert(typeof myString1 === 'string', "Invalid type for argument 0 myString1"); 9 | console.assert(typeof myString2 === 'string', "Invalid type for argument 1 myString2"); 10 | 11 | var _returnValue = myString1 + myString2; 12 | 13 | console.assert(typeof _returnValue === 'string', "Invalid type for return value"); 14 | return _returnValue; 15 | } 16 | 17 | console.log('return is ', myFunctionDeclaration('foo', 'bar')); 18 | /** 19 | * Test if it is odd 20 | * @param {Number} n - my number 21 | */ 22 | function isOdd(n) { 23 | console.assert(typeof n === 'number', "Invalid type for argument 0 n"); 24 | 25 | if (n % 2) { 26 | console.log(n + ' is odd'); 27 | } else { 28 | console.log(n + ' is even'); 29 | } 30 | } 31 | 32 | // Let's use this function 33 | isOdd(0); 34 | isOdd(1); 35 | isOdd('foobar'); 36 | //# sourceMappingURL=nicelib-debug.js.map 37 | -------------------------------------------------------------------------------- /examples/gulpfile/build/nicelib-debug.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["browser-sourcemaps.js","isOdd.js"],"names":[],"mappings":";;;;;;AAMA,SAAS,qBAAT,CAA+B,SAA/B,EAA0C,SAA1C,EAAoD;AAAA;AAAA;;AAAA,qBACrC,YAAU,SAD2B;;AAAA;AAAA;AAEnD;;AAGD,QAAQ,GAAR,CAAY,YAAZ,EAA0B,sBAAsB,KAAtB,EAA6B,KAA7B,CAA1B;;;;;ACPA,SAAS,KAAT,CAAe,CAAf,EAAiB;AAAA;;AAChB,KAAI,IAAI,CAAR,EAAW;AACV,UAAQ,GAAR,CAAY,IAAE,SAAd;AACA,EAFD,MAEK;AACJ,UAAQ,GAAR,CAAY,IAAE,UAAd;AACA;AACD;;;AAGD,MAAM,CAAN;AACA,MAAM,CAAN;AACA,MAAM,QAAN","file":"nicelib-debug.js","sourcesContent":["/**\n * This is a super function\n * @param {String} myString1 - a super string\n * @param {String} myString2 - a super string\n * @return {String} - super returned string\n */\nfunction myFunctionDeclaration(myString1, myString2){\n return myString1+myString2;\n}\n\n\nconsole.log('return is ', myFunctionDeclaration('foo', 'bar'))\n \n","/**\n * Test if it is odd\n * @param {Number} n - my number\n */\nfunction isOdd(n){\n\tif( n % 2 ){\n\t\tconsole.log(n+' is odd')\n\t}else{\n\t\tconsole.log(n+' is even')\n\t}\n}\n\n// Let's use this function\nisOdd(0)\nisOdd(1)\nisOdd('foobar')\n"]} -------------------------------------------------------------------------------- /examples/gulpfile/build/nicelib-jsdoced.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2) { 8 | console.assert(typeof myString1 === 'string', "Invalid type for argument 0 myString1"); 9 | console.assert(typeof myString2 === 'string', "Invalid type for argument 1 myString2"); 10 | 11 | var _returnValue = myString1 + myString2; 12 | 13 | console.assert(typeof _returnValue === 'string', "Invalid type for return value"); 14 | return _returnValue; 15 | } 16 | 17 | console.log('return is ', myFunctionDeclaration('foo', 'bar')); 18 | /** 19 | * Test if it is odd 20 | * @param {Number} n - my number 21 | */ 22 | function isOdd(n) { 23 | console.assert(typeof n === 'number', "Invalid type for argument 0 n"); 24 | 25 | if (n % 2) { 26 | console.log(n + ' is odd'); 27 | } else { 28 | console.log(n + ' is even'); 29 | } 30 | } 31 | 32 | // Let's use this function 33 | isOdd(0); 34 | isOdd(1); 35 | isOdd('foobar'); -------------------------------------------------------------------------------- /examples/gulpfile/build/nicelib-min.js: -------------------------------------------------------------------------------- 1 | function myFunctionDeclaration(o,n){return o+n}function isOdd(o){o%2?console.log(o+" is odd"):console.log(o+" is even")}console.log("return is ",myFunctionDeclaration("foo","bar")),isOdd(0),isOdd(1),isOdd("foobar"); -------------------------------------------------------------------------------- /examples/gulpfile/build/nicelib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2){ 8 | return myString1+myString2; 9 | } 10 | 11 | 12 | console.log('return is ', myFunctionDeclaration('foo', 'bar')) 13 | 14 | 15 | /** 16 | * Test if it is odd 17 | * @param {Number} n - my number 18 | */ 19 | function isOdd(n){ 20 | if( n % 2 ){ 21 | console.log(n+' is odd') 22 | }else{ 23 | console.log(n+' is even') 24 | } 25 | } 26 | 27 | // Let's use this function 28 | isOdd(0) 29 | isOdd(1) 30 | isOdd('foobar') 31 | -------------------------------------------------------------------------------- /examples/gulpfile/gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of gulpfile.js 3 | */ 4 | 5 | var gulp = require('gulp'); 6 | var babel = require('gulp-babel'); 7 | var concat = require('gulp-concat'); 8 | var minify = require('gulp-minify'); 9 | var filter = require('gulp-filter'); 10 | var notify = require('gulp-notify'); 11 | var eslint = require('gulp-eslint'); 12 | var sourcemaps = require('gulp-sourcemaps'); 13 | 14 | var fileNames = [ 15 | './src/*.js', 16 | ] 17 | 18 | 19 | gulp.task('default', ['build-everything']) 20 | gulp.task('build-everything', ['build-plain', 'build-debug', 'build-minify', 'build-jsdoced']) 21 | 22 | ////////////////////////////////////////////////////////////////////////////////// 23 | // Comments 24 | ////////////////////////////////////////////////////////////////////////////////// 25 | gulp.task('build-plain', function() { 26 | gulp.src(fileNames) 27 | .pipe(concat('nicelib.js')) 28 | .pipe(gulp.dest('build')) 29 | }) 30 | 31 | gulp.task('build-debug', function() { 32 | gulp.src(fileNames) 33 | .pipe(sourcemaps.init()) 34 | .pipe(eslint({ 35 | rules: { 36 | "valid-jsdoc": ["error", {"requireReturn": false}] 37 | }, 38 | })) 39 | .pipe(eslint.format()) 40 | .pipe(babel({ 41 | plugins : ['jsdoced'], 42 | compact : false 43 | })) 44 | .pipe(concat('nicelib-debug.js')) 45 | .pipe(eslint.failOnError()) // Brick on failure to be super strict 46 | .pipe(sourcemaps.write('.')) 47 | .pipe(gulp.dest('build')) 48 | }) 49 | 50 | gulp.task('build-minify', function() { 51 | gulp.src(fileNames) 52 | .pipe(concat('nicelib.js')) 53 | .pipe(minify()) 54 | .pipe(gulp.dest('build')) 55 | }) 56 | 57 | gulp.task('build-jsdoced', function() { 58 | gulp.src(fileNames) 59 | .pipe(babel({ 60 | plugins : ['jsdoced'], 61 | })) 62 | .pipe(concat('nicelib-jsdoced.js')) 63 | .pipe(gulp.dest('build')) 64 | }) 65 | -------------------------------------------------------------------------------- /examples/gulpfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nicelib", 3 | "version": "1.0.0", 4 | "description": "Fake library - example of babel-plugin-jsdoced", 5 | "main": "gulpfile.js", 6 | "devDependencies": { 7 | "gulp": "*", 8 | "gulp-babel": "^6.1.2", 9 | "gulp-concat": "^2.6.0", 10 | "gulp-eslint": "^2.0.0", 11 | "gulp-filter": "^4.0.0", 12 | "gulp-minify": "0.0.10", 13 | "gulp-notify": "^2.2.0", 14 | "gulp-sourcemaps": "^2.0.0-alpha" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/gulpfile/src/browser-sourcemaps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String} myString2 - a super string 5 | * @return {String} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2){ 8 | return myString1+myString2; 9 | } 10 | 11 | 12 | console.log('return is ', myFunctionDeclaration('foo', 'bar')) 13 | 14 | -------------------------------------------------------------------------------- /examples/gulpfile/src/isOdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test if it is odd 3 | * @param {Number} n - my number 4 | */ 5 | function isOdd(n){ 6 | if( n % 2 ){ 7 | console.log(n+' is odd') 8 | }else{ 9 | console.log(n+' is even') 10 | } 11 | } 12 | 13 | // Let's use this function 14 | isOdd(0) 15 | isOdd(1) 16 | isOdd('foobar') 17 | -------------------------------------------------------------------------------- /examples/sample-es2015.js: -------------------------------------------------------------------------------- 1 | var a = { 2 | /** 3 | * [isOdd description] 4 | * @param {Number} v [description] 5 | * @return {Boolean} [description] 6 | */ 7 | isOdd: function(v){ return v %2 ? true : false } 8 | } 9 | -------------------------------------------------------------------------------- /examples/sample-es6-arrowfunction-explicitreturn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String|Number|Function|THREE.Vector3} myString2 - a super string 5 | * @return {Number|String} - super returned string 6 | */ 7 | var myFunctionExpression = (myString1, myString2) => { 8 | console.log('now is', new Date()) 9 | return myString1+myString1+'---'+myString2; 10 | } 11 | 12 | 13 | console.log('return is ', myFunctionExpression('foo', 'bar')) 14 | -------------------------------------------------------------------------------- /examples/sample-es6-arrowfunction-implicitreturn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Number} x - super number 3 | * @return {Number} - super returned string 4 | */ 5 | var myFunctionExpression = (x) => x+1*(x+2) 6 | -------------------------------------------------------------------------------- /examples/sample-eslint-jsdoc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A description 3 | * @param {Number} num1 - The first number. 4 | * @return {Number} returned value 5 | */ 6 | function foo(num1) { 7 | // ... 8 | } 9 | -------------------------------------------------------------------------------- /examples/sample-functiondeclaration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {Number} myString2 - a super string 5 | * @return {Number} - super returned string 6 | */ 7 | function myFunctionDeclaration(myString1, myString2){ 8 | console.log('now is', new Date()) 9 | return myString1+myString1+'---'+myString2; 10 | } 11 | 12 | 13 | console.log('return is ', myFunctionDeclaration('foo', 'bar')) 14 | -------------------------------------------------------------------------------- /examples/sample-functionexpression.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a super function 3 | * @param {String} myString1 - a super string 4 | * @param {String|Number|Function|THREE.Vector3} myString2 - a super string 5 | * @return {Number|String} - super returned string 6 | */ 7 | var myFunctionExpression = function(myString1, myString2){ 8 | console.log('now is', new Date()) 9 | return myString1+myString1+'---'+myString2; 10 | } 11 | 12 | 13 | console.log('return is ', myFunctionExpression('foo', 'bar')) 14 | -------------------------------------------------------------------------------- /examples/sample-functionreturnnested.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @return {Number} sdfadf 3 | */ 4 | (function(){ 5 | return 3 6 | })() 7 | 8 | 9 | /** 10 | * @param {Number} param - sdfsdf 11 | * @return {String} myString1 - a super string 12 | */ 13 | function testFunction(param){ 14 | /** 15 | * @param {Number} myNumber sd 16 | * @return {Number} sdf 17 | */ 18 | function nestedFunction(myNumber){ 19 | return 3 20 | } 21 | 22 | function nestedFunction2(myNumber){ 23 | return 3 24 | } 25 | /** 26 | * @return {Number} adfsd 27 | */ 28 | return param+(function(){ 29 | return 3 30 | })() 31 | } 32 | -------------------------------------------------------------------------------- /examples/sample-jsx-react.js: -------------------------------------------------------------------------------- 1 | /** @jsx dom */ 2 | 3 | var { dom } = require("deku"); 4 | 5 | /** 6 | * @param {String} className - super class 7 | * @return {ReactElement} element 8 | */ 9 | function testFunction(className){ 10 | return
11 | 12 |

{[user.firstName, user.lastName].join(' ')}

13 |
; 14 | } -------------------------------------------------------------------------------- /examples/sample-property-target.js: -------------------------------------------------------------------------------- 1 | var foo = new Proxy({ 2 | /** 3 | * great salutation 4 | * @type {String} 5 | */ 6 | message: 'hello world', 7 | command: 'run', 8 | _jsdocedProperties : { 9 | message: function(value){ return typeof value === 'string' }, 10 | } 11 | }, { 12 | set: function(object, property, value) { 13 | if( object._jsdocedProperties && object._jsdocedProperties[property] ){ 14 | var checkingFunction = object._jsdocedProperties[property] 15 | var isValid = checkingFunction(value) 16 | console.assert(isValid, 'property "'+property+ '" value is invalid') 17 | } 18 | 19 | // The default behavior to store the value 20 | object[property] = value; 21 | } 22 | }) 23 | 24 | /** 25 | * @type {Boolean} 26 | */ 27 | foo._jsdocedProperties = foo._jsdocedProperties || {} 28 | foo._jsdocedProperties.bar = function(value){ return typeof value === 'boolean' } 29 | // foo.bar = 100 30 | foo.message = 'newValue' 31 | -------------------------------------------------------------------------------- /examples/sample-property.js: -------------------------------------------------------------------------------- 1 | var foo = { 2 | /** 3 | * great salutation 4 | * @type {String} 5 | */ 6 | message: 'hello world', 7 | command: 'run', 8 | } 9 | 10 | /** 11 | * @type {boolean} 12 | */ 13 | foo.bar = true; 14 | 15 | foo.bar = 'dd' 16 | -------------------------------------------------------------------------------- /examples/test-es6-proxy.js: -------------------------------------------------------------------------------- 1 | var foo = {} 2 | 3 | /** 4 | * @type {Boolean} 5 | */ 6 | foo.bar = true; 7 | 8 | foo._jsdocedProperties = foo._jsdocedProperties || {} 9 | foo._jsdocedProperties.bar = function(value){ return typeof value === 'boolean' } 10 | 11 | var Proxy = require('harmony-proxy'); 12 | 13 | foo = new Proxy(foo, { 14 | set: function(object, property, value) { 15 | if( object._jsdocedProperties && object._jsdocedProperties[property] ){ 16 | var checkingFunction = object._jsdocedProperties[property] 17 | var isValid = checkingFunction(value) 18 | console.assert('property', property, 'value', value) 19 | // console.log('checking property', property, 'isValid', isValid) 20 | } 21 | // The default behavior to store the value 22 | object[property] = value; 23 | } 24 | }) 25 | 26 | foo.bar = false 27 | foo.bar = 100 28 | -------------------------------------------------------------------------------- /examples/testbed.js: -------------------------------------------------------------------------------- 1 | debugger; 2 | 3 | var foo = { 4 | // bar : 1 5 | } 6 | 7 | foo.bar = 1 8 | 9 | console.log('this is the end') 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-jsdoced", 3 | "version": "1.2.4", 4 | "description": "It implements jsdoced javascript as a babel plugin", 5 | "main": "plugin-jsdoced-function.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "dependencies": { 10 | "babel-generator": "^6.7.5", 11 | "babel-template": "^6.7.0" 12 | }, 13 | "devDependencies": { 14 | "babel-plugin-transform-react-jsx": "^6.7.5", 15 | "babel-preset-es2015": "^6.6.0", 16 | "babel-preset-react": "^6.5.0", 17 | "babel-traverse": "^6.7.6", 18 | "deku": "^1.0.0", 19 | "eslint-config-standard": "^5.1.0", 20 | "eslint-plugin-standard": "^1.3.2", 21 | "mocha": "^2.4.5", 22 | "react": "^15.0.1", 23 | "react-dom": "^15.0.1" 24 | }, 25 | "scripts": { 26 | "test": "node_modules/.bin/mocha test/test-*.js" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/jeromeetienne/babel-plugin-jsdoced.git" 31 | }, 32 | "keywords": [ 33 | "jsdoc", 34 | "babel", 35 | "plugin", 36 | "jsx", 37 | "es6", 38 | "es2015" 39 | ], 40 | "author": "Jerome Etienne", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/jeromeetienne/babel-plugin-jsdoced/issues" 44 | }, 45 | "homepage": "https://github.com/jeromeetienne/babel-plugin-jsdoced#readme" 46 | } 47 | -------------------------------------------------------------------------------- /plugin-jsdoced-function.js: -------------------------------------------------------------------------------- 1 | var jsdocParse = require('./vendor/jsdocParse.js') 2 | 3 | ////////////////////////////////////////////////////////////////////////////////// 4 | // Comments 5 | ////////////////////////////////////////////////////////////////////////////////// 6 | 7 | var pluginOptions = { 8 | minimalReturn : true, // true if the return should be scoped in a {} IIF necessary - TODO to remove. this is useelessly complex. only in case there is a bug in this code 9 | errorType : 'assert', // 'assert' to generate error as assert, 'exception' to generate error as exception 10 | } 11 | 12 | function updatePluginOptions(state){ 13 | // NOTE: the api to handle babel plugin options is... weird to say the list 14 | if( state.opts.minimalReturn !== undefined ) pluginOptions.minimalReturn = state.opts.minimalReturn 15 | if( state.opts.errorType !== undefined ) pluginOptions.errorType = state.opts.errorType 16 | } 17 | 18 | ////////////////////////////////////////////////////////////////////////////////// 19 | // Comments 20 | ////////////////////////////////////////////////////////////////////////////////// 21 | 22 | function generateCheckCode(varType, varName, message){ 23 | var conditionString = jsdocParse.types2Conditions(varType, varName); 24 | if( pluginOptions.errorType === 'assert' ){ 25 | var str = "console.assert("+conditionString+", '"+message+"');" 26 | }else if( pluginOptions.errorType === 'exception' ){ 27 | var str = "if(!("+conditionString+")) throw new TypeError('"+message+"');" 28 | }else console.assert(false) 29 | return str 30 | } 31 | 32 | ////////////////////////////////////////////////////////////////////////////////// 33 | // Comments 34 | ////////////////////////////////////////////////////////////////////////////////// 35 | 36 | module.exports = function(babel) { 37 | var t = babel.types 38 | var contentLines; 39 | 40 | // to detect infinite traverse on returnStatement 41 | var RETURN_MARKER = Symbol(); 42 | 43 | return { 44 | visitor: { 45 | Program(path, file) { 46 | // Store the content lines to parse the jsdoc 47 | contentLines = file.file.code.split('\n') 48 | }, 49 | FunctionDeclaration : function(path, state) { 50 | updatePluginOptions(state) 51 | postProcessFunction(path) 52 | }, 53 | FunctionExpression : function(path, state) { 54 | updatePluginOptions(state) 55 | postProcessFunction(path) 56 | }, 57 | ArrowFunctionExpression : function(path, state){ 58 | updatePluginOptions(state) 59 | 60 | // Handle the implicit return case 61 | // - modify ```() => 'foo'``` into ```() => { return 'foo' }``` 62 | // - then apply the usual return rules 63 | if( path.node.body.type !== 'BlockStatement' ){ 64 | var code = '{return EXPRESSION;}' 65 | var implicitReturnTemplate = babel.template(code); 66 | var block = implicitReturnTemplate({ 67 | EXPRESSION : path.node.body 68 | }) 69 | path.node.body = block; 70 | } 71 | // sanity check - 72 | console.assert(path.node.body.type === 'BlockStatement') 73 | 74 | // call postProcessFunction - for usual return rule 75 | postProcessFunction(path) 76 | }, 77 | } 78 | } 79 | 80 | 81 | ////////////////////////////////////////////////////////////////////////////////// 82 | // Comments 83 | ////////////////////////////////////////////////////////////////////////////////// 84 | function postProcessFunction(path){ 85 | var nodeFunctionBody = path.node.body.body 86 | var functionPath = path 87 | ////////////////////////////////////////////////////////////////////////////////// 88 | // Comments 89 | ////////////////////////////////////////////////////////////////////////////////// 90 | // get jsdocJson for this node 91 | // console.log('node', path.node) 92 | var lineNumber = path.node.loc.start.line-1 93 | var jsdocJson = jsdocParse.extractJsdocJson(contentLines, lineNumber) 94 | // if no jsdocJson, do nothing 95 | if( jsdocJson === null ) return 96 | // console.error('found jsdoc', jsdocJson) 97 | 98 | ////////////////////////////////////////////////////////////////////////////////// 99 | // handle jsdocs params 100 | ////////////////////////////////////////////////////////////////////////////////// 101 | if( jsdocJson.params ){ 102 | var code = '' 103 | Object.keys(jsdocJson.params).forEach(function(varName, index){ 104 | var param = jsdocJson.params[varName] 105 | code += generateCheckCode(param.type, varName, 'Invalid type for argument '+index+' '+varName); 106 | }) 107 | // code = '{' + code + '}' 108 | var paramTemplate = babel.template(code); 109 | var block = paramTemplate() 110 | // insert the created block 111 | if( block instanceof Array ){ 112 | while( block.length > 0 ){ 113 | nodeFunctionBody.unshift(block.pop()) 114 | } 115 | }else{ 116 | nodeFunctionBody.unshift(block); 117 | } 118 | } 119 | 120 | ////////////////////////////////////////////////////////////////////////////////// 121 | // Comments 122 | ////////////////////////////////////////////////////////////////////////////////// 123 | 124 | // TODO to trap the return, do a visitor to get the return at the root of the function 125 | var visitorReturn = { 126 | ReturnStatement : function(path){ 127 | 128 | // console.error('ReturnStatement', path.parentPath.type) 129 | // When processing the 'return' path, mark it so you know you've processed it. 130 | if (path.node[RETURN_MARKER] === true ) return; 131 | 132 | // Get the parent function 133 | function getParentFunction(path){ 134 | for(var nodePath = path.parentPath; nodePath; nodePath = nodePath.parentPath ){ 135 | // console.error('node type', nodePath.node.type) 136 | var isFunction = nodePath.node.type === 'FunctionDeclaration' 137 | || nodePath.node.type === 'ArrowFunctionExpression' 138 | || nodePath.node.type === 'FunctionExpression' 139 | if( isFunction ) return nodePath 140 | } 141 | return null 142 | } 143 | var parentFunction = getParentFunction(path) 144 | if( parentFunction !== functionPath ) return; 145 | 146 | 147 | // generate code in string 148 | var code = '' 149 | code += 'var VARNAME = (RETURN_EXPRESSION);' 150 | code += generateCheckCode(jsdocJson.return.type, 'VARNAME', "Invalid type for return value"); 151 | code += 'return VARNAME;' 152 | 153 | if( pluginOptions.minimalReturn === true ){ 154 | // add scope in case of if( condition ) return 2; 155 | if( path.parentPath.type !== 'BlockStatement' ){ 156 | code = '{' + code + '}' 157 | } 158 | }else{ 159 | code = '{' + code + '}' 160 | } 161 | 162 | var returnTemplate = babel.template(code); 163 | 164 | var block = returnTemplate({ 165 | // To store the value during the assertions 166 | VARNAME : path.scope.generateUidIdentifier("returnValue"), 167 | // To reinsert the returned value from original expression 168 | RETURN_EXPRESSION : path.node.argument, 169 | }); 170 | 171 | // mark the return at the end as 'alreadyProcessed' 172 | if( block instanceof Array ){ 173 | block[block.length-1][RETURN_MARKER] = true; 174 | path.replaceWithMultiple(block); 175 | }else{ 176 | block.body[block.body.length-1][RETURN_MARKER] = true; 177 | path.replaceWith(block); 178 | } 179 | }, 180 | } 181 | if( jsdocJson.return ) path.traverse(visitorReturn); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /plugin-jsdoced-property.js: -------------------------------------------------------------------------------- 1 | /** 2 | * - very experimental 3 | */ 4 | 5 | 6 | var jsdocParse = require('./vendor/jsdocParse.js') 7 | var babelTraverse = require('babel-traverse') 8 | 9 | 10 | ////////////////////////////////////////////////////////////////////////////////// 11 | // Comments 12 | ////////////////////////////////////////////////////////////////////////////////// 13 | 14 | module.exports = function(babel) { 15 | var t = babel.types 16 | var contentLines; 17 | 18 | 19 | // to detect infinite traverse on returnStatement 20 | var OBJECT_JSDOCED_MARKER = Symbol(); 21 | 22 | return { 23 | visitor: { 24 | Program(path, file) { 25 | // Store the content lines to parse the jsdoc 26 | contentLines = file.file.code.split('\n') 27 | }, 28 | ////////////////////////////////////////////////////////////////////////////////// 29 | // Comments 30 | ////////////////////////////////////////////////////////////////////////////////// 31 | AssignmentExpression : function(path, state) { 32 | // When processing the 'return' path, mark it so you know you've processed it. 33 | if( path.node[OBJECT_JSDOCED_MARKER] ) return; 34 | 35 | 36 | var assignmentExpression = path.node; 37 | var leftExpression = assignmentExpression.left 38 | var rightExpression = assignmentExpression.right 39 | // path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go."))); 40 | // console.error('AssignmentExpression', path.node) 41 | 42 | 43 | // if leftExpression isnt a 'MemberExpression', return now 44 | if( leftExpression.type !== 'MemberExpression' ) return 45 | 46 | // check to avoid reccursive 47 | if( leftExpression.property.name === '_jsdocedProperties' ) return 48 | if( leftExpression.object.type === 'MemberExpression' && leftExpression.object.property.name === '_jsdocedProperties' ) return 49 | 50 | ////////////////////////////////////////////////////////////////////////////////// 51 | // Comments 52 | ////////////////////////////////////////////////////////////////////////////////// 53 | 54 | // if there is no code location, i can't path.node.loc 55 | if( path.node.loc === undefined ) return; 56 | 57 | var lineNumber = path.node.loc.start.line-1 58 | var jsdocJson = jsdocParse.extractJsdocJson(contentLines, lineNumber) 59 | // if no jsdocJson, do nothing 60 | if( jsdocJson === null ) return 61 | 62 | console.error('jsdocJson', jsdocJson) 63 | 64 | ////////////////////////////////////////////////////////////////////////////////// 65 | // Comments 66 | ////////////////////////////////////////////////////////////////////////////////// 67 | 68 | 69 | var conditionString = jsdocParse.types2Conditions(jsdocJson.type, 'value') 70 | 71 | var code = '' 72 | code += "OBJECT._jsdocedProperties = OBJECT._jsdocedProperties || new Object;" 73 | code += "OBJECT._jsdocedProperties.PROPERTYNAME = function(value){ return "+conditionString+" };" 74 | 75 | var template = babel.template(code); 76 | var block = template({ 77 | OBJECT: leftExpression.object, 78 | PROPERTYNAME : leftExpression.property, 79 | }) 80 | 81 | path.insertBefore(block); 82 | }, 83 | 84 | ////////////////////////////////////////////////////////////////////////////////// 85 | // Comments 86 | ////////////////////////////////////////////////////////////////////////////////// 87 | 88 | ObjectExpression : function(path){ 89 | // When processing the 'return' path, mark it so you know you've processed it. 90 | if( path.node[OBJECT_JSDOCED_MARKER] ) return; 91 | 92 | // console.error('ObjectExpression', path.node) 93 | 94 | var codeProxyInit = `new Proxy(TARGET, { 95 | set: function jsdocedPropertyTypeCheck(object, property, value) { 96 | if( object._jsdocedProperties && object._jsdocedProperties[property] ){ 97 | var checkingFunction = object._jsdocedProperties[property] 98 | var isValid = checkingFunction(value) 99 | console.assert(isValid, 'property "'+property+ '" value is invalid') 100 | } 101 | object[property] = value; 102 | } 103 | })` 104 | 105 | var template = babel.template(codeProxyInit); 106 | var block = template({ 107 | TARGET : path.node 108 | }) 109 | 110 | 111 | path.replaceWith(block) 112 | 113 | // 114 | path.traverse({ 115 | ObjectExpression: function(path){ 116 | path.node[OBJECT_JSDOCED_MARKER] = true; 117 | } 118 | }) 119 | 120 | }, 121 | ////////////////////////////////////////////////////////////////////////////////// 122 | // Comments 123 | ////////////////////////////////////////////////////////////////////////////////// 124 | 125 | ObjectProperty : function(path){ 126 | 127 | if( path.node.key.name === 'set' ) return 128 | 129 | 130 | var lineNumber = path.node.loc.start.line-1 131 | var jsdocJson = jsdocParse.extractJsdocJson(contentLines, lineNumber) 132 | // if no jsdocJson, do nothing 133 | if( jsdocJson === null ) return 134 | 135 | console.error('parentPath', path.parentPath.node) 136 | console.error('ObjectProperty', path.node) 137 | debugger; 138 | console.error('jsdocJson', jsdocJson) 139 | 140 | // check if there is a _jsdocedProperties in the parent Object 141 | console.assert(path.parentPath.node.type === 'ObjectExpression') 142 | var hasJsdocedProperties = path.parentPath.node.properties.find(function(node){ 143 | console.assert(node.type === 'ObjectProperty') 144 | var found = node.key.name === '_jsdocedProperties' ? true : false 145 | return found; 146 | }) ? true : false; 147 | console.error('hasJsdocedProperties', hasJsdocedProperties) 148 | 149 | }, 150 | 151 | ////////////////////////////////////////////////////////////////////////////////// 152 | // Comments 153 | ////////////////////////////////////////////////////////////////////////////////// 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /test/test-es6-arrowfunction.js: -------------------------------------------------------------------------------- 1 | var code = ` 2 | /** 3 | * @param {String} x - super returned string 4 | */ 5 | var testFunctionParam = (x) => { return x+2 } 6 | 7 | /** 8 | * @return {String} - super returned string 9 | */ 10 | var testFunctionReturn = (x) => x 11 | 12 | // export local variables 13 | module.exports = { 14 | testFunctionParam : testFunctionParam, 15 | testFunctionReturn: testFunctionReturn 16 | } 17 | ` 18 | 19 | ////////////////////////////////////////////////////////////////////////////// 20 | // Code Separator 21 | ////////////////////////////////////////////////////////////////////////////// 22 | 23 | var babel = require("babel-core") 24 | var result = babel.transform(code, { 25 | presets: ["es2015"], 26 | plugins: ["./plugin-jsdoced-function.js"] 27 | }); 28 | // eval the result 29 | var evalResult = eval(result.code) 30 | var testFunctionParam = evalResult.testFunctionParam 31 | var testFunctionReturn = evalResult.testFunctionReturn 32 | 33 | // console.log('code', result.code) 34 | 35 | 36 | ////////////////////////////////////////////////////////////////////////////// 37 | // Code Separator 38 | ////////////////////////////////////////////////////////////////////////////// 39 | 40 | describe('ES6 Arrow Function: arguments type', function() { 41 | it('should work with a string argument', function () { 42 | try { 43 | testFunctionParam('ddd') 44 | }catch(e){ 45 | console.assert(false) 46 | } 47 | }); 48 | it('should fail with a number argument', function () { 49 | var failed = false 50 | try { 51 | testFunctionParam(3) 52 | }catch(e){ 53 | failed = true 54 | } 55 | console.assert(failed === true) 56 | }); 57 | it('should fail with a Object argument', function () { 58 | var failed = false 59 | try { 60 | testFunctionParam({}) 61 | }catch(e){ 62 | failed = true 63 | } 64 | console.assert(failed === true) 65 | }); 66 | it('should fail with no argument', function () { 67 | var failed = false 68 | try { 69 | testFunctionParam() 70 | }catch(e){ 71 | failed = true 72 | } 73 | console.assert(failed === true) 74 | }); 75 | }); 76 | 77 | ////////////////////////////////////////////////////////////////////////////// 78 | // Code Separator 79 | ////////////////////////////////////////////////////////////////////////////// 80 | 81 | describe('ES6 Arrow Function: return type', function() { 82 | it('should work with a string return value', function () { 83 | try { 84 | testFunctionReturn('ddd') 85 | }catch(e){ 86 | console.assert(false) 87 | } 88 | }); 89 | it('should fail with a number return', function () { 90 | var failed = false 91 | try { 92 | testFunctionReturn(3) 93 | }catch(e){ 94 | failed = true 95 | } 96 | console.assert(failed === true) 97 | }); 98 | it('should fail with a Object return', function () { 99 | var failed = false 100 | try { 101 | testFunctionReturn({}) 102 | }catch(e){ 103 | failed = true 104 | } 105 | console.assert(failed === true) 106 | }); 107 | it('should fail with no return value', function () { 108 | var failed = false 109 | try { 110 | testFunctionReturn() 111 | }catch(e){ 112 | failed = true 113 | } 114 | console.assert(failed === true) 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/test-functiondeclaration.js: -------------------------------------------------------------------------------- 1 | var code = ` 2 | /** 3 | * @param {String} myString - super string 4 | */ 5 | function testFunctionParam(myString){ 6 | // ... nothing 7 | } 8 | 9 | /** 10 | * @return {String} - super string 11 | */ 12 | function testFunctionReturn(value){ 13 | return value 14 | } 15 | ` 16 | 17 | ////////////////////////////////////////////////////////////////////////////// 18 | // Code Separator 19 | ////////////////////////////////////////////////////////////////////////////// 20 | 21 | 22 | var babel = require("babel-core") 23 | var result = babel.transform(code, { 24 | plugins: ["./plugin-jsdoced-function.js"] 25 | }); 26 | // eval the result 27 | eval(result.code) 28 | 29 | // console.log('code', result.code) 30 | 31 | ////////////////////////////////////////////////////////////////////////////// 32 | // Code Separator 33 | ////////////////////////////////////////////////////////////////////////////// 34 | 35 | describe('Function declaration: arguments type', function() { 36 | it('should work with a string argument', function () { 37 | try { 38 | testFunctionParam('ddd') 39 | }catch(e){ 40 | console.assert(false) 41 | } 42 | }); 43 | it('should fail with a number argument', function () { 44 | var failed = false 45 | try { 46 | testFunctionParam(3) 47 | }catch(e){ 48 | failed = true 49 | } 50 | console.assert(failed === true) 51 | }); 52 | it('should fail with a Object argument', function () { 53 | var failed = false 54 | try { 55 | testFunctionParam({}) 56 | }catch(e){ 57 | failed = true 58 | } 59 | console.assert(failed === true) 60 | }); 61 | it('should fail with no argument', function () { 62 | var failed = false 63 | try { 64 | testFunctionParam() 65 | }catch(e){ 66 | failed = true 67 | } 68 | console.assert(failed === true) 69 | }); 70 | }); 71 | 72 | ////////////////////////////////////////////////////////////////////////////// 73 | // Code Separator 74 | ////////////////////////////////////////////////////////////////////////////// 75 | 76 | describe('Function declaration: return type', function() { 77 | it('should work with a string return value', function () { 78 | try { 79 | testFunctionReturn('ddd') 80 | }catch(e){ 81 | console.assert(false) 82 | } 83 | }); 84 | it('should fail with a number return', function () { 85 | var failed = false 86 | try { 87 | testFunctionReturn(3) 88 | }catch(e){ 89 | failed = true 90 | } 91 | console.assert(failed === true) 92 | }); 93 | it('should fail with a Object return', function () { 94 | var failed = false 95 | try { 96 | testFunctionReturn({}) 97 | }catch(e){ 98 | failed = true 99 | } 100 | console.assert(failed === true) 101 | }); 102 | it('should fail with no return value', function () { 103 | var failed = false 104 | try { 105 | testFunctionReturn() 106 | }catch(e){ 107 | failed = true 108 | } 109 | console.assert(failed === true) 110 | }); 111 | }); -------------------------------------------------------------------------------- /test/test-functionexpression.js: -------------------------------------------------------------------------------- 1 | var code = ` 2 | /** 3 | * @param {String} myString - super string 4 | */ 5 | var myFunctionExpression = function(myString){ 6 | // ... nothing 7 | } 8 | ` 9 | 10 | ////////////////////////////////////////////////////////////////////////////// 11 | // Code Separator 12 | ////////////////////////////////////////////////////////////////////////////// 13 | 14 | 15 | var babel = require("babel-core") 16 | var result = babel.transform(code, { 17 | plugins: ["./plugin-jsdoced-function.js"] 18 | }); 19 | // eval the result 20 | eval(result.code) 21 | 22 | ////////////////////////////////////////////////////////////////////////////// 23 | // Code Separator 24 | ////////////////////////////////////////////////////////////////////////////// 25 | 26 | // console.log('code', result.code) 27 | 28 | describe('Function expression: arguments type', function() { 29 | it('should work with a string argument', function () { 30 | try { 31 | myFunctionExpression('ddd') 32 | }catch(e){ 33 | console.assert(false) 34 | } 35 | }); 36 | it('should fail with a number argument', function () { 37 | var failed = false 38 | try { 39 | myFunctionExpression(3) 40 | }catch(e){ 41 | failed = true 42 | } 43 | console.assert(failed === true) 44 | }); 45 | it('should fail with a Object argument', function () { 46 | var failed = false 47 | try { 48 | myFunctionExpression({}) 49 | }catch(e){ 50 | failed = true 51 | } 52 | console.assert(failed === true) 53 | }); 54 | it('should fail with no argument', function () { 55 | var failed = false 56 | try { 57 | myFunctionExpression() 58 | }catch(e){ 59 | failed = true 60 | } 61 | console.assert(failed === true) 62 | }); 63 | }); -------------------------------------------------------------------------------- /test/test-jsx-function.js: -------------------------------------------------------------------------------- 1 | var code = ` 2 | // main.js 3 | var React = require('react'); 4 | 5 | /** 6 | * @param {String} className - super class 7 | */ 8 | function testFunctionParam(className){ 9 | } 10 | 11 | /** 12 | * @return {Object} - i cant get a better type out of React.createElement 13 | */ 14 | function testFunctionReturn(){ 15 | return
16 | 17 |
; 18 | } 19 | 20 | // export local variables 21 | module.exports = { 22 | testFunctionParam : testFunctionParam, 23 | testFunctionReturn: testFunctionReturn 24 | } 25 | ` 26 | 27 | ////////////////////////////////////////////////////////////////////////////// 28 | // Code Separator 29 | ////////////////////////////////////////////////////////////////////////////// 30 | 31 | var babel = require("babel-core") 32 | var result = babel.transform(code, { 33 | presets: ["react"], 34 | plugins: ["./plugin-jsdoced-function.js"] 35 | }); 36 | // console.log('code', result.code) 37 | // eval the result 38 | var evalResult = eval(result.code) 39 | var testFunctionParam = evalResult.testFunctionParam 40 | var testFunctionReturn = evalResult.testFunctionReturn 41 | 42 | ////////////////////////////////////////////////////////////////////////////// 43 | // Code Separator 44 | ////////////////////////////////////////////////////////////////////////////// 45 | 46 | describe('JSX Function: arguments type', function() { 47 | it('should work with a string argument', function () { 48 | try { 49 | testFunctionParam('ddd') 50 | }catch(e){ 51 | console.assert(false) 52 | } 53 | }); 54 | it('should fail with a number argument', function () { 55 | var failed = false 56 | try { 57 | testFunctionParam(3) 58 | }catch(e){ 59 | failed = true 60 | } 61 | console.assert(failed === true) 62 | }); 63 | it('should fail with a Object argument', function () { 64 | var failed = false 65 | try { 66 | testFunctionParam({}) 67 | }catch(e){ 68 | failed = true 69 | } 70 | console.assert(failed === true) 71 | }); 72 | it('should fail with no argument', function () { 73 | var failed = false 74 | try { 75 | testFunctionParam() 76 | }catch(e){ 77 | failed = true 78 | } 79 | console.assert(failed === true) 80 | }); 81 | }); 82 | 83 | ////////////////////////////////////////////////////////////////////////////// 84 | // Code Separator 85 | ////////////////////////////////////////////////////////////////////////////// 86 | 87 | describe('JSX Function: return type', function() { 88 | it('should work with a string return value', function () { 89 | try { 90 | testFunctionReturn() 91 | }catch(e){ 92 | console.assert(false) 93 | } 94 | }); 95 | }); -------------------------------------------------------------------------------- /vendor/jsdocParse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview parse jsdoc content 3 | */ 4 | 5 | /** 6 | * @namespace 7 | */ 8 | var jsdocParse = jsdocParse || {} 9 | 10 | 11 | // export to node.js 12 | if( typeof(window) === 'undefined' ) module.exports = jsdocParse; 13 | 14 | 15 | /** 16 | * parse jsdoc comment and return a 'json-ified' version of it 17 | * 18 | * @param {String} jsdocContent String containing the content 19 | * @return {Object} the json object 20 | */ 21 | jsdocParse.parseJsdoc = function(jsdocContent){ 22 | var lines = jsdocContent.split('\n') 23 | 24 | // remove first and last line 25 | lines.pop() 26 | lines.shift() 27 | 28 | // remove leading ```*``` if any 29 | for(var i = 0; i < lines.length; i++){ 30 | lines[i] = lines[i].replace(/^(\s*\*\s*)/, '') 31 | } 32 | 33 | var output = { 34 | params : {}, 35 | tags : {}, 36 | } 37 | 38 | ////////////////////////////////////////////////////////////////////////////////// 39 | // Description 40 | ////////////////////////////////////////////////////////////////////////////////// 41 | for(var i = 0; i < lines.length; i++){ 42 | var line = lines[i] 43 | var matches = line.match(/^@([^\s])+/) 44 | if( matches !== null ) continue 45 | if( output.description === undefined ){ 46 | output.description = '' 47 | } 48 | output.description += line.trim() 49 | } 50 | 51 | 52 | ////////////////////////////////////////////////////////////////////////////////// 53 | // Tags 54 | ////////////////////////////////////////////////////////////////////////////////// 55 | lines.forEach(function(line){ 56 | // console.log('line', line) 57 | // console.log('tag line', line.match(/^@/)) 58 | if( line.match(/^@/) === null ) return 59 | var matches = line.match(/^@([^\s])+/) 60 | // console.log('matches', matches) 61 | var tagName = matches[0].replace(/^@/, '').toLowerCase() 62 | 63 | // console.log('tagName', tagName ) 64 | if( tagName === 'param' ){ 65 | // param may have multiple formats 66 | // reference http://usejsdoc.org/tags-param.html 67 | 68 | // PARSE following formats 69 | // - @param {string} somebody my description goes here 70 | // - @param {string} somebody 71 | var matches = line.match(/^@([^\s]+)\s+{([^\s]+)}\s+([^\s]+)\s*(.*)$/) 72 | if( matches !== null ){ 73 | console.assert(matches.length === 5) 74 | var paramType = matches[2] 75 | var paramName = matches[3] 76 | var paramDescription = matches[4] // result is empty string 77 | output.params[paramName] = { 78 | type : canonizeType(paramType), 79 | description : paramDescription 80 | } 81 | return 82 | } 83 | 84 | // PARSE following formats 85 | // - @param somebody 86 | var matches = line.match(/^@([^\s]+)\s+([^\s]+)\s*$/) 87 | if( matches !== null ){ 88 | console.assert(matches.length === 3) 89 | var paramType = '' 90 | var paramName = matches[2] 91 | var paramDescription = '' 92 | output.params[paramName] = { 93 | type : canonizeType(paramType), 94 | description : paramDescription 95 | } 96 | return 97 | } 98 | 99 | console.assert(false, 'unknown format for @param') 100 | return 101 | }else if( tagName === 'return' || tagName === 'returns' ){ 102 | // reference http://usejsdoc.org/tags-returns.html 103 | var matches = line.match(/^@([^\s]+)\s+{([^\s]+)}\s+(.*)$/) 104 | console.assert(matches.length === 4) 105 | var paramType = matches[2] 106 | var paramDescription = matches[3] 107 | output.return = { 108 | type : canonizeType(paramType), 109 | description : paramDescription 110 | } 111 | }else if( tagName === 'type' ){ 112 | // reference http://usejsdoc.org/tags-type.html 113 | var matches = line.match(/^@([^\s]+)\s+{([^\s]+)}(.*)$/) 114 | console.assert(matches.length === 4) 115 | var paramType = matches[2] 116 | output.type = canonizeType(paramType) 117 | }else{ 118 | output.tags = output.tags || {} 119 | output.tags[tagName] = true 120 | // console.assert(false) 121 | // console.warn('unhandled tag tagName', tagName) 122 | // }else if( tagName.toLowerCase() === 'return' ){ 123 | } 124 | }) 125 | 126 | /** 127 | * canonize a @type. 128 | * - it handles a good subset of http://usejsdoc.org/tags-type.html 129 | * 130 | * @param {String} type - the type as a string from jsdoc 131 | * @return {String} The type as a string for better.js 132 | */ 133 | function canonizeType(type){ 134 | ////////////////////////////////////////////////////////////////////////////////// 135 | // Comments 136 | ////////////////////////////////////////////////////////////////////////////////// 137 | 138 | // handle the multiple param case 139 | var hasMultiple = type.split('|').length > 1 ? true : false 140 | if( hasMultiple ){ 141 | var canonizedType = '' 142 | type.split('|').forEach(function(type, index){ 143 | if( index > 0 ) canonizedType += '|' 144 | canonizedType += processOne(type) 145 | }) 146 | return canonizedType 147 | } 148 | 149 | // parse one param 150 | return processOne(type) 151 | 152 | /** 153 | * process one @type 154 | * 155 | * @param {String} paramType - the type as a string from jsdoc 156 | * @return {String} The type as a string for better.js 157 | */ 158 | function processOne(paramType){ 159 | if( paramType.toLowerCase() === 'function' ) return 'Function' 160 | if( paramType.toLowerCase() === 'object' ) return 'Object' 161 | if( paramType.toLowerCase() === 'boolean' ) return 'Boolean' 162 | if( paramType.toLowerCase() === 'number' ) return 'Number' 163 | if( paramType.toLowerCase() === 'date' ) return 'Date' 164 | if( paramType.toLowerCase() === 'array' ) return 'Array' 165 | 166 | // from http://usejsdoc.org/tags-type.html 167 | 168 | // honor "@type {?Number} - an number or null" 169 | // - return Number|null 170 | if( paramType[0] === '?' ) return canonizeType(paramType.slice(1))+'|null' 171 | // honor "@type {!Number} - an number but never null" 172 | // - return Number|"nonnull" 173 | if( paramType[0] === '!' ) return canonizeType(paramType.slice(1))+'|"nonnull"' 174 | // honor "@type {Number[]} - an array of number" 175 | if( paramType.match(/\[\]$/) ) return 'Array' 176 | // honor "@type {Array.} - an array of number" 177 | if( paramType.match(/^Array\./i) ) return 'Array' 178 | // honor "@type {Number=} - an option number" 179 | if( paramType.match(/=$/i) ) return canonizeType(paramType.slice(0,-1))+'|undefined' 180 | 181 | // if there is no paramType specified, use better.js "any" 182 | if( paramType === '' ) return '\"any\"' 183 | 184 | return paramType 185 | } 186 | } 187 | 188 | ////////////////////////////////////////////////////////////////////////////////// 189 | // post processing 190 | ////////////////////////////////////////////////////////////////////////////////// 191 | 192 | // honor output.isClass 193 | var hasConstructor = Object.getOwnPropertyNames(output.tags).indexOf('constructor') !== -1 ? true : false 194 | var hasClass = output.tags.class ? true : false 195 | output.isClass = ( hasClass || hasConstructor ) ? true : false 196 | 197 | // honor "@param {String} [myString] - this is a optional string" 198 | Object.keys(output.params).slice(0).forEach(function(paramName){ 199 | // test if it is a optional parameter 200 | var matches = paramName.match(/^\[(.*)\]$/); 201 | if( matches === null ) return 202 | // README: here the @param changes of paramName as we remove the [] 203 | var newName = matches[1] 204 | // recreate param with new name 205 | output.params[newName] = output.params[paramName] 206 | output.params[newName].type = output.params[newName].type + '|undefined' 207 | // delete old param 208 | delete output.params[paramName] 209 | }) 210 | 211 | // remove output.params if it is empty 212 | if( Object.keys(output.params).length === 0 ) delete output.params 213 | // remove output.tags if it is empty 214 | if( Object.keys(output.tags).length === 0 ) delete output.tags 215 | 216 | // return output 217 | return output 218 | 219 | } 220 | 221 | 222 | ////////////////////////////////////////////////////////////////////////////////// 223 | // Comment // 224 | ////////////////////////////////////////////////////////////////////////////////// 225 | /** 226 | * [extractJsdocContent description] 227 | * @param {String[]} lines [description] 228 | * @param {Number} bottomLine [description] 229 | * @return {Object|null} [description] 230 | */ 231 | jsdocParse.extractJsdocContent = function(lines, bottomLine){ 232 | console.assert(bottomLine >= 0) 233 | 234 | var lineEnd = bottomLine-1 235 | // skip blank lines 236 | while( lineEnd >= 0 && lines[lineEnd].match(/^\s*$/) !== null ){ 237 | lineEnd -- 238 | // console.log('skip') 239 | } 240 | if( lineEnd < 0 ) return null 241 | 242 | // check if it is the signature end 243 | if( lines[lineEnd].match(/\*\/\s*$/) === null ) return null 244 | 245 | for(var lineStart = lineEnd;lineStart >= 0; lineStart--){ 246 | var line = lines[lineStart] 247 | var matches = line.match(/^\s*\/\*\*\s*$/) 248 | var isJsdocHead = matches !== null ? true : false 249 | if( isJsdocHead === true ) break 250 | } 251 | if( lineEnd <= lineStart ) return null 252 | var jsdocContent = lines.slice(lineStart, lineEnd+1).join('\n') 253 | return jsdocContent 254 | } 255 | 256 | /** 257 | * extract jsdoc and return it as json 258 | * 259 | * @param {String[]} lines [description] 260 | * @param {Number} bottomLine [description] 261 | * @return {Object|null} [description] 262 | */ 263 | jsdocParse.extractJsdocJson = function(lines, bottomLine){ 264 | // get jsdocContent 265 | var jsdocContent = jsdocParse.extractJsdocContent(lines, bottomLine) 266 | // if no jsdocContent, do nothing 267 | if( jsdocContent === null ) return null 268 | 269 | // get json version of jsdocContent 270 | var jsdocJson = jsdocParse.parseJsdoc( jsdocContent ) 271 | 272 | return jsdocJson 273 | } 274 | 275 | 276 | jsdocParse.types2Conditions = function(type, varName){ 277 | // console.error('types2Conditions', arguments) 278 | 279 | // handle multiple types case 280 | if( type.indexOf('|') !== -1 ){ 281 | var conditions = '' 282 | type.split('|').forEach(function(type){ 283 | if( conditions.length > 0 ) conditions += ' || ' 284 | conditions += types2Conditions(type, varName) 285 | }) 286 | return conditions 287 | } 288 | 289 | // handle single type 290 | if( type.toLowerCase() === 'string' ){ 291 | return "typeof("+varName+") === 'string'"; 292 | }else if( type.toLowerCase() === 'number' ){ 293 | return "typeof("+varName+") === 'number'"; 294 | }else if( type.toLowerCase() === 'undefined' ){ 295 | return "typeof("+varName+") === 'undefined'"; 296 | }else if( type.toLowerCase() === 'function' ){ 297 | return varName+" instanceof Function"; 298 | }else if( type.toLowerCase() === 'null' ){ 299 | return varName+" === null"; 300 | }else{ 301 | return varName+" instanceof "+type; 302 | } 303 | } 304 | --------------------------------------------------------------------------------