├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .tm_properties ├── .travis.yml ├── CHANGES.md ├── Contributing.md ├── LICENSE ├── README.md ├── bin └── jsdox ├── config.json ├── docs └── tags.txt ├── fixtures ├── test2.js ├── test3.js ├── test4.js ├── test5.js ├── test6.js ├── test7.js ├── test8.js ├── under │ ├── simple.js │ └── test.js └── under_grandparent │ └── under_parent │ └── test.js ├── jsdox.js ├── lib ├── analyze.js └── generateMD.js ├── package.json ├── sample_output ├── simple.md ├── test.md ├── test2.md ├── test3.md ├── test4.md ├── test5.md ├── test6.md ├── test7.md └── test8.md ├── templates ├── class.mustache ├── file.mustache ├── function.mustache ├── index.mustache └── overview.mustache └── test ├── jsdox.js ├── lib ├── analyze.js └── generateMD.js └── mocha.opts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.mustache] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | 11 | pids 12 | logs 13 | output 14 | results 15 | 16 | node_modules 17 | npm-debug.log 18 | output 19 | 20 | ## OSX Generated Files ## 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | Icon 25 | 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear on external disk 31 | .Spotlight-V100 32 | .Trashes 33 | 34 | ## Windows Generated Files ## 35 | # Windows image file caches 36 | Thumbs.db 37 | ehthumbs.db 38 | 39 | # Folder config file 40 | Desktop.ini 41 | 42 | # Recycle Bin used on file shares 43 | $RECYCLE.BIN/ 44 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "fileExtensions": [".js"], 3 | "requireCurlyBraces": [ 4 | "if", 5 | "else", 6 | "for", 7 | "while", 8 | "do", 9 | "try", 10 | "catch" 11 | ], 12 | "requireSpaceAfterKeywords": [ 13 | "if", 14 | "else", 15 | "for", 16 | "while", 17 | "do", 18 | "switch", 19 | "case", 20 | "return", 21 | "try", 22 | "typeof" 23 | ], 24 | "disallowSpaceAfterKeywords": [ 25 | "catch" 26 | ], 27 | "requireSpaceBeforeBlockStatements": true, 28 | "requireParenthesesAroundIIFE": true, 29 | "requireSpacesInConditionalExpression": true, 30 | "disallowSpacesInNamedFunctionExpression": { 31 | "beforeOpeningRoundBrace": true 32 | }, 33 | "requireSpacesInFunctionExpression": { 34 | "beforeOpeningCurlyBrace": true 35 | }, 36 | "requireSpacesInAnonymousFunctionExpression": { 37 | "beforeOpeningCurlyBrace": true 38 | }, 39 | "requireBlocksOnNewline": 1, 40 | "disallowEmptyBlocks": true, 41 | "disallowMultipleVarDecl": true, 42 | "disallowSpacesInsideArrayBrackets": true, 43 | "disallowSpacesInsideParentheses": true, 44 | "disallowQuotedKeysInObjects": true, 45 | "disallowDanglingUnderscores": true, 46 | "disallowSpaceAfterObjectKeys": true, 47 | "requireCommaBeforeLineBreak": true, 48 | "disallowSpaceAfterPrefixUnaryOperators": true, 49 | "disallowSpaceBeforePostfixUnaryOperators": true, 50 | "disallowSpaceBeforeBinaryOperators": [ 51 | "," 52 | ], 53 | "requireSpaceBeforeBinaryOperators": true, 54 | "requireSpaceAfterBinaryOperators": true, 55 | "requireCamelCaseOrUpperCaseIdentifiers": true, 56 | "disallowKeywords": [ 57 | "with" 58 | ], 59 | "validateIndentation": 2, 60 | "disallowMixedSpacesAndTabs": true, 61 | "disallowTrailingWhitespace": true, 62 | "disallowTrailingComma": true, 63 | "disallowKeywordsOnNewLine": [ 64 | "else" 65 | ], 66 | "requireLineFeedAtFileEnd": true, 67 | "requireCapitalizedConstructors": true, 68 | "requireDotNotation": true, 69 | "disallowYodaConditions": true, 70 | "disallowNewlineBeforeBlockStatements": true, 71 | "validateLineBreaks": "LF" 72 | } 73 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | test/mocha.opts 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "proto": true, 3 | "browser": true, 4 | "curly": true, 5 | "devel": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "es5": false, 9 | "evil": false, 10 | "immed": false, 11 | "jquery": true, 12 | "latedef": false, 13 | "laxcomma": true, 14 | "newcap": true, 15 | "node": true, 16 | "noempty": true, 17 | "nonew": true, 18 | "predef": 19 | [ 20 | "after", 21 | "afterEach", 22 | "before", 23 | "beforeEach", 24 | "describe", 25 | "it", 26 | "unescape", 27 | "par", 28 | "each", 29 | "define" 30 | ], 31 | "smarttabs": true, 32 | "trailing": false, 33 | "undef": true, 34 | "strict": false, 35 | "expr": true 36 | } 37 | -------------------------------------------------------------------------------- /.tm_properties: -------------------------------------------------------------------------------- 1 | softTabs = true 2 | tabSize = 2 3 | wrapColumn = 120 4 | showWrapColumn = true 5 | 6 | projectDirectory = "$CWD" 7 | windowTitle = "$TM_DISPLAYNAME — ${CWD/^.*\///} ($TM_SCM_BRANCH)" 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 0.4.10 2 | ------ 3 | 4 | * don't crash because argv is missing with debug on (psq) 5 | * analyzer: include constructor params in class information (psq) 6 | * add support for nested params (anselmstordeur) 7 | 8 | 0.4.9 9 | ------ 10 | 11 | * fix the error that occurs when a class has no methods (gaborsar) 12 | 13 | 0.4.8 14 | ------ 15 | 16 | * fix for subdirectories with an empty parent (gaborsar) 17 | 18 | 0.4.7 19 | ------ 20 | 21 | * Process descriptions for @link tags (nicksay) 22 | 23 | 0.4.6 24 | ------ 25 | 26 | * Add options to sort the index differently (nicksay) 27 | * Add support for @namespace and @interface tags (nicksay) 28 | 29 | 0.4.5 30 | ------ 31 | 32 | * Require the latest version of JSDoc3 Parser (nicksay) 33 | 34 | 0.4.4 35 | ------ 36 | 37 | * fix undefined "argv" when using in programatic context (boneskull) 38 | 39 | 0.4.3 40 | ------ 41 | 42 | * Index generation for your generated documentation with -i (vdeturckheim) 43 | * Recursive generation of documentation with -r (ie: documentation for subdirectories is generated too) (vdeturckheim) 44 | * Respectful recursive generation with --rr (ie: the documentation for dir1/dir2/file.js will be in output_dir/dir1/dir/file.md) (vdeturckheim) 45 | * Use JSCS to check code formatting (mrjoelkemp, psq) 46 | * normalizing headers to pound format (boneskull) 47 | * use alternate horizontal rules so you don't get them confused with headers (boneskull) 48 | * adding types to members (boneskull) 49 | * types display monospace (boneskull) 50 | * classes prefixed with "Class:" (boneskull) 51 | * `@example` tag support for `@module` and `@function` (boneskull) 52 | * separate `@author` from `@license` (boneskull) 53 | 54 | 55 | 56 | 0.4.2 57 | ------ 58 | 59 | * move `analyze` and `generatedMD` in their own module (mrjoelkemp) 60 | * more tests (mrjoelkemp) 61 | * `-A` option was ignored and became the default; fixed so it is no longer the default (boneskull) 62 | * fixed issue wherein description of `@return` would become `@description` of function (duplicated) if not set for `@return` (boneskull) 63 | * removed redundant `jshint-config.json` (boneskull) 64 | * fixed `.jshintrc` (boneskull) 65 | * fixed inability to find configuration file (boneskull) 66 | * added `.editorconfig` so my editor doesn't blast trailing spaces in `.mustache` files (boneskull) 67 | * `@example` tag support (boneskull) 68 | * `@property` tag support (boneskull) 69 | * `@private` tag support (boneskull) 70 | * added types for class members (boneskull) 71 | * correctly test for `@private` (mlms13) 72 | 73 | 0.4.1 74 | ------ 75 | 76 | * fix typo in `printHelp()` (psq) 77 | 78 | 0.4.0 79 | ------ 80 | 81 | * Add missing `templateDir` option in help output (psq) 82 | * More CLI Tests (mrjoelkemp) 83 | * Support for `@requires` and `@member` (lemori) 84 | * New `--templateDir` option (lemori) 85 | * More tests: CLI, Travis Integration, JSHint integration (mrjoelkemp) 86 | * Travis badge (psq) 87 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | Contributing guidelines 2 | --- 3 | 4 | ### Maintainers 5 | 6 | * Pascal Belloncle ([@psq](https://twitter.com/psq)) 7 | * Joel Kemp ([@mrjoelkemp](https://twitter.com/mrjoelkemp)) 8 | 9 | ### Disclaimers: 10 | 11 | 1. If you want to change the stylistic output of the generated markdown, please use the `--templateDir` option to supply custom mustache templates. 12 | - Only template changes that incorporate new tags (or fix visual bugs) will be considered for review. 13 | 14 | ### Regenerate sample_output ### 15 | 16 | If your PR makes changes to the output, regenerate the sample_output content by running 17 | ``` 18 | node jsdox.js -i --rr -o sample_output/ fixtures 19 | ``` 20 | 21 | ### Update README.md and CHANGES.md ### 22 | 23 | As needed. 24 | 25 | ### Pull-requests 26 | 27 | If you fixed or added something useful to the project, you can send a pull-request that **includes unit tests** for your change(s). 28 | Your PR will be reviewed by a maintainer and accepted, or commented for rework, or declined. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 Sutoiku 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsdox [![npm](http://img.shields.io/npm/v/jsdox.svg)](https://npmjs.org/package/jsdox) [![npm](http://img.shields.io/npm/dm/jsdox.svg)](https://npmjs.org/package/jsdox) [![build status](https://travis-ci.org/sutoiku/jsdox.svg?branch=master)](https://travis-ci.org/sutoiku/jsdox) 2 | 3 | jsdox is a simple jsdoc 3 generator. It pulls documentation tags based on a subset of [jsdoc 3](http://usejsdoc.org/) from your javascript files and generates [markdown](http://daringfireball.net/projects/markdown/) files. 4 | 5 | Relies on the [JSDoc3 parser](https://github.com/mrjoelkemp/jsdoc3-parser) to get the full AST including comments. 6 | 7 | ### CLI Options 8 | 9 | Usage: `jsdox [options] ` 10 | 11 | `--config ` (alias `-c`): Configuration JSON file. 12 | 13 | `--All` (alias `-A`): Generates documentation for all available elements including internal methods. 14 | 15 | `--debug` (alias `-d`): Prints debugging information to the console. 16 | 17 | `--help` (alias `-H`): Prints help and quits. 18 | 19 | `--version` (alias `-v`): Prints the current version and quits. 20 | 21 | `--output ` (alias `-o`): Output directory. Default value is `output`. 22 | 23 | `--templateDir ` (alias `-t`): Template directory to use instead of built-in ones. 24 | 25 | `--index ` (alias `-i`): Generates an index with the documentation. An optional file name can be provided as an argument. Default value is `index`. 26 | 27 | `--index-sort `: Defines how to sort the index. Options are: `standard` (sorted by name), `namespace` (sorted by package/module and name), and `none` (not sorted). Default value is `standard`. 28 | 29 | `--recursive` (alias `-r`): Generates documentation in all subdirectories of the source folder. 30 | 31 | `--respect-recursive` (alias `--rr`): Generate subdirectories and copy the original organization of the sources. 32 | 33 | 34 | # Resources 35 | * [jsdox](http://jsdox.org) Documentation 36 | * Github [repo](https://github.com/sutoiku/jsdox) 37 | * [Changelog](https://github.com/sutoiku/jsdox/blob/master/CHANGES.md) 38 | * Issue [tracker](https://github.com/sutoiku/jsdox/issues) 39 | * Contribute by [reading the guidelines](https://github.com/sutoiku/jsdox/blob/master/Contributing.md) and creating [pull requests](https://github.com/sutoiku/jsdox/pulls)! 40 | * Run the test suite using `npm test` 41 | 42 | # Related projects 43 | * [grunt-jsdox](https://github.com/mmacmillan/grunt-jsdox) A grunt task 44 | to run jsdox on your project 45 | 46 | # Author and contributors 47 | * Pascal Belloncle (psq, Original author) 48 | * Sam Blowes (blowsie) 49 | * Todd Henderson (thenderson21) 50 | * Nic Jansma (nicjansma) 51 | * Joel Kemp (mrjoelkemp) 52 | * Ron Korving (ronkorving) 53 | * Mike MacMillan (mmacmillan) 54 | * Michael Martin-Smucker (mlms13) 55 | * Akeem McLennon (bluelaguna) 56 | * Gabor Sar (gaborsar) 57 | * Marc Trudel (stelcheck) 58 | * Anselm Stordeur (anselmstordeur) 59 | * Vladimir de Turckheim (vdeturckheim) 60 | 61 | # License 62 | 63 | jsdox.js is freely distributable under the terms of the MIT license. 64 | 65 | Copyright (c) 2012-2016 Sutoiku 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 68 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, 69 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 72 | 73 | 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 76 | -------------------------------------------------------------------------------- /bin/jsdox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var jsdox = require('../jsdox').jsdox; 4 | 5 | var argv = require('optimist') 6 | .options('output', { 7 | alias: 'o', 8 | default:'output' 9 | }) 10 | .options('config', { 11 | alias: 'c' 12 | }) 13 | .options('version', { 14 | alias: 'v' 15 | }) 16 | .options('help', { 17 | alias: 'h' 18 | }) 19 | .options('A', { 20 | alias: 'All', 21 | boolean: true 22 | }) 23 | .options('d', { 24 | alias: 'debug', 25 | boolean: true 26 | }) 27 | .options('templateDir', { 28 | alias: 't' 29 | }) 30 | .options('index', { 31 | alias: 'i' 32 | }) 33 | .options('index-sort', { 34 | default: 'standard' 35 | }) 36 | .options('recursive', { 37 | alias: 'r' 38 | }) 39 | .options('respect-recursive', { 40 | alias: 'rr' 41 | }) 42 | .argv; 43 | 44 | jsdox(argv); 45 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": "./fixtures", 3 | "output": "sample_output", 4 | "All": false, 5 | "debug": false 6 | } -------------------------------------------------------------------------------- /docs/tags.txt: -------------------------------------------------------------------------------- 1 | @constructor [ ] 2 | || @class 3 | @classdesc 4 | @constant [ ] 5 | || @const [ ] 6 | # @constructs 7 | @copyright 8 | @default [] 9 | @deprecated [] 10 | @description 11 | || @desc 12 | @enum [] 13 | # @example 14 | @throws ??? 15 | || @exception 16 | @exports ??? 17 | || @module 18 | @overview 19 | || @file 20 | || @fileoverview 21 | # @fires 22 | @global ??? 23 | @ignore 24 | @license 25 | # @memberof 26 | # @namespace 27 | @param [ ] 28 | || @arg [ ] 29 | || @ argument [ ] 30 | @private 31 | @property [ ] 32 | @protected 33 | @public 34 | @readonly 35 | @requires ?? 36 | @returns [ ] 37 | || @return [ ] 38 | @see 39 | @since ??? 40 | @summary 41 | @this [name] 42 | @type ??? 43 | @version ??? 44 | @emits 45 | @fires 46 | -------------------------------------------------------------------------------- /fixtures/test2.js: -------------------------------------------------------------------------------- 1 | /*global exports:true */ 2 | 3 | /** 4 | * Some extra text 5 | * @title Some smart title goes here 6 | * @overview This is the overview with some `markdown` included, how nice! 7 | * @copyright (c) 2012 Blah Blah Blah 8 | * @license MIT 9 | * @author Joe Schmo 10 | */ 11 | 12 | exports = { 13 | /** 14 | * @param {String} a - the first param 15 | * @param {String} b - the second param 16 | * @returns {String} the result 17 | */ 18 | func1: function(a, b) { 19 | return 1; 20 | }, 21 | 22 | /** 23 | * @param c the first param 24 | * @param d the second param 25 | * @ returns the other result 26 | */ 27 | func2: function(c, d) { 28 | return null; 29 | } 30 | 31 | }; 32 | 33 | 34 | /** 35 | * exported with dot notation 36 | * @param {String} param the parameter 37 | */ 38 | exports.exported = function(param) { 39 | return 5; 40 | }; 41 | 42 | /** 43 | * global function 44 | * @param {String} param the parameter 45 | */ 46 | var globalFunction = function(param) { 47 | return 5; 48 | }; 49 | 50 | 51 | exports.func1('test.js'); 52 | -------------------------------------------------------------------------------- /fixtures/test3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some extra text 3 | * @title Some smart title goes here 4 | * @overview This is the overview with some `markdown` included, how nice! 5 | * @copyright (c) 2012 Blah Blah Blah 6 | * @license MIT 7 | * @author Joe Schmo 8 | */ 9 | 10 | 11 | /** 12 | * Create an object object 13 | * @class Object 14 | * @member {Sheet} datasheet The object's 'Data' sheet 15 | * @member {Sheet} fieldsNumber The object's number of fields 16 | * @member {Sheet} fields The names of the object's fields 17 | */ 18 | 19 | 20 | /** 21 | * Create a record 22 | * @method create 23 | * @param {Object} values An object holding the initial values of the record's fields 24 | * @return {Object} The created record 25 | */ 26 | 27 | exports.Object.prototype.create = function(values) { 28 | try { 29 | return new exports.Record(values, this); 30 | } 31 | catch (error) { 32 | console.log('Error in crud.create() function: ' + error); 33 | throw error; 34 | } 35 | }; 36 | 37 | /** 38 | * Remove a record 39 | * @method 40 | */ 41 | 42 | exports.Object.prototype.remove = function(values) { 43 | // TBD 44 | }; 45 | -------------------------------------------------------------------------------- /fixtures/test4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some extra text 3 | * @title Some smart title goes here 4 | * @overview This is the overview with some `markdown` included, how nice! 5 | * @copyright (c) 2012 Blah Blah Blah 6 | * @license MIT 7 | * @author Joe Schmo 8 | */ 9 | 10 | /** 11 | This is a test internal function 12 | with a description on multiple lines 13 | @param {String} file filename to parse 14 | @param {Boolean} [optional] Changes behavior 15 | */ 16 | function _internalFunction(file, optional) { 17 | return false; 18 | } 19 | 20 | /** 21 | This is a test internal function 22 | with a description on multiple lines 23 | @param {String} file filename to parse 24 | @param {Boolean} [optional] Changes behavior 25 | @private 26 | */ 27 | function privateFunction(file, optional) { 28 | return false; 29 | } 30 | 31 | 32 | /** 33 | This is a test function 34 | with a description on multiple lines 35 | @param {String} file filename to parse 36 | @param {Boolean} [optional] Changes behavior 37 | */ 38 | function notAnInternalFunction(file, optional) { 39 | return true; 40 | } 41 | -------------------------------------------------------------------------------- /fixtures/test5.js: -------------------------------------------------------------------------------- 1 | /** 2 | @overview This sample will output module requires and members. 3 | @license MIT 4 | @version 0.0.1 5 | @author lemori 6 | */ 7 | 8 | /** 9 | init system configuration 10 | @module base 11 | @requires './model/settings' 12 | */ 13 | var QRCODE_DIR, ROOT, UPLOAD_DIR, init, path, settings; 14 | 15 | path = require('path'); 16 | 17 | settings = require('./model/settings'); 18 | 19 | /** @member */ 20 | 21 | ROOT = '.'; 22 | 23 | /** @member */ 24 | 25 | UPLOAD_DIR = 'upload'; 26 | 27 | /** @member */ 28 | 29 | QRCODE_DIR = 'qrcode'; 30 | 31 | 32 | /** 33 | Read global config from database 34 | @public 35 | @function init 36 | */ 37 | 38 | init = function() { 39 | console.log('init()'); 40 | }; 41 | 42 | exports.init = init; 43 | -------------------------------------------------------------------------------- /fixtures/test6.js: -------------------------------------------------------------------------------- 1 | /** 2 | @overview This sample handles namespaces, interfaces, and links. 3 | @license MIT 4 | */ 5 | 6 | 7 | /** 8 | * The top-level namespace. 9 | * @namespace 10 | */ 11 | var main = {}; 12 | 13 | 14 | /** 15 | * Initializes everything. 16 | */ 17 | main.init = function() {}; 18 | 19 | 20 | /** 21 | * Disposes everything. 22 | */ 23 | main.dispose = function() {}; 24 | 25 | 26 | /** 27 | * Definition for a Thing object used by a Worker. See {@link main.Worker}. 28 | * @interface 29 | */ 30 | main.Thing; 31 | 32 | 33 | /** 34 | * Every Thing has a name. 35 | * @type {string} 36 | */ 37 | main.Thing.prototype.name; 38 | 39 | 40 | /** 41 | * Every Thing might have some data. 42 | * @type {*|undefined} 43 | */ 44 | main.Thing.prototype.data; 45 | 46 | 47 | /** 48 | * Definition for a Worker. 49 | * @interface 50 | */ 51 | main.Worker; 52 | 53 | 54 | /** 55 | * Have a Worker do some Thing. See {@link main.Thing}. 56 | * 57 | * @param {main.Thing} thing The Thing to do. See {@link main.Thing}. 58 | */ 59 | main.Worker.prototype.do = function(thing) {}; 60 | 61 | 62 | /** 63 | * Namespace for utility functions. 64 | * @namespace 65 | */ 66 | main.util = {}; 67 | 68 | 69 | /** 70 | * Run the Foo utility. 71 | */ 72 | main.util.foo = function() {}; 73 | 74 | 75 | /** 76 | * Run the Bar utility. 77 | */ 78 | main.util.bar = function() {}; 79 | -------------------------------------------------------------------------------- /fixtures/test7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview Test classes without methods. 3 | * @license MIT 4 | * @author Gabor Sar 5 | */ 6 | 7 | /** 8 | * Test class. 9 | * 10 | * @class Test 11 | * @param {*} a First parameter. 12 | * @param {*} b Second parameter. 13 | */ 14 | function Test(a, b) { 15 | 16 | /** 17 | * @member {*} a First member. 18 | */ 19 | this.a = a; 20 | 21 | /** 22 | * @member {*} b Second member. 23 | */ 24 | this.b = b; 25 | } -------------------------------------------------------------------------------- /fixtures/test8.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @title Object params 3 | */ 4 | 5 | /** 6 | This is a test function 7 | with a object that has attributes 8 | @param {String} file filename to parse 9 | @param {Object} [options] Changes behavior 10 | @param {Boolean} options.enableOption1 should option1 be enabled 11 | @param {Boolean} options.enableOption2 should option2 be enabled 12 | */ 13 | function optionsFunction(file, options) { 14 | return true; 15 | } 16 | -------------------------------------------------------------------------------- /fixtures/under/simple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some extra text 3 | * @overview What's up? 4 | * @copyright (c) 2012 Blah Blah Blah 5 | * @license MIT 6 | * @author Joe Schmo 7 | * @version 1.0.1 8 | */ -------------------------------------------------------------------------------- /fixtures/under/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some extra text 3 | * @overview What's up? 4 | * @copyright (c) 2012 Blah Blah Blah 5 | * @license MIT 6 | * @author Joe Schmo 7 | * @version 1.0.1 8 | */ 9 | 10 | var util = require('util'); 11 | var fs = require('fs'); 12 | var jsp = require("uglify-js").parser; 13 | 14 | /** 15 | * Collection of stuff 16 | * @type {Object} 17 | */ 18 | var foo = { 19 | /** 20 | * I so cool 21 | * @return {Boolean|null} 22 | * @deprecated Not a good function 23 | */ 24 | bar: function() { 25 | return true; 26 | } 27 | }; 28 | 29 | /** 30 | This is a test function 31 | with a description on multiple lines 32 | @param {String|null} file filename to parse 33 | this parsing thing is funny business 34 | @param {Boolean|null} [optional] Changes behavior 35 | @fires module:foo#one_thing 36 | @fires module:foo#another 37 | @emits module:foo#booyah 38 | */ 39 | function testNamed(file, optional) { 40 | fs.readFile(file, function (err, data) { 41 | if (err) { 42 | throw err; 43 | } 44 | var ast = jsp.parse(data.toString()); 45 | console.log(util.inspect(ast, false, 20, true)); 46 | /* term */ 47 | // console.log(util.inspect(ast[1][0][1])); 48 | // console.log(util.inspect(ast[1][3][1])); 49 | console.log(util.inspect(ast[1][5][1])); 50 | }); 51 | } 52 | 53 | /** 54 | Can I get some description please 55 | on more than one line, if possible. 56 | @module foo 57 | */ 58 | 59 | 60 | /** 61 | function without name 62 | @function testAnonynous 63 | @returns {String} the result 64 | */ 65 | 66 | var 67 | testAnonynous = function() { 68 | return null; 69 | }, 70 | /** 71 | second function without name 72 | @returns {String} the result 73 | */ 74 | testAnon2 = function() { // #TODO 75 | return 0; 76 | } 77 | ; 78 | 79 | var multi = { 80 | /** 81 | * @param a the first param 82 | * @param b the second param 83 | * @returns the result 84 | */ 85 | func1: function(a, b) { 86 | return 1; 87 | }, 88 | 89 | /** 90 | * @param c the first param 91 | * @param d the second param 92 | * @returns the other result 93 | */ 94 | func2: function(c, d) { 95 | return null; 96 | } 97 | 98 | }; 99 | 100 | /** 101 | This is a deprecated function. 102 | @deprecated Because I said so 103 | */ 104 | function testDeprecated() { 105 | } 106 | 107 | testNamed('test.js'); 108 | 109 | /** 110 | * @description Provides chainable functions to easily build and execute a command. 111 | * @property {String} last_err Last error, if present 112 | * @class 113 | */ 114 | var Ketch = function Ketch() { 115 | 116 | /** 117 | * @description Internal array representation of this command. 118 | * @type {Array} 119 | */ 120 | this.cmd = Ketch.parseArgs.apply(null, arguments); 121 | }; 122 | 123 | /** 124 | * This is a class 125 | * @class SampleClass 126 | */ 127 | function SampleClass(parm1){ 128 | 129 | /** 130 | * A method in the class 131 | * @param a the first param 132 | * @param b the second param 133 | * @returns the result 134 | * @example 135 | * func1(1, 2) 136 | */ 137 | this.func1 = function(a, b) { 138 | return 1; 139 | }; 140 | 141 | /** 142 | function without name 143 | @function testAnonynous 144 | @returns {String} the result 145 | */ 146 | 147 | var testAnonynous = function() { 148 | return null; 149 | }; 150 | 151 | /** 152 | This is a test method 153 | with a description on multiple lines 154 | @param {String|null} file filename to parse 155 | this parsing thing is funny business 156 | @param {Boolean|null} [optional] Changes behavior 157 | @fires module:foo#one_thing 158 | @fires module:foo#another 159 | @emits module:foo#booyah 160 | */ 161 | function testNamed(file, optional) { 162 | fs.readFile(file, function (err, data) { 163 | if (err) { 164 | throw err; 165 | } 166 | var ast = jsp.parse(data.toString()); 167 | console.log(util.inspect(ast, false, 20, true)); 168 | /* term */ 169 | // console.log(util.inspect(ast[1][0][1])); 170 | // console.log(util.inspect(ast[1][3][1])); 171 | console.log(util.inspect(ast[1][5][1])); 172 | }); 173 | } 174 | } -------------------------------------------------------------------------------- /fixtures/under_grandparent/under_parent/test.js: -------------------------------------------------------------------------------- 1 | /*global exports:true */ 2 | 3 | /** 4 | * @overview This file is contained by a folder within an other folder. 5 | * @license MIT 6 | * @author Gabor Sar 7 | */ 8 | 9 | exports = { 10 | /** 11 | * Return the sum of two numbers. 12 | * @param {Number} a - the first param 13 | * @param {Number} b - the second param 14 | * @returns {Number} the result 15 | */ 16 | func1: function(a, b) { 17 | return a + b; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /jsdox.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2016 Sutoiku 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 7 | persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | var util = require('util'); 19 | var fs = require('fs'); 20 | var path = require('path'); 21 | var q = require('q'); 22 | var packageJson = require('./package.json'); 23 | var jsdocParser = require('jsdoc3-parser'); 24 | var analyze = require('./lib/analyze'); 25 | var generateMD = require('./lib/generateMD'); 26 | var index = { 27 | classes: [], 28 | functions: [] 29 | }; 30 | 31 | /** 32 | * Whether or not to print debug information. 33 | * Global to this module. 34 | * 35 | * @type {Boolean} 36 | */ 37 | var debug = false; 38 | 39 | /** 40 | * Cache of the optimist arguments list 41 | * 42 | * @type {Object} 43 | */ 44 | var argv = {}; 45 | 46 | /** 47 | * Pretty print utility 48 | * @param {Object} ast [description] 49 | * @return {String} 50 | */ 51 | function inspect(ast) { 52 | return util.inspect(ast, false, 20); 53 | } 54 | 55 | function printHelp() { 56 | console.log('Usage:\tjsdox [options] '); 57 | console.log('\tjsdox --All --output docs folder\n'); 58 | console.log('Options:'); 59 | console.log(' -c,\t--config \t\t Configuration JSON file.'); 60 | console.log(' -A,\t--All\t\t\t Generates documentation for all available elements including internal methods.'); 61 | console.log(' -d,\t--debug\t\t\t Prints debugging information to the console.'); 62 | console.log(' -H,\t--help\t\t\t Prints this message and quits.'); 63 | console.log(' -v,\t--version\t\t Prints the current version and quits.'); 64 | console.log(' -o,\t--output\t\t Output directory.'); 65 | console.log(' -t,\t--templateDir\t\t Template directory to use instead of built-in ones.'); 66 | console.log(' -i,\t--index\t\t\t Generates an index with the documentation. A file name can be provided in argument.'); 67 | console.log(' -r,\t--recursive\t\t Generates documentation in all subdirectories of the directory given as argument.'); 68 | console.log(' --rr,\t--respect-recursive\t Will generate subdirectories and copy the original organization of the sources.'); 69 | 70 | process.exit(); 71 | } 72 | 73 | function printVersion() { 74 | console.log('Version: ' + packageJson.version); 75 | process.exit(); 76 | } 77 | 78 | /** 79 | * @param {String} filename 80 | * @param {String} destination 81 | * @param {String} templateDir 82 | * @param {Function} cb 83 | * @param {Function} fileCb 84 | */ 85 | function generateForDir(filename, destination, templateDir, cb, fileCb) { 86 | var waiting = 0; 87 | var touched = 0; 88 | var error = null; 89 | 90 | var readdirSyncRec = function(dir, filelist) { 91 | var files = fs.readdirSync(dir); 92 | filelist = filelist || []; 93 | files.forEach(function(file) { 94 | if (fs.statSync(path.join(dir, file)).isDirectory()) { 95 | filelist = readdirSyncRec(path.join(dir, file), filelist); 96 | } else { 97 | filelist.push(path.join(dir, file)); 98 | } 99 | }); 100 | return filelist; 101 | }; 102 | 103 | function mkdirParentSync(dirPath) { 104 | try { 105 | fs.mkdirSync(dirPath); 106 | } catch(err) { 107 | if (err) { 108 | // parent directory not found 109 | if (err.code === "ENOENT") { 110 | fs.mkdirSync(path.dirname(dirPath)); 111 | fs.mkdirSync(dirPath); 112 | } else { 113 | throw err; 114 | } 115 | } 116 | } 117 | } 118 | 119 | function oneFile(directory, file, cb) { 120 | var fullpath; 121 | if (argv.rr) { 122 | fullpath = path.join(path.join(destination, path.dirname(file)), path.basename(file)); 123 | } else { 124 | fullpath = path.join(destination, file); 125 | } 126 | fullpath = fullpath.replace(/\.js$/, '.md'); 127 | 128 | if (debug) { 129 | console.log('Generating', fullpath); 130 | } 131 | 132 | waiting++; 133 | 134 | jsdocParser(path.join(directory, path.basename(file)), function(err, result) { 135 | if (err) { 136 | console.error('Error generating docs for file', file, err); 137 | waiting--; 138 | if (!waiting) { 139 | return cb(err); 140 | } else { 141 | error = err; 142 | } 143 | } 144 | 145 | if (debug) { 146 | console.log(file + ' AST: ', util.inspect(result, false, 20)); 147 | console.log(file + ' Analyzed: ', util.inspect(analyze(result, argv), false, 20)); 148 | } 149 | 150 | var data = analyze(result, argv); 151 | var output = generateMD(data, templateDir); 152 | 153 | if (argv.index) { 154 | for (var i = 0; i < data.functions.length; i++) { 155 | if (data.functions[i].className === undefined) { 156 | var toAddFct = data.functions[i]; 157 | toAddFct.file = path.relative(destination, fullpath); 158 | toAddFct.sourcePath = path.relative(destination, path.join(directory, path.basename(file))); 159 | index.functions.push(toAddFct); 160 | } 161 | } 162 | for (var j = 0; j < data.classes.length; j++) { 163 | if (data.functions[j] && data.functions[j].className === undefined) { 164 | var toAddClass = data.classes[j]; 165 | toAddClass.file = path.relative(destination, fullpath); 166 | toAddClass.sourcePath = path.relative(destination, path.join(directory, path.basename(file))); 167 | index.classes.push(toAddClass); 168 | } 169 | } 170 | } 171 | 172 | if (output) { 173 | fileCb && fileCb(file, data); 174 | fs.writeFile(fullpath, output, function(err) { 175 | waiting--; 176 | if (err) { 177 | console.error('Error generating docs for file', file, err); 178 | error = err; 179 | } 180 | if (!waiting) { 181 | return cb(error); 182 | } 183 | }); 184 | 185 | } else { 186 | waiting--; 187 | if (!waiting) { 188 | return cb(error); 189 | } 190 | } 191 | }); 192 | } 193 | 194 | if (filename.match(/\.js$/)) { 195 | oneFile(path.dirname(filename), path.basename(filename), cb); 196 | 197 | } else { 198 | if (argv.recursive || argv.rr) { 199 | fs.stat(filename, function (err, s) { 200 | if (!err && s.isDirectory()) { 201 | var contentList = readdirSyncRec(filename); 202 | contentList.forEach(function(fileFullPath) { 203 | if (argv.rr) { 204 | //create the sub-directories 205 | try { 206 | mkdirParentSync(path.join(destination, path.dirname(fileFullPath))); 207 | } catch(err) {} //lazy way: if the file already exists, everything is alright. 208 | try { 209 | oneFile(path.dirname(fileFullPath), fileFullPath, cb), touched++; 210 | } catch(err) { 211 | console.error('Error generating docs for files', path.basename(fileFullPath), err); 212 | return cb(err); 213 | } 214 | } else { 215 | try { 216 | oneFile(path.dirname(fileFullPath), path.basename(fileFullPath), cb), touched++; 217 | } catch(err) { 218 | console.error('Error generating docs for files', path.basename(fileFullPath), err); 219 | return cb(err); 220 | } 221 | } 222 | }); 223 | if (!touched) { 224 | cb(); 225 | } 226 | 227 | } else { 228 | cb(); 229 | } 230 | }); 231 | } else { 232 | fs.stat(filename, function (err, s) { 233 | if (!err && s.isDirectory()) { 234 | fs.readdir(filename, function (err, files) { 235 | if (err) { 236 | console.error('Error generating docs for files', filename, err); 237 | return cb(err); 238 | } 239 | files.forEach(function (file) { 240 | if (file.match(/\.js$/)) { 241 | oneFile(filename, file, cb), touched++; 242 | } 243 | }); 244 | if (!touched) { 245 | cb(); 246 | } 247 | }); 248 | } else { 249 | cb(); 250 | } 251 | }); 252 | } 253 | } 254 | } 255 | 256 | /** 257 | * @param {String} file 258 | * @param {Function} callback 259 | */ 260 | function loadConfigFile(file, argv, callback) { 261 | var config; 262 | 263 | // Check to see if file exists 264 | file = path.resolve(process.cwd(), file); 265 | 266 | fs.exists(file, function(exists) { 267 | if (exists) { 268 | try { 269 | config = require(file); 270 | } catch(err) { 271 | console.error('Error loading config file: ', err); 272 | process.exit(); 273 | } 274 | 275 | for (var key in config) { 276 | if (key !== 'input') { 277 | argv[key] = config[key]; 278 | } else { 279 | argv._[0] = config[key]; 280 | } 281 | } 282 | callback(); 283 | 284 | } else { 285 | console.error('Error loading config file: ', file); 286 | process.exit(); 287 | } 288 | }); 289 | } 290 | 291 | function main(argv) { 292 | if (typeof argv._[0] !== 'undefined') { 293 | fs.mkdir(argv.output, function() { 294 | q.all(argv._.map(function(file) { 295 | var deferred = q.defer(); 296 | 297 | generateForDir(file, argv.output, argv.templateDir, function(err) { 298 | if (err) { 299 | console.error(err); 300 | throw err; 301 | } 302 | 303 | deferred.resolve(); 304 | }); 305 | 306 | return deferred.promise; 307 | })) 308 | .then(function() { 309 | //create index 310 | if (argv.index) { 311 | var fileName; 312 | if (argv.index === true) { 313 | fileName = 'index'; 314 | } else { 315 | fileName = argv.index; 316 | } 317 | if (typeof argv.output === 'string') { 318 | fileName = path.join(argv.output, fileName); 319 | } else { 320 | fileName = path.join('output', fileName); 321 | } 322 | fs.writeFileSync(fileName + '.md', generateMD(index, argv.templateDir, true, argv['index-sort'])); 323 | } 324 | }) 325 | .then(function () { 326 | console.log('jsdox completed'); 327 | }); 328 | }); 329 | } else { 330 | console.error('Error missing input file or directory.'); 331 | printHelp(); 332 | } 333 | } 334 | 335 | function jsdox(args) { 336 | argv = args; 337 | debug = !!argv.debug; 338 | 339 | if (argv.help) { 340 | printHelp(); 341 | } 342 | 343 | if (argv.version) { 344 | printVersion(); 345 | } 346 | 347 | if (argv.config) { 348 | // @todo: refactor to not rely on argv 349 | loadConfigFile(argv.config, argv, main); 350 | } else { 351 | main(argv); 352 | } 353 | } 354 | 355 | exports.analyze = analyze; 356 | exports.generateMD = generateMD; 357 | exports.generateForDir = generateForDir; 358 | exports.jsdox = jsdox; 359 | -------------------------------------------------------------------------------- /lib/analyze.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012-2016 Sutoiku 3 | * 4 | * Transforms the AST into a form that represents a single file with modules and their functions. 5 | * 6 | * @param {Object} ast 7 | * @param {Object} argv Command-line args 8 | * @returns {Object} 9 | * 10 | * @example 11 | * { functions: 12 | * [ { name: 'testNamed', 13 | * params: [ { name: 'file', type: 'String', value: 'filename to parse' } ], 14 | * returns: '', 15 | * version: '', 16 | * description: 'This is a test function\nwith a description on multiple lines' }, 17 | * { name: 'testAnonynous', 18 | * params: [], 19 | * returns: 'the result', 20 | * version: '', 21 | * description: 'function without name', 22 | * type: 'String' } ], 23 | * methods: [], 24 | * classes: [], 25 | * modules: 26 | * [ { name: 'test_module', 27 | * functions: 28 | * [ { name: 'testAnonynous', 29 | * params: [], 30 | * returns: 'the result', 31 | * version: '', 32 | * description: 'function without name', 33 | * type: 'String' } ], 34 | * classes: [], 35 | * description: '' } ], 36 | * global_functions: 37 | * [ { name: 'testNamed', 38 | * params: [ { name: 'file', type: 'String', value: 'filename to parse' } ], 39 | * returns: '', 40 | * version: '', 41 | * description: 'This is a test function\nwith a description on multiple lines' } ], 42 | * description: 'Some extra text\nSome more extra text', 43 | * overview: 'This is the overview', 44 | * copyright: '2012 Blah Blah Blah', 45 | * license: 'MIT', 46 | * author: 'Joe Schmo', 47 | * version: '' 48 | * } 49 | */ 50 | module.exports = function(ast, argv) { 51 | var result = { 52 | functions: [], 53 | methods: [], 54 | classes: [], 55 | modules: [], 56 | members: [], 57 | globalModule: null, 58 | description: '', 59 | overview: '', 60 | copyright: '', 61 | license: '', 62 | author: '', 63 | version: '', 64 | hasMembers: false 65 | }; 66 | var currentModule = null; 67 | var currentClass = null; 68 | var currentFunction = null; 69 | var ignoreInternal = !argv.All; 70 | 71 | function initGlobalModule() { 72 | var global = {}; 73 | global.name = 'Global'; 74 | global.functions = []; 75 | global.classes = []; 76 | 77 | result.modules.push(global); 78 | result.globalModule = global; 79 | } 80 | 81 | if (!ast) { 82 | return null; 83 | } 84 | 85 | ast.forEach(function (tag) { 86 | if (ignoreInternal && isInternal(tag)) { return; } 87 | switch (tag.kind) { 88 | case 'file': 89 | result.license = tag.license; 90 | result.author = tag.author; 91 | result.copyright = tag.copyright; 92 | result.overview = tag.description; 93 | 94 | (currentFunction || result).version = tag.version; 95 | (currentFunction || result).deprecated = tag.deprecated || true; 96 | break; 97 | case 'function': 98 | if (tag.undocumented) { break; } 99 | 100 | var fn = tag; 101 | fn.params = tag.params || []; 102 | fn.params.forEach(function (p) { 103 | p.nested = (p.name.indexOf('.') > -1); 104 | }); 105 | fn.hasParams = !!fn.params.length; 106 | // For the function signature 107 | fn.paramsString = fn.params.filter(function (p) { 108 | // Filter out nested params 109 | return !p.nested; 110 | }).map(function(p) { 111 | return p.name; 112 | }).join(', '); 113 | 114 | // For param details 115 | fn.params.forEach(setPipedTypesString); 116 | fn.returns = tag.returns || []; 117 | fn.returns.forEach(function(ret) { 118 | setPipedTypesString(ret); 119 | ret.description = ret.description || false; 120 | }); 121 | // To avoid reaching to the parent for these fields 122 | fn.version = tag.version || false; 123 | fn.fires = tag.fires || []; 124 | fn.description = tag.description; 125 | fn.deprecated = tag.deprecated || false; 126 | fn.moduleName = currentModule ? currentModule.name : ''; 127 | fn.examples = tag.examples || []; 128 | currentFunction = fn; 129 | if (currentClass) { 130 | currentClass.methods.push(fn); 131 | fn.className = currentClass ? currentClass.name : ''; 132 | } else if (currentModule) { 133 | currentModule.functions.push(fn); 134 | } else { 135 | if (!result.globalModule) { 136 | initGlobalModule(); 137 | } 138 | result.globalModule.functions.push(fn); 139 | } 140 | result.functions.push(fn); 141 | break; 142 | case 'emits': 143 | case 'fires': 144 | fn.fires.push(tag.name); 145 | break; 146 | case 'member': 147 | if (currentClass && tag.undocumented !== true) { 148 | currentClass.members.push(tag); 149 | setPipedTypesString(tag); 150 | } else if (tag.scope === 'inner' && tag.undocumented !== true) { 151 | result.members.push({member: tag.name}); 152 | result.hasMembers = true; 153 | } 154 | break; 155 | case 'return': 156 | case 'returns': 157 | if (currentFunction) { 158 | currentFunction.returns = tag.text; 159 | currentFunction.type = tag.type; 160 | } 161 | break; 162 | case 'module': 163 | case 'namespace': 164 | var module = {}; 165 | module.name = tag.name; 166 | module.longname = tag.longname; 167 | module.examples = tag.examples || []; 168 | module.functions = []; 169 | module.classes = []; 170 | module.description = tag.description; 171 | module.requires = tag.requires || []; 172 | module.hasRequires = !!module.requires.length; 173 | module.requires.forEach(function(r, i) { 174 | if (!r) { return ''; } 175 | module.requires[i] = {req: r}; 176 | }); 177 | result.modules.push(module); 178 | currentModule = module; 179 | currentClass = null; 180 | break; 181 | case 'class': 182 | case 'interface': 183 | var klass = {}; 184 | klass.name = tag.name; 185 | klass.longname = tag.longname; 186 | klass.methods = []; 187 | klass.description = tag.description; 188 | klass.members = tag.properties || []; 189 | klass.members.forEach(setPipedTypesString); 190 | klass.params = tag.params || []; 191 | result.classes.push(klass); 192 | if (currentModule) { 193 | currentModule.classes.push(klass); 194 | } else { 195 | if (!result.globalModule) { 196 | initGlobalModule(); 197 | } 198 | result.globalModule.classes.push(klass); 199 | } 200 | currentClass = klass; 201 | break; 202 | } 203 | }); 204 | 205 | result.classes.forEach(function(klass) { 206 | klass.hasMembers = !!klass.members.length; 207 | }); 208 | 209 | return result; 210 | }; 211 | 212 | /** 213 | * Attaches a 'typesString' pipe-separated attribute 214 | * containing the node's types 215 | * @param {AST} node - May or may not contain a type attribute 216 | */ 217 | function setPipedTypesString(node) { 218 | if (!node.type) { return ''; } 219 | 220 | node.typesString = node.type.names.join(' | '); 221 | } 222 | 223 | function isInternal(tag) { 224 | return tag && ((tag.name && tag.name.lastIndexOf('_', 0) === 0) || tag.access === 'private'); 225 | } 226 | -------------------------------------------------------------------------------- /lib/generateMD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012-2016 Sutoiku 3 | * 4 | */ 5 | 6 | var Mustache = require('mustache'); 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | var util = require('util'); 10 | 11 | 12 | /** 13 | * Replaces {@link ...} with `[...](...)`. 14 | * @param {string} str - string to process 15 | * @param {Object} targets - map of targets to use for links (optional) 16 | * @return {string} 17 | */ 18 | function replaceLink(str, targets) { 19 | return str.replace(/\{@link\s+([^}]+)\}/g, function(full, link) { 20 | if (link in targets) { 21 | return util.format('[%s](%s)', link, targets[link]); 22 | } else if (link.match(/^(https?:)?\/\//)) { 23 | return util.format('[%s](%s)', link, link); 24 | } 25 | return link; 26 | }); 27 | } 28 | 29 | 30 | /** 31 | * Processes a tag for Markdown replacements. 32 | * @param {Object} tag - tag to process 33 | * @param {Object} targets - map of targets to use for links (optional) 34 | */ 35 | function processTag(tag, targets) { 36 | if (tag.description) { 37 | tag.description = replaceLink(tag.description, targets); 38 | } 39 | if (tag.params) { 40 | tag.params.forEach(function (param) { 41 | if (param.description) { 42 | param.description = replaceLink(param.description, targets); 43 | } 44 | }); 45 | } 46 | if (tag.members) { 47 | tag.members.forEach(function (member) { 48 | processTag(member, targets); 49 | }); 50 | } 51 | if (tag.methods) { 52 | tag.methods.forEach(function (method) { 53 | processTag(method, targets); 54 | }); 55 | } 56 | } 57 | 58 | 59 | /** 60 | * Renders markdown from the given analyzed AST 61 | * @param {Object} ast - output from analyze() 62 | * @param {String} templateDir - templates directory (optional) 63 | * @return {String} Markdown output 64 | */ 65 | module.exports = function(ast, templateDir, isIndex, sort) { 66 | if (!ast) { throw new Error('no analyzed ast to generate markdown from'); } 67 | 68 | var templates; 69 | 70 | if (!templateDir) { 71 | templateDir = path.resolve(__dirname, '../templates'); 72 | } else { 73 | templateDir = templateDir.replace(/\\/g, '/'); 74 | } 75 | 76 | var tags = (ast.modules || []) 77 | .concat(ast.classes || []) 78 | .concat(ast.functions || []); 79 | var targets = {}; 80 | tags.forEach(function (tag) { 81 | if (tag.longname) { 82 | tag.target = targets[tag.longname] = '#' + tag.longname.toLowerCase(); 83 | } 84 | }); 85 | 86 | //if ast is an index file, we need to sort the contents and to use the right templates; 87 | if (isIndex) { 88 | console.log('Now generating index'); 89 | var sortFn; 90 | if (sort === 'none') { 91 | sortFn = null; 92 | } else if (sort === 'namespace') { 93 | sortFn = function(a, b) { 94 | var namespaceA = a.longname.split('.').slice(0, -1); 95 | var namespaceB = b.longname.split('.').slice(0, -1); 96 | if (namespaceA < namespaceB) { 97 | return -1; 98 | } 99 | if (namespaceA > namespaceB) { 100 | return 1; 101 | } 102 | return a.name < b.name ? -1 : 1; 103 | }; 104 | } else { 105 | sortFn = function(a, b) { 106 | return a.name < b.name ? -1 : 1; 107 | }; 108 | } 109 | if (sortFn !== null) { 110 | ast.classes.sort(sortFn); 111 | ast.functions.sort(sortFn); 112 | } 113 | 114 | templates = { 115 | index: fs.readFileSync(templateDir + '/index.mustache', 'utf8'), 116 | class: fs.readFileSync(templateDir + '/overview.mustache', 'utf8'),//do we need different overview templates for functions or classes here ? 117 | function: fs.readFileSync(templateDir + '/overview.mustache', 'utf8') 118 | }; 119 | return Mustache.render(templates.index, ast, templates); 120 | } 121 | 122 | tags.forEach(function (tag) { 123 | processTag(tag, targets); 124 | }); 125 | 126 | templates = { 127 | file: fs.readFileSync(templateDir + '/file.mustache', 'utf8'), 128 | class: fs.readFileSync(templateDir + '/class.mustache', 'utf8'), 129 | function: fs.readFileSync(templateDir + '/function.mustache', 'utf8') 130 | }; 131 | return Mustache.render(templates.file, ast, templates); 132 | }; 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Pascal Belloncle", 4 | "url": "http://jsdox.org" 5 | }, 6 | "maintainers": [ 7 | { 8 | "name": "Marc Trudel", 9 | "email": "mtrudel@wizcorp.jp", 10 | "web": "http://www.wizcorp.jp" 11 | } 12 | ], 13 | "license": "MIT", 14 | "name": "jsdox", 15 | "description": "Simple JSDoc 3 to Markdown generator", 16 | "version": "0.4.10", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/sutoiku/jsdox.git" 20 | }, 21 | "main": "./jsdox.js", 22 | "files": [ 23 | "bin/jsdox", 24 | "jsdox.js", 25 | "LICENSE", 26 | "README.md", 27 | "templates", 28 | "lib" 29 | ], 30 | "scripts": { 31 | "lint": "node_modules/.bin/jscs jsdox.js lib test && node_modules/.bin/jshint --config .jshintrc jsdox.js lib/* bin/* test/* fixtures/*", 32 | "test": "npm run lint && node_modules/.bin/mocha;" 33 | }, 34 | "preferGlobal": true, 35 | "bin": { 36 | "jsdox": "./bin/jsdox" 37 | }, 38 | "dependencies": { 39 | "jsdoc3-parser": "^1.0.3", 40 | "mustache": "^0.8.2", 41 | "optimist": "~0.3.4", 42 | "q": "^1.0.1" 43 | }, 44 | "devDependencies": { 45 | "expect.js": "^0.3.1", 46 | "jscs": "^1.6.1", 47 | "jshint": "^2.5.3", 48 | "mocha": "^1.21.4", 49 | "sinon": "^1.10.3" 50 | }, 51 | "optionalDependencies": {}, 52 | "engines": { 53 | "node": ">=0.8.4" 54 | }, 55 | "homepage": "http://jsdox.org", 56 | "bugs": "https://github.com/sutoiku/jsdox/issues" 57 | } 58 | -------------------------------------------------------------------------------- /sample_output/simple.md: -------------------------------------------------------------------------------- 1 | * * * 2 | 3 | *(c) 2012 Blah Blah Blah* 4 | 5 | **Author:** Joe Schmo 6 | 7 | **License:** MIT 8 | 9 | **Overview:** What's up? 10 | 11 | **Version:** 1.0.1 12 | -------------------------------------------------------------------------------- /sample_output/test.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ### bar() 10 | 11 | I so cool 12 | 13 | Deprecated: Not a good function 14 | 15 | **Returns**: `Boolean | null` 16 | 17 | 18 | ### testNamed(file, optional) 19 | 20 | This is a test function 21 | with a description on multiple lines 22 | 23 | **Parameters** 24 | 25 | **file**: `String | null`, filename to parse 26 | this parsing thing is funny business 27 | 28 | **optional**: `Boolean | null`, Changes behavior 29 | 30 | **Fires**: module:foo#event:one_thing 31 | 32 | **Fires**: module:foo#event:another 33 | 34 | **Fires**: module:foo#event:booyah 35 | 36 | 37 | 38 | 39 | # foo 40 | 41 | Can I get some description please 42 | on more than one line, if possible. 43 | 44 | 45 | 46 | * * * 47 | 48 | ### foo.testAnonynous() 49 | 50 | function without name 51 | 52 | **Returns**: `String`, the result 53 | 54 | 55 | ### foo.testAnon2() 56 | 57 | second function without name 58 | 59 | **Returns**: `String`, the result 60 | 61 | 62 | ### foo.func1(a, b) 63 | 64 | Can I get some description please 65 | on more than one line, if possible. 66 | 67 | **Parameters** 68 | 69 | **a**: , the first param 70 | 71 | **b**: , the second param 72 | 73 | **Returns**: , the result 74 | 75 | 76 | ### foo.func2(c, d) 77 | 78 | Can I get some description please 79 | on more than one line, if possible. 80 | 81 | **Parameters** 82 | 83 | **c**: , the first param 84 | 85 | **d**: , the second param 86 | 87 | **Returns**: , the other result 88 | 89 | 90 | ### foo.testDeprecated() 91 | 92 | This is a deprecated function. 93 | 94 | Deprecated: Because I said so 95 | 96 | 97 | 98 | ## Class: Ketch 99 | Provides chainable functions to easily build and execute a command. 100 | 101 | **last_err**: `String` , Last error, if present 102 | **cmd**: `Array` , Internal array representation of this command. 103 | 104 | ## Class: SampleClass 105 | This is a class 106 | 107 | ### foo.SampleClass.func1(a, b) 108 | 109 | A method in the class 110 | 111 | **Parameters** 112 | 113 | **a**: , the first param 114 | 115 | **b**: , the second param 116 | 117 | **Returns**: , the result 118 | 119 | **Example**: 120 | ```js 121 | func1(1, 2) 122 | ``` 123 | 124 | ### foo.SampleClass.testAnonynous() 125 | 126 | function without name 127 | 128 | **Returns**: `String`, the result 129 | 130 | ### foo.SampleClass.testNamed(file, optional) 131 | 132 | This is a test method 133 | with a description on multiple lines 134 | 135 | **Parameters** 136 | 137 | **file**: `String | null`, filename to parse 138 | this parsing thing is funny business 139 | 140 | **optional**: `Boolean | null`, Changes behavior 141 | 142 | **Fires**: module:foo#event:one_thing 143 | 144 | **Fires**: module:foo#event:another 145 | 146 | **Fires**: module:foo#event:booyah 147 | 148 | 149 | 150 | 151 | * * * 152 | 153 | *(c) 2012 Blah Blah Blah* 154 | 155 | **Author:** Joe Schmo 156 | 157 | **License:** MIT 158 | 159 | **Overview:** What's up? 160 | 161 | **Version:** 1.0.1 162 | -------------------------------------------------------------------------------- /sample_output/test2.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ### func1(a, b) 10 | 11 | **Parameters** 12 | 13 | **a**: `String`, the first param 14 | 15 | **b**: `String`, the second param 16 | 17 | **Returns**: `String`, the result 18 | 19 | 20 | ### func2(c, d) 21 | 22 | **Parameters** 23 | 24 | **c**: , the first param 25 | 26 | **d**: , the second param 27 | @ returns the other result 28 | 29 | 30 | 31 | ### exported(param) 32 | 33 | exported with dot notation 34 | 35 | **Parameters** 36 | 37 | **param**: `String`, the parameter 38 | 39 | 40 | 41 | ### globalFunction(param) 42 | 43 | global function 44 | 45 | **Parameters** 46 | 47 | **param**: `String`, the parameter 48 | 49 | 50 | 51 | 52 | * * * 53 | 54 | *(c) 2012 Blah Blah Blah* 55 | 56 | **Author:** Joe Schmo 57 | 58 | **License:** MIT 59 | 60 | **Overview:** This is the overview with some `markdown` included, how nice! 61 | 62 | 63 | -------------------------------------------------------------------------------- /sample_output/test3.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ### create(values) 10 | 11 | Create a record 12 | 13 | **Parameters** 14 | 15 | **values**: `Object`, An object holding the initial values of the record's fields 16 | 17 | **Returns**: `Object`, The created record 18 | 19 | 20 | ### remove() 21 | 22 | Remove a record 23 | 24 | 25 | 26 | 27 | * * * 28 | 29 | *(c) 2012 Blah Blah Blah* 30 | 31 | **Author:** Joe Schmo 32 | 33 | **License:** MIT 34 | 35 | **Overview:** This is the overview with some `markdown` included, how nice! 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample_output/test4.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ### notAnInternalFunction(file, optional) 10 | 11 | This is a test function 12 | with a description on multiple lines 13 | 14 | **Parameters** 15 | 16 | **file**: `String`, filename to parse 17 | 18 | **optional**: `Boolean`, Changes behavior 19 | 20 | 21 | 22 | 23 | * * * 24 | 25 | *(c) 2012 Blah Blah Blah* 26 | 27 | **Author:** Joe Schmo 28 | 29 | **License:** MIT 30 | 31 | **Overview:** This is the overview with some `markdown` included, how nice! 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample_output/test5.md: -------------------------------------------------------------------------------- 1 | # base 2 | 3 | init system configuration 4 | 5 | **Requires:** 6 | 7 | + module:'./model/settings' 8 | 9 | **Members:** 10 | 11 | + ROOT 12 | + UPLOAD_DIR 13 | + QRCODE_DIR 14 | 15 | * * * 16 | 17 | ### base.init() 18 | 19 | Read global config from database 20 | 21 | 22 | 23 | 24 | * * * 25 | 26 | 27 | 28 | **Author:** lemori 29 | 30 | **License:** MIT 31 | 32 | **Overview:** This sample will output module requires and members. 33 | 34 | **Version:** 0.0.1 35 | -------------------------------------------------------------------------------- /sample_output/test6.md: -------------------------------------------------------------------------------- 1 | # main 2 | 3 | The top-level namespace. 4 | 5 | 6 | 7 | * * * 8 | 9 | ### main.init() 10 | 11 | Initializes everything. 12 | 13 | 14 | 15 | ### main.dispose() 16 | 17 | Disposes everything. 18 | 19 | 20 | 21 | ## Class: Thing 22 | Definition for a Thing object used by a Worker. See [main.Worker](#main.worker). 23 | 24 | **name**: `string` , Every Thing has a name. 25 | **data**: `* | undefined` , Every Thing might have some data. 26 | 27 | ## Class: Worker 28 | Definition for a Worker. 29 | 30 | ### main.Worker.do(thing) 31 | 32 | Have a Worker do some Thing. See [main.Thing](#main.thing). 33 | 34 | **Parameters** 35 | 36 | **thing**: `main.Thing`, The Thing to do. See [main.Thing](#main.thing). 37 | 38 | 39 | 40 | 41 | # util 42 | 43 | Namespace for utility functions. 44 | 45 | 46 | 47 | * * * 48 | 49 | ### util.foo() 50 | 51 | Run the Foo utility. 52 | 53 | 54 | 55 | ### util.bar() 56 | 57 | Run the Bar utility. 58 | 59 | 60 | 61 | 62 | * * * 63 | 64 | 65 | 66 | 67 | 68 | **License:** MIT 69 | 70 | **Overview:** This sample handles namespaces, interfaces, and links. 71 | 72 | 73 | -------------------------------------------------------------------------------- /sample_output/test7.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ## Class: Test 10 | Test class. 11 | 12 | **a**: `*` , First member. 13 | **b**: `*` , Second member. 14 | 15 | 16 | * * * 17 | 18 | 19 | 20 | **Author:** Gabor Sar 21 | 22 | **License:** MIT 23 | 24 | **Overview:** Test classes without methods. 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample_output/test8.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ### optionsFunction(file, options) 10 | 11 | This is a test function 12 | with a object that has attributes 13 | 14 | **Parameters** 15 | 16 | **file**: `String`, filename to parse 17 | 18 | **options**: `Object`, Changes behavior 19 | 20 | - **options.enableOption1**: `Boolean`, should option1 be enabled 21 | 22 | - **options.enableOption2**: `Boolean`, should option2 be enabled 23 | 24 | 25 | 26 | 27 | * * * 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/class.mustache: -------------------------------------------------------------------------------- 1 | {{#name}} 2 | ## Class: {{name}} 3 | {{/name}} 4 | {{#description}}{{{description}}}{{/description}} 5 | 6 | {{#members}} 7 | **{{name}}**: {{#typesString}}`{{typesString}}`{{/typesString}} {{#description}}, {{{description}}}{{/description}} 8 | {{/members}} 9 | {{#methods}} 10 | {{> function}} 11 | {{/methods}} 12 | -------------------------------------------------------------------------------- /templates/file.mustache: -------------------------------------------------------------------------------- 1 | {{#modules}} 2 | {{#name}} 3 | # {{name}} 4 | {{/name}} 5 | 6 | {{#description}}{{{description}}}{{/description}} 7 | 8 | {{#hasRequires}} 9 | **Requires:** 10 | 11 | {{#requires}} 12 | + {{{req}}} 13 | {{/requires}} 14 | {{/hasRequires}} 15 | 16 | {{#hasMembers}} 17 | **Members:** 18 | 19 | {{#members}} 20 | + {{{member}}} 21 | {{/members}} 22 | {{/hasMembers}} 23 | 24 | {{#examples}} 25 | **Example:** 26 | ```js 27 | {{{examples}}} 28 | ``` 29 | 30 | {{/examples}} 31 | * * * 32 | 33 | {{#functions}} 34 | {{> function}} 35 | 36 | {{/functions}} 37 | {{#classes}} 38 | {{> class}} 39 | 40 | {{/classes}} 41 | 42 | {{/modules}} 43 | * * * 44 | 45 | {{#copyright}}*{{copyright}}*{{/copyright}} 46 | 47 | {{#author}}**Author:** {{author}}{{/author}} 48 | 49 | {{#license}}**License:** {{license}} {{/license}} 50 | 51 | {{#overview}}**Overview:** {{{overview}}}{{/overview}} 52 | 53 | {{#version}}**Version:** {{version}}{{/version}} 54 | -------------------------------------------------------------------------------- /templates/function.mustache: -------------------------------------------------------------------------------- 1 | ### {{#moduleName}}{{moduleName}}.{{/moduleName}}{{#className}}{{className}}.{{/className}}{{name}}({{#paramsString}}{{paramsString}}{{/paramsString}}) {{#version}}{{version}}{{/version}} 2 | 3 | {{#description}} 4 | {{{description}}} 5 | 6 | {{/description}} 7 | {{#deprecated}} 8 | Deprecated: {{{deprecated}}} 9 | 10 | {{/deprecated}} 11 | {{#hasParams}} 12 | **Parameters** 13 | 14 | {{/hasParams}} 15 | {{#params}} 16 | {{#nested}} - {{/nested}}**{{name}}**: {{#typesString}}`{{typesString}}`{{/typesString}}{{#description}}, {{{description}}}{{/description}} 17 | 18 | {{/params}} 19 | {{#fires}} 20 | **Fires**: {{.}} 21 | 22 | {{/fires}} 23 | {{#returns}} 24 | **Returns**: {{#typesString}}`{{typesString}}`{{/typesString}}{{#description}}, {{{description}}}{{/description}} 25 | {{/returns}} 26 | 27 | {{#examples}} 28 | **Example**: 29 | ```js 30 | {{{examples}}} 31 | ``` 32 | 33 | {{/examples}} 34 | -------------------------------------------------------------------------------- /templates/index.mustache: -------------------------------------------------------------------------------- 1 | Functions 2 | ========= 3 | 4 | 5 | {{#functions}} 6 | {{> function}} 7 | 8 | 9 | 10 | {{/functions}} 11 | 12 | 13 | Classes 14 | ======= 15 | 16 | 17 | {{#classes}} 18 | {{> class}} 19 | 20 | {{/classes}} -------------------------------------------------------------------------------- /templates/overview.mustache: -------------------------------------------------------------------------------- 1 | ###[{{name}}]({{{file}}}): 2 | {{description}} (in [{{sourcePath}}]({{{sourcePath}}})) 3 | -------------------------------------------------------------------------------- /test/jsdox.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var expect = require('expect.js'); 3 | var fs = require('fs'); 4 | 5 | var bin = 'bin/jsdox'; 6 | 7 | describe('jsdox', function() { 8 | it('prints an error if an input file or directory is not supplied', function(done) { 9 | expectOutputFromCommand(bin, 'Error', done, true); 10 | }); 11 | 12 | it('generates non-empty output markdown files from the fixtures/ files', function(done) { 13 | var cmd = bin + ' fixtures/**.js -o sample_output'; 14 | 15 | exec(cmd, function(err, stdout, stderr) { 16 | expect(stderr).to.be.empty(); 17 | 18 | fs.readdirSync('sample_output').forEach(function(outputFile) { 19 | if (!fs.statSync('sample_output/' + outputFile).isDirectory()) { 20 | var content = fs.readFileSync('sample_output/' + outputFile).toString(); 21 | expect(content).not.to.be.empty(); 22 | } 23 | }); 24 | 25 | done(); 26 | }); 27 | }); 28 | 29 | it('generates non-empty output markdown files from the fixtures/ and the fixtures/under files', function(done) { 30 | this.timeout(5000); 31 | 32 | var cmd = bin + ' fixtures/ -o sample_output -r'; 33 | //in case an old index.md is here 34 | try { 35 | fs.unlinkSync('sample_output/index.md'); 36 | } catch(err) {} 37 | 38 | exec(cmd, function(err, stdout, stderr) { 39 | expect(stderr).to.be.empty(); 40 | 41 | var nbFiles = 0; 42 | fs.readdirSync('sample_output').forEach(function(outputFile) { 43 | if (!fs.statSync('sample_output/' + outputFile).isDirectory()) { 44 | var content = fs.readFileSync('sample_output/' + outputFile).toString(); 45 | expect(content).not.to.be.empty(); 46 | nbFiles += 1; 47 | } 48 | }); 49 | expect(nbFiles).to.be(9); 50 | 51 | done(); 52 | }); 53 | }); 54 | 55 | it('generates non-empty output markdown files from the fixtures/ and the fixtures/under and' + 56 | ' the fixtures/under_grandparent/under_parent files and an under and an under_grandparent/under_parent directory in outputs', function(done) { 57 | this.timeout(5000); 58 | 59 | var cmd = bin + ' fixtures/ -o sample_output --rr -i'; 60 | 61 | exec(cmd, function(err, stdout, stderr) { 62 | expect(stderr).to.be.empty(); 63 | 64 | var nbFilesA = 0; 65 | var nbFilesB = 0; 66 | var nbFilesC = 0; 67 | 68 | fs.readdirSync('sample_output/fixtures').forEach(function(outputFile) { 69 | if (!fs.statSync('sample_output/fixtures/' + outputFile).isDirectory()) { 70 | if (!fs.statSync('sample_output/' + outputFile).isDirectory()) { 71 | var content = fs.readFileSync('sample_output/fixtures/' + outputFile).toString(); 72 | expect(content).not.to.be.empty(); 73 | nbFilesA += 1; 74 | //clean for future tests 75 | fs.unlinkSync('sample_output/fixtures/' + outputFile); 76 | } 77 | } 78 | }); 79 | expect(nbFilesA).to.be(7); 80 | 81 | fs.readdirSync('sample_output/fixtures/under').forEach(function(outputFile) { 82 | if (!fs.statSync('sample_output/fixtures/under/' + outputFile).isDirectory()) { 83 | var content = fs.readFileSync('sample_output/fixtures/under/' + outputFile).toString(); 84 | expect(content).not.to.be.empty(); 85 | nbFilesB += 1; 86 | fs.unlinkSync('sample_output/fixtures/under/' + outputFile); 87 | } 88 | }); 89 | expect(nbFilesB).to.be(2); 90 | 91 | fs.readdirSync('sample_output/fixtures/under_grandparent/under_parent').forEach(function(outputFile) { 92 | if (!fs.statSync('sample_output/fixtures/under_grandparent/under_parent/' + outputFile).isDirectory()) { 93 | var content = fs.readFileSync('sample_output/fixtures/under_grandparent/under_parent/' + outputFile).toString(); 94 | expect(content).not.to.be.empty(); 95 | nbFilesC += 1; 96 | fs.unlinkSync('sample_output/fixtures/under_grandparent/under_parent/' + outputFile); 97 | } 98 | }); 99 | expect(nbFilesC).to.be(1); 100 | 101 | fs.rmdirSync('sample_output/fixtures/under_grandparent/under_parent/'); 102 | fs.rmdirSync('sample_output/fixtures/under_grandparent/'); 103 | fs.rmdirSync('sample_output/fixtures/under/'); 104 | fs.rmdirSync('sample_output/fixtures/'); 105 | 106 | done(); 107 | }); 108 | }); 109 | 110 | it('generates non-empty output markdown files from the fixtures/ and the fixtures/under files and index.md', function(done) { 111 | this.timeout(5000); 112 | 113 | var cmd = bin + ' fixtures/ -o sample_output -r -i'; 114 | 115 | exec(cmd, function(err, stdout, stderr) { 116 | expect(stderr).to.be.empty(); 117 | 118 | var nbFiles = 0; 119 | var hasIndex = false; 120 | fs.readdirSync('sample_output').forEach(function(outputFile) { 121 | if (fs.lstatSync('sample_output/' + outputFile).isFile()) { 122 | var content = fs.readFileSync('sample_output/' + outputFile).toString(); 123 | expect(content).not.to.be.empty(); 124 | nbFiles += 1; 125 | hasIndex = hasIndex || (outputFile === 'index.md'); 126 | } 127 | }); 128 | expect(nbFiles).to.be(10); 129 | expect(hasIndex).to.be(true); 130 | //clean index for other tests 131 | fs.unlinkSync('sample_output/index.md'); 132 | 133 | done(); 134 | }); 135 | }); 136 | 137 | describe('cli options', function() { 138 | it('prints the help menu with the -H option', function(done) { 139 | expectOutputFromCommand(bin + ' -H', 'Usage:', done); 140 | }); 141 | 142 | it('prints the version with the -v option', function(done) { 143 | expectOutputFromCommand(bin + ' -v', require('../package.json').version, done); 144 | }); 145 | 146 | it('accepts a custom template directory with the -t option'); 147 | 148 | describe('-o option', function() { 149 | it('converts an input file to an output markdown file'); 150 | it('converts an input directory of files to an output directory of markdown files'); 151 | }); 152 | }); 153 | }); 154 | 155 | /** 156 | * Helper for asserting that the output from running jsdox from the cli 157 | * contains a given string 158 | * @param {String} cmd - The command to execute 159 | * @param {String} output - The string that should be in the output 160 | * @param {Function} done - Executed when the exec is finished 161 | * @param {Boolean} isError - Whether or not to check stderr instead 162 | */ 163 | function expectOutputFromCommand(cmd, output, done, isError) { 164 | exec(cmd, function(err, stdout, stderr) { 165 | var stream = isError ? stderr : stdout; 166 | expect(stream.indexOf(output) !== -1).to.be(true); 167 | done(); 168 | }); 169 | } 170 | -------------------------------------------------------------------------------- /test/lib/analyze.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var sinon = require('sinon'); 3 | var analyze = require('../../lib/analyze'); 4 | var jsdoc = require('jsdoc3-parser'); 5 | 6 | describe('analyze', function() { 7 | it.skip('takes a JSDoc ast and returns a transformed AST', function() { 8 | // Get parser for one of the fixtures 9 | // Generate the AST using jsdoc 10 | // Pass to analyze 11 | // expect(typeof analyze(ast)).to.be('object'); 12 | }); 13 | 14 | describe('aggregation', function() { 15 | it.skip('groups all functions'); 16 | it.skip('groups all methods'); 17 | it.skip('groups all classes'); 18 | it.skip('groups all private members'); 19 | }); 20 | 21 | describe('Supported JSDoc tags', function() { 22 | describe('file-level tags', function() { 23 | it.skip('captures @description', function() { 24 | // Feed in a snippet to jsdoc to get a simple ast that has the @description tag 25 | // Pass to analyze 26 | // expect(analyze(ast).overview).not.to.be(undefined); 27 | }); 28 | it.skip('captures @overview'); 29 | it.skip('captures @license'); 30 | it.skip('captures @author'); 31 | it.skip('captures @copyright'); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/lib/generateMD.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var sinon = require('sinon'); 3 | var generateMD = require('../../lib/generateMD'); 4 | var Mustache = require('mustache'); 5 | var fs = require('fs'); 6 | 7 | describe('generateMD', function() { 8 | // In case we need to restore earlier 9 | function restore() { 10 | if (fs.readFileSync.restore) { fs.readFileSync.restore(); } 11 | if (Mustache.render.restore) { Mustache.render.restore(); } 12 | } 13 | 14 | beforeEach(function() { 15 | sinon.stub(fs, 'readFileSync'); 16 | sinon.stub(Mustache, 'render'); 17 | }); 18 | 19 | afterEach(function() { 20 | restore(); 21 | }); 22 | 23 | it('accepts a custom template directory', function() { 24 | var custom = '../template'; 25 | generateMD([], custom); 26 | expect(fs.readFileSync.args[0][0].indexOf(custom) !== -1).to.be(true); 27 | }); 28 | 29 | it('defaults to the library\'s template directory if a custom one is not supplied', function() { 30 | generateMD([]); 31 | expect(fs.readFileSync.args[0][0].indexOf('templates') !== -1).to.be(true); 32 | }); 33 | 34 | it('renders a given ast with Mustache', function() { 35 | generateMD([]); 36 | expect(Mustache.render.called).to.be(true); 37 | }); 38 | 39 | it('returns a string representing the generated markdown for a given ast', function() { 40 | restore(); 41 | 42 | var analyzed = { 43 | functions: [], 44 | methods: [], 45 | classes: [], 46 | modules: [], 47 | members: [], 48 | globalModule: null, 49 | globalVariables: [], 50 | description: '', 51 | overview: 'What\'s up?', 52 | copyright: '(c) 2012 Blah Blah Blah', 53 | license: 'MIT', 54 | author: ['Joe Schmo'], 55 | version: '1.0.1', 56 | hasMembers: false, 57 | deprecated: true 58 | }; 59 | 60 | expect(typeof generateMD(analyzed)).to.be('string'); 61 | }); 62 | 63 | it('throws an error if an ast is not supplied', function() { 64 | expect(generateMD).to.throwError(); 65 | }); 66 | 67 | it('sorts index classes and functions by name', function() { 68 | var analyzed = { 69 | functions: [ 70 | {name: 'zero', longname: 'zero' }, 71 | {name: 'one', longname: 'foo.one' }, 72 | {name: 'two', longname: 'bar.two' }, 73 | {name: 'three', longname: 'bar.three' }, 74 | {name: 'four', longname: 'foo.four' } 75 | ], 76 | classes: [ 77 | {name: 'Five', longname: 'Five' }, 78 | {name: 'Six', longname: 'bar.Six' } 79 | ] 80 | }; 81 | 82 | generateMD(analyzed, null, true, 'standard'); 83 | 84 | expect(analyzed.functions).to.eql([ 85 | {name: 'four', longname: 'foo.four', target: '#foo.four' }, 86 | {name: 'one', longname: 'foo.one', target: '#foo.one' }, 87 | {name: 'three', longname: 'bar.three', target: '#bar.three' }, 88 | {name: 'two', longname: 'bar.two', target: '#bar.two' }, 89 | {name: 'zero', longname: 'zero', target: '#zero' } 90 | ]); 91 | expect(analyzed.classes).to.eql([ 92 | {name: 'Five', longname: 'Five', target: '#five' }, 93 | {name: 'Six', longname: 'bar.Six', target: '#bar.six' } 94 | ]); 95 | }); 96 | 97 | it('sorts index classes and functions by namespace', function() { 98 | var analyzed = { 99 | functions: [ 100 | {name: 'zero', longname: 'zero' }, 101 | {name: 'one', longname: 'foo.one' }, 102 | {name: 'two', longname: 'bar.two' }, 103 | {name: 'three', longname: 'bar.three' }, 104 | {name: 'four', longname: 'foo.four' } 105 | ], 106 | classes: [ 107 | {name: 'Five', longname: 'Five' }, 108 | {name: 'Six', longname: 'bar.Six' } 109 | ] 110 | }; 111 | 112 | generateMD(analyzed, null, true, 'namespace'); 113 | 114 | expect(analyzed.functions).to.eql([ 115 | {name: 'zero', longname: 'zero', target: '#zero' }, 116 | {name: 'three', longname: 'bar.three', target: '#bar.three' }, 117 | {name: 'two', longname: 'bar.two', target: '#bar.two' }, 118 | {name: 'four', longname: 'foo.four', target: '#foo.four' }, 119 | {name: 'one', longname: 'foo.one', target: '#foo.one' } 120 | ]); 121 | expect(analyzed.classes).to.eql([ 122 | {name: 'Five', longname: 'Five', target: '#five' }, 123 | {name: 'Six', longname: 'bar.Six', target: '#bar.six' } 124 | ]); 125 | }); 126 | 127 | it('leaves index classes and functions unsorted', function() { 128 | var analyzed = { 129 | functions: [ 130 | {name: 'zero', longname: 'zero' }, 131 | {name: 'one', longname: 'foo.one' }, 132 | {name: 'two', longname: 'bar.two' }, 133 | {name: 'three', longname: 'bar.three' }, 134 | {name: 'four', longname: 'foo.four' } 135 | ], 136 | classes: [ 137 | {name: 'Five', longname: 'Five' }, 138 | {name: 'Six', longname: 'bar.Six' } 139 | ] 140 | }; 141 | 142 | generateMD(analyzed, null, true, 'none'); 143 | 144 | expect(analyzed.functions).to.eql([ 145 | {name: 'zero', longname: 'zero', target: '#zero' }, 146 | {name: 'one', longname: 'foo.one', target: '#foo.one' }, 147 | {name: 'two', longname: 'bar.two', target: '#bar.two' }, 148 | {name: 'three', longname: 'bar.three', target: '#bar.three' }, 149 | {name: 'four', longname: 'foo.four', target: '#foo.four' } 150 | ]); 151 | expect(analyzed.classes).to.eql([ 152 | {name: 'Five', longname: 'Five', target: '#five' }, 153 | {name: 'Six', longname: 'bar.Six', target: '#bar.six' } 154 | ]); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/jsdox.js test/lib/*.js 2 | --reporter dot 3 | --ui bdd 4 | --growl --------------------------------------------------------------------------------