├── .npmrc ├── test ├── fixtures │ ├── description-no-tags.js │ ├── tags-malformed-trailing.js │ ├── tags-malformed-middle.js │ ├── description-tag-middle2.js │ ├── description-tag-middle.js │ ├── examples-multiple.js │ ├── markdown.js │ ├── examples-gfm-no-stars.js │ ├── description-tag.js │ ├── multiline.js │ ├── example-large.js │ └── huge.js ├── test.js ├── support │ └── index.js ├── examples.js ├── examples-javadoc.js ├── examples-indented.js ├── tokenize.js ├── tags.js ├── multiline.js └── examples-gfm.js ├── .travis.yml ├── .gitattributes ├── .editorconfig ├── .gitignore ├── examples.js ├── index.js ├── LICENSE ├── lib ├── utils.js └── tokenize.js ├── package.json ├── .github └── contributing.md ├── .eslintrc.json ├── .verb.md └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /test/fixtures/description-no-tags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * documentMode is an IE-only property 3 | * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 4 | */ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | os: 3 | - linux 4 | - osx 5 | - windows 6 | language: node_js 7 | node_js: 8 | - node 9 | - '11' 10 | - '10' 11 | - '9' 12 | - '8' 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /test/fixtures/tags-malformed-trailing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | * @param {*} obj 4 | * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 5 | * String ...) 6 | */ -------------------------------------------------------------------------------- /test/fixtures/tags-malformed-middle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | * @param {*} obj 4 | * @param {*} obj true if `obj` is an array or array-like object (NodeList, Arguments, 5 | * String ...) 6 | * @return {boolean} 7 | */ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [{**/{actual,fixtures,expected,templates}/**,*.md}] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # always ignore files 2 | *.DS_Store 3 | .idea 4 | .vscode 5 | *.sublime-* 6 | 7 | # test related, or directories generated by tests 8 | test/actual 9 | actual 10 | coverage 11 | .nyc* 12 | 13 | # npm 14 | node_modules 15 | npm-debug.log 16 | 17 | # yarn 18 | yarn.lock 19 | yarn-error.log 20 | 21 | # misc 22 | _gh_pages 23 | _draft 24 | _drafts 25 | bower_components 26 | vendor 27 | temp 28 | tmp 29 | TODO.md 30 | package-lock.json -------------------------------------------------------------------------------- /test/fixtures/description-tag-middle2.js: -------------------------------------------------------------------------------- 1 | // from angular code (@license AngularJS v1.5.0-rc.0 (c) 2010-2015 Google, Inc. http://angularjs.org License: MIT) 2 | /** 3 | * @ngdoc function 4 | * @name angular.lowercase 5 | * @module ng 6 | * @kind function 7 | * 8 | * @description Converts the specified string to lowercase. 9 | * @param {string} string String to be converted to lowercase. 10 | * @returns {string} Lowercased string. 11 | */ -------------------------------------------------------------------------------- /examples.js: -------------------------------------------------------------------------------- 1 | const extract = require('extract-comments'); 2 | const tokenize = require('./'); 3 | const comments = extract([ 4 | '/**', 5 | ' * foo bar baz', 6 | ' * ', 7 | ' * ```js', 8 | ' * const foo = "bar";', 9 | ' * ```', 10 | ' *', 11 | ' * @param {string} something', 12 | ' * @param {string} else', 13 | ' */', 14 | '', 15 | 'function foo() {}', 16 | '', 17 | ].join('\n')); 18 | 19 | const tok = tokenize(comments[0]); 20 | console.log(tok); 21 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const tokenize = require('..'); 6 | 7 | describe('tokenize-comment', function() { 8 | describe('sound check', function() { 9 | it('should export a function', function() { 10 | assert.equal(typeof tokenize, 'function'); 11 | }); 12 | }); 13 | 14 | describe('error handling', function() { 15 | it('should throw an error when invalid args are passed', function() { 16 | assert.throws(() => tokenize(), /expected input to be a string/); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/fixtures/description-tag-middle.js: -------------------------------------------------------------------------------- 1 | // from angular code (@license AngularJS v1.5.0-rc.0 (c) 2010-2015 Google, Inc. http://angularjs.org License: MIT) 2 | /** 3 | * @ngdoc module 4 | * @name ng 5 | * @module ng 6 | * @description 7 | * 8 | * # ng (core module) 9 | * The ng module is loaded by default when an AngularJS application is started. The module itself 10 | * contains the essential components for an AngularJS application to function. The table below 11 | * lists a high level breakdown of each of the services/factories, filters, directives and testing 12 | * components available within this core module. 13 | * 14 | *
15 | */ -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tokenize = require('./lib/tokenize'); 4 | const utils = require('./lib/utils'); 5 | const { define, typeOf } = utils; 6 | 7 | module.exports = function(input, options = {}) { 8 | let state = { description: '', footer: '', examples: [], tags: [] }; 9 | 10 | if (typeOf(input) === 'object' && input.raw) { 11 | state = { ...input, ...state }; 12 | input = input.raw; 13 | } 14 | 15 | if (typeof input !== 'string') { 16 | throw new TypeError('expected input to be a string'); 17 | } 18 | 19 | const str = options.stripStars !== false ? utils.stripStars(input) : input; 20 | const ast = tokenize(str, options, state); 21 | define(state, 'ast', ast); 22 | return state; 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/examples-multiple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a comment with 3 | * several lines of text. 4 | * 5 | * An example 6 | * 7 | * ```js 8 | * var foo = bar; 9 | * var foo = bar; 10 | * var foo = bar; 11 | * ``` 12 | * 13 | * Another example 14 | * 15 | * var baz = fez; 16 | * var baz = fez; 17 | * var baz = fez; 18 | * 19 | * Another example 20 | * 21 | * var baz = fez; 22 | * var baz = fez; 23 | * 24 | * 25 | * 26 | * And another example 27 | * 28 | * ```js 29 | * var foo = bar; 30 | * var foo = bar; 31 | * ``` 32 | * 33 | * Another example 34 | * 35 | * @example 36 | * var baz = fez; 37 | * 38 | * @example 39 | * // this is a comment 40 | * var alalla = zzzz; 41 | * 42 | * @param {String} foo bar 43 | * @returns {Object} Instance of Foo 44 | * @api public 45 | */ 46 | -------------------------------------------------------------------------------- /test/fixtures/markdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set a parser that can later be used to parse any given string. 3 | * 4 | * ```js 5 | * // foo.parser(name, replacements) 6 | * foo.parser("foo", function(a, b, c) { 7 | * // body... 8 | * }) 9 | * ``` 10 | * 11 | * This is arbitrary text. 12 | * 13 | * * This is arbitrary text. 14 | * * This is arbitrary text. 15 | * * This is arbitrary text. 16 | * 17 | * **Example** 18 | * 19 | * {%= docs("example-parser.md") %} 20 | * 21 | * This is a another description after the example. 22 | * 23 | * @param {String} `alpha` 24 | * @param {Object|Array} `arr` Object or array of replacement patterns to associate. 25 | * @property {String|RegExp} [arr] `pattern` 26 | * @property {String|Function} [arr] `replacement` 27 | * @param {String} `beta` 28 | * @property {Array} [beta] `foo` This is foo option. 29 | * @property {Array} [beta] `bar` This is bar option 30 | * @return {Strings} to allow chaining 31 | * @api public 32 | */ 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018, Jon Schlinkert. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/support/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var stringify = require('stringify-object'); 6 | var extract = require('extract-comments'); 7 | var writeFile = require('write'); 8 | var tokenize = require('../..'); 9 | 10 | exports.files = function() { 11 | var cwd = path.resolve.apply(path, arguments); 12 | var files = fs.readdirSync(cwd); 13 | var res = {}; 14 | 15 | for (var i = 0; i < files.length; i++) { 16 | var name = files[i]; 17 | var fp = path.resolve(cwd, name); 18 | res[name.slice(0, -3)] = fs.readFileSync(fp, 'utf8'); 19 | } 20 | return res; 21 | }; 22 | 23 | exports.generate = function(name, dest) { 24 | var fixtures = exports.files(__dirname, '../fixtures'); 25 | var comments = extract(fixtures[name]).filter(function(comment) { 26 | return comment.type === 'BlockComment'; 27 | }); 28 | 29 | var res = ''; 30 | 31 | for (var i = 0; i < comments.length; i++) { 32 | var raw = '\n\n/*' + comments[i].raw + '*/\n'; 33 | var tok = stringify(tokenize(raw), {indent: ' '}); 34 | tok = tok.replace(/\s*Node\s*/g, ''); 35 | res += raw; 36 | res += `\nassert.deepEqual(tokenize(comments[${i}].raw), ${tok});`; 37 | } 38 | 39 | writeFile.sync(dest, res.trim()); 40 | }; 41 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.stripStars = function(str) { 4 | // [^\S\r\n\u2028\u2029] 5 | const res = str.replace(/\t/g, ' ') 6 | .replace(/^\s*\/\*+[^\S\n]*/, '') 7 | .replace(/[^\S\n]*\*\/\s*$/, '') 8 | .replace(/^[^\S\n]*\*/gm, '') 9 | .replace(/^[^\S\n]{1,3}@(?=\S)/gm, '@') 10 | .replace(/\s+$/, ''); 11 | 12 | return exports.trimRight(exports.stripIndent(res)); 13 | }; 14 | 15 | exports.stripIndent = function(str) { 16 | let match = /(?:^|\n)([^\S\n]*)\S/.exec(str); 17 | if (match) { 18 | let len = match[1].length; 19 | return str.replace(new RegExp(`(^|\\n) {${len}}`, 'g'), '$1'); 20 | } 21 | return str; 22 | }; 23 | 24 | exports.trimRight = function(str) { 25 | return str.replace(/\s+$/, ''); 26 | }; 27 | 28 | /** 29 | * Return the native type of a value 30 | */ 31 | 32 | const typeOf = exports.typeOf = val => { 33 | if (typeof val === 'string') return 'string'; 34 | if (Array.isArray(val)) return 'array'; 35 | if (val instanceof RegExp) { 36 | return 'regexp'; 37 | } 38 | if (val && typeof val === 'object') { 39 | return 'object'; 40 | } 41 | }; 42 | 43 | /** 44 | * Define a non-enumerable property on an object 45 | */ 46 | 47 | exports.define = (obj, key, value) => { 48 | Reflect.defineProperty(obj, key, { 49 | enumerable: false, 50 | configurable: true, 51 | writable: true, 52 | value 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /test/fixtures/examples-gfm-no-stars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc function 3 | * @name angular.forEach 4 | * @module ng 5 | * @kind function 6 | * 7 | * @description 8 | * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 9 | * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 10 | * is the value of an object property or an array element, `key` is the object property key or 11 | * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 12 | * 13 | * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 14 | * using the `hasOwnProperty` method. 15 | * 16 | * Unlike ES262's 17 | * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 18 | * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 19 | * return the value provided. 20 | * 21 | ```js 22 | var values = {name: 'misko', gender: 'male'}; 23 | var log = []; 24 | angular.forEach(values, function(value, key) { 25 | this.push(key + ': ' + value); 26 | }, log); 27 | expect(log).toEqual(['name: misko', 'gender: male']); 28 | ``` 29 | * 30 | * @param {Object|Array} obj Object to iterate over. 31 | * @param {Function} iterator Iterator function. 32 | * @param {Object=} context Object to become context (`this`) for the iterator function. 33 | * @returns {Object|Array} Reference to `obj`. 34 | */ 35 | -------------------------------------------------------------------------------- /test/fixtures/description-tag.js: -------------------------------------------------------------------------------- 1 | // from angular code (@license AngularJS v1.5.0-rc.0 (c) 2010-2015 Google, Inc. http://angularjs.org License: MIT) 2 | /** 3 | * @description 4 | * 5 | * This object provides a utility for producing rich Error messages within 6 | * Angular. It can be called as follows: 7 | * 8 | * var exampleMinErr = minErr('example'); 9 | * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 10 | * 11 | * The above creates an instance of minErr in the example namespace. The 12 | * resulting error will have a namespaced error code of example.one. The 13 | * resulting error will replace {0} with the value of foo, and {1} with the 14 | * value of bar. The object is not restricted in the number of arguments it can 15 | * take. 16 | * 17 | * If fewer arguments are specified than necessary for interpolation, the extra 18 | * interpolation markers will be preserved in the final string. 19 | * 20 | * Since data will be parsed statically during a build step, some restrictions 21 | * are applied with respect to how minErr instances are created and called. 22 | * Instances should have names of the form namespaceMinErr for a minErr created 23 | * using minErr('namespace') . Error codes, namespaces and template strings 24 | * should all be static strings, not variables or general expressions. 25 | * 26 | * @param {string} module The namespace to use for the new minErr instance. 27 | * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 28 | * error from returned function, for cases when a particular type of error is useful. 29 | * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 30 | */ 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tokenize-comment", 3 | "description": "Uses snapdragon to tokenize a single JavaScript block comment into an object, with description, tags, and code example sections that can be passed to any other comment parsers for further parsing.", 4 | "version": "3.0.1", 5 | "homepage": "https://github.com/jonschlinkert/tokenize-comment", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "jonschlinkert/tokenize-comment", 8 | "bugs": { 9 | "url": "https://github.com/jonschlinkert/tokenize-comment/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "index.js", 14 | "lib" 15 | ], 16 | "main": "index.js", 17 | "engines": { 18 | "node": ">=8" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "dependencies": { 24 | "snapdragon-lexer": "^4.0.0" 25 | }, 26 | "devDependencies": { 27 | "extract-comments": "^1.0.0", 28 | "gulp-format-md": "^2.0.0", 29 | "mocha": "^5.2.0", 30 | "stringify-object": "^3.3.0", 31 | "write": "^1.0.3" 32 | }, 33 | "keywords": [ 34 | "code", 35 | "comment", 36 | "examples", 37 | "gfm", 38 | "indented", 39 | "javadoc", 40 | "javascript", 41 | "jsdoc", 42 | "parse", 43 | "tokenize" 44 | ], 45 | "verb": { 46 | "toc": false, 47 | "layout": "default", 48 | "tasks": [ 49 | "readme" 50 | ], 51 | "plugins": [ 52 | "gulp-format-md" 53 | ], 54 | "related": { 55 | "list": [ 56 | "parse-comments", 57 | "snapdragon", 58 | "strip-comments" 59 | ] 60 | }, 61 | "lint": { 62 | "reflinks": true 63 | }, 64 | "reflinks": [ 65 | "catharsis", 66 | "doctrine", 67 | "dox", 68 | "js-comments", 69 | "jsdoc", 70 | "parse-comments", 71 | "verb" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/fixtures/multiline.js: -------------------------------------------------------------------------------- 1 | // sourced from dox 2 | // TJ Holowaychuk 3 | // MIT License 4 | 5 | /** 6 | * only 7 | * 8 | * @sample 9 | * one 10 | * two 11 | * three 12 | */ 13 | function only() { 14 | } 15 | 16 | /** 17 | * first 18 | * 19 | * @sample 20 | * one 21 | * two 22 | * three 23 | * @bar last 24 | */ 25 | function first() { 26 | } 27 | 28 | /** 29 | * last 30 | * 31 | * @foo first 32 | * @sample 33 | * one 34 | * two 35 | * three 36 | */ 37 | function last() { 38 | } 39 | 40 | /** 41 | * mid 42 | * 43 | * @foo first 44 | * @sample 45 | * one 46 | * two 47 | * three 48 | * @bar last 49 | */ 50 | function mid() { 51 | } 52 | 53 | /** 54 | * only 55 | * 56 | * @param {String} foo 57 | * one 58 | * two 59 | * three 60 | */ 61 | function onlyParam() { 62 | } 63 | 64 | /** 65 | * first 66 | * 67 | * @param {String} foo 68 | * one 69 | * two 70 | * three 71 | * @bar last 72 | */ 73 | function firstParam() { 74 | } 75 | 76 | /** 77 | * last 78 | * 79 | * @foo first 80 | * @param {String} foo 81 | * one 82 | * two 83 | * three 84 | */ 85 | function lastParam() { 86 | } 87 | 88 | /** 89 | * mid 90 | * 91 | * @foo first 92 | * @param {String} foo 93 | * one 94 | * two 95 | * three 96 | * @bar last 97 | */ 98 | function midParam() { 99 | } 100 | 101 | /** 102 | * only 103 | * 104 | * @return {String} 105 | * one 106 | * two 107 | * three 108 | */ 109 | function onlyReturn() { 110 | } 111 | 112 | /** 113 | * first 114 | * 115 | * @return {String} 116 | * one 117 | * two 118 | * three 119 | * @bar last 120 | */ 121 | function firstReturn() { 122 | } 123 | 124 | /** 125 | * last 126 | * 127 | * @foo first 128 | * @return {String} 129 | * one 130 | * two 131 | * three 132 | */ 133 | function lastReturn() { 134 | } 135 | 136 | /** 137 | * mid 138 | * 139 | * @foo first 140 | * @return {String} 141 | * one 142 | * two 143 | * three 144 | * @bar last 145 | */ 146 | function midReturn() { 147 | } 148 | 149 | /** 150 | * example 151 | * 152 | * @example 153 | * test(one); 154 | * test(two); 155 | */ 156 | function example() { 157 | } 158 | 159 | /** 160 | * @tag-1 foo 161 | * @tag-2 bar 162 | * 163 | * @tag-3 baz 164 | */ 165 | 166 | /** 167 | * @tag-1 168 | * foo 169 | * @tag-2 170 | * bar 171 | * 172 | * @tag-3 173 | * baz 174 | */ -------------------------------------------------------------------------------- /test/fixtures/example-large.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @example 3 | * 4 | 5 | [contenteditable] { 6 | border: 1px solid black; 7 | background-color: white; 8 | min-height: 20px; 9 | } 10 | 11 | .ng-invalid { 12 | border: 1px solid red; 13 | } 14 | 15 | 16 | 17 | angular.module('customControl', ['ngSanitize']). 18 | directive('contenteditable', ['$sce', function($sce) { 19 | return { 20 | restrict: 'A', // only activate on element attribute 21 | require: '?ngModel', // get a hold of NgModelController 22 | link: function(scope, element, attrs, ngModel) { 23 | if (!ngModel) return; // do nothing if no ng-model 24 | 25 | // Specify how UI should be updated 26 | ngModel.$render = function() { 27 | element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 28 | }; 29 | 30 | // Listen for change events to enable binding 31 | element.on('blur keyup change', function() { 32 | scope.$evalAsync(read); 33 | }); 34 | read(); // initialize 35 | 36 | // Write data to the model 37 | function read() { 38 | var html = element.html(); 39 | // When we clear the content editable the browser leaves a
behind 40 | // If strip-br attribute is provided then we strip this out 41 | if ( attrs.stripBr && html == '
' ) { 42 | html = ''; 43 | } 44 | ngModel.$setViewValue(html); 45 | } 46 | } 47 | }; 48 | }]); 49 |
50 | 51 |
52 |
Change me!
56 | Required! 57 |
58 | 59 |
60 |
61 | 62 | it('should data-bind and become invalid', function() { 63 | if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 64 | // SafariDriver can't handle contenteditable 65 | // and Firefox driver can't clear contenteditables very well 66 | return; 67 | } 68 | var contentEditable = element(by.css('[contenteditable]')); 69 | var content = 'Change me!'; 70 | 71 | expect(contentEditable.getText()).toEqual(content); 72 | 73 | contentEditable.clear(); 74 | contentEditable.sendKeys(protractor.Key.BACK_SPACE); 75 | expect(contentEditable.getText()).toEqual(''); 76 | expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 77 | }); 78 | 79 | *
80 | * 81 | * 82 | */ -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to tokenize-comment 2 | 3 | First and foremost, thank you! We appreciate that you want to contribute to tokenize-comment, your time is valuable, and your contributions mean a lot to us. 4 | 5 | **What does "contributing" mean?** 6 | 7 | Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: 8 | 9 | - Updating or correcting documentation 10 | - Feature requests 11 | - Bug reports 12 | 13 | If you'd like to learn more about contributing in general, the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) has a lot of useful information. 14 | 15 | **Showing support for tokenize-comment** 16 | 17 | Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use. 18 | 19 | Don't have time to contribute? No worries, here are some other ways to show your support for tokenize-comment: 20 | 21 | - star the [project](https://github.com/jonschlinkert/tokenize-comment) 22 | - tweet your support for tokenize-comment 23 | 24 | ## Issues 25 | 26 | ### Before creating an issue 27 | 28 | Please try to determine if the issue is caused by an underlying library, and if so, create the issue there. Sometimes this is difficult to know. We only ask that you attempt to give a reasonable attempt to find out. Oftentimes the readme will have advice about where to go to create issues. 29 | 30 | Try to follow these guidelines 31 | 32 | - **Investigate the issue**: 33 | - **Check the readme** - oftentimes you will find notes about creating issues, and where to go depending on the type of issue. 34 | - Create the issue in the appropriate repository. 35 | 36 | ### Creating an issue 37 | 38 | Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: 39 | 40 | - **version**: please note the version of tokenize-comment are you using 41 | - **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using 42 | - **error messages**: please paste any error messages into the issue, or a [gist](https://gist.github.com/) 43 | 44 | ## Above and beyond 45 | 46 | Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. 47 | 48 | - read the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) 49 | - take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). 50 | - Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). 51 | - use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others 52 | - use syntax highlighting by adding the correct language name after the first "code fence" 53 | 54 | 55 | [node-glob]: https://github.com/isaacs/node-glob 56 | [micromatch]: https://github.com/jonschlinkert/micromatch 57 | [so]: http://stackoverflow.com/questions/tagged/tokenize-comment -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | 6 | "env": { 7 | "browser": false, 8 | "es6": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | 13 | "parserOptions":{ 14 | "ecmaVersion": 9, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "modules": true, 18 | "experimentalObjectRestSpread": true 19 | } 20 | }, 21 | 22 | "globals": { 23 | "document": false, 24 | "navigator": false, 25 | "window": false 26 | }, 27 | 28 | "rules": { 29 | "accessor-pairs": 2, 30 | "arrow-spacing": [2, { "before": true, "after": true }], 31 | "block-spacing": [2, "always"], 32 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 33 | "comma-dangle": [2, "never"], 34 | "comma-spacing": [2, { "before": false, "after": true }], 35 | "comma-style": [2, "last"], 36 | "constructor-super": 2, 37 | "curly": [2, "multi-line"], 38 | "dot-location": [2, "property"], 39 | "eol-last": 2, 40 | "eqeqeq": [2, "allow-null"], 41 | "generator-star-spacing": [2, { "before": true, "after": true }], 42 | "handle-callback-err": [2, "^(err|error)$" ], 43 | "indent": [2, 2, { "SwitchCase": 1 }], 44 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 45 | "keyword-spacing": [2, { "before": true, "after": true }], 46 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 47 | "new-parens": 2, 48 | "no-array-constructor": 2, 49 | "no-caller": 2, 50 | "no-class-assign": 2, 51 | "no-cond-assign": 2, 52 | "no-const-assign": 2, 53 | "no-control-regex": 2, 54 | "no-debugger": 2, 55 | "no-delete-var": 2, 56 | "no-dupe-args": 2, 57 | "no-dupe-class-members": 2, 58 | "no-dupe-keys": 2, 59 | "no-duplicate-case": 2, 60 | "no-empty-character-class": 2, 61 | "no-eval": 2, 62 | "no-ex-assign": 2, 63 | "no-extend-native": 2, 64 | "no-extra-bind": 2, 65 | "no-extra-boolean-cast": 2, 66 | "no-extra-parens": [2, "functions"], 67 | "no-fallthrough": 2, 68 | "no-floating-decimal": 2, 69 | "no-func-assign": 2, 70 | "no-implied-eval": 2, 71 | "no-inner-declarations": [2, "functions"], 72 | "no-invalid-regexp": 2, 73 | "no-irregular-whitespace": 2, 74 | "no-iterator": 2, 75 | "no-label-var": 2, 76 | "no-labels": 2, 77 | "no-lone-blocks": 2, 78 | "no-mixed-spaces-and-tabs": 2, 79 | "no-multi-spaces": 2, 80 | "no-multi-str": 2, 81 | "no-multiple-empty-lines": [2, { "max": 1 }], 82 | "no-native-reassign": 0, 83 | "no-negated-in-lhs": 2, 84 | "no-new": 2, 85 | "no-new-func": 2, 86 | "no-new-object": 2, 87 | "no-new-require": 2, 88 | "no-new-wrappers": 2, 89 | "no-obj-calls": 2, 90 | "no-octal": 2, 91 | "no-octal-escape": 2, 92 | "no-proto": 0, 93 | "no-redeclare": 2, 94 | "no-regex-spaces": 2, 95 | "no-return-assign": 2, 96 | "no-self-compare": 2, 97 | "no-sequences": 2, 98 | "no-shadow-restricted-names": 2, 99 | "no-spaced-func": 2, 100 | "no-sparse-arrays": 2, 101 | "no-this-before-super": 2, 102 | "no-throw-literal": 2, 103 | "no-trailing-spaces": 0, 104 | "no-undef": 2, 105 | "no-undef-init": 2, 106 | "no-unexpected-multiline": 2, 107 | "no-unneeded-ternary": [2, { "defaultAssignment": false }], 108 | "no-unreachable": 2, 109 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 110 | "no-useless-call": 0, 111 | "no-with": 2, 112 | "one-var": [0, { "initialized": "never" }], 113 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], 114 | "padded-blocks": [0, "never"], 115 | "quotes": [2, "single", "avoid-escape"], 116 | "radix": 2, 117 | "semi": [2, "always"], 118 | "semi-spacing": [2, { "before": false, "after": true }], 119 | "space-before-blocks": [2, "always"], 120 | "space-before-function-paren": [2, "never"], 121 | "space-in-parens": [2, "never"], 122 | "space-infix-ops": 2, 123 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 124 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 125 | "use-isnan": 2, 126 | "valid-typeof": 2, 127 | "wrap-iife": [2, "any"], 128 | "yoda": [2, "never"] 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const support = require('./support'); 6 | const tokenize = require('..'); 7 | const fixtures = support.files(__dirname, 'fixtures'); 8 | 9 | describe('examples', function() { 10 | it('should tokenize gfm, indented or javadoc examples', function() { 11 | const tok = tokenize(fixtures['examples-multiple']); 12 | 13 | assert.deepEqual(tok, { 14 | description: 'This is a comment with\nseveral lines of text.', 15 | footer: '', 16 | examples: [ 17 | { 18 | type: 'gfm', 19 | language: 'js', 20 | description: 'An example', 21 | raw: '```js\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n```', 22 | value: '\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n' 23 | }, 24 | { 25 | type: 'indented', 26 | language: '', 27 | description: 'Another example', 28 | raw: ' var baz = fez;\n var baz = fez;\n var baz = fez;\n', 29 | value: 'var baz = fez;\nvar baz = fez;\nvar baz = fez;\n' 30 | }, 31 | { 32 | type: 'indented', 33 | language: '', 34 | description: 'Another example', 35 | raw: ' var baz = fez;\n var baz = fez;\n', 36 | value: 'var baz = fez;\nvar baz = fez;\n' 37 | }, 38 | { 39 | type: 'gfm', 40 | language: 'js', 41 | description: 'And another example', 42 | raw: '```js\nvar foo = bar;\nvar foo = bar;\n```', 43 | value: '\nvar foo = bar;\nvar foo = bar;\n' 44 | }, 45 | { 46 | type: 'javadoc', 47 | language: '', 48 | description: 'Another example', 49 | raw: '@example\nvar baz = fez;\n', 50 | value: '\nvar baz = fez;\n' 51 | }, 52 | { 53 | type: 'javadoc', 54 | language: '', 55 | description: '', 56 | raw: '@example\n// this is a comment\nvar alalla = zzzz;\n', 57 | value: '\n// this is a comment\nvar alalla = zzzz;\n' 58 | } 59 | ], 60 | tags: [ 61 | { 62 | type: 'tag', 63 | raw: '@param {String} foo bar', 64 | key: 'param', 65 | value: '{String} foo bar' 66 | }, 67 | { 68 | type: 'tag', 69 | raw: '@returns {Object} Instance of Foo', 70 | key: 'returns', 71 | value: '{Object} Instance of Foo' 72 | }, 73 | { 74 | type: 'tag', 75 | raw: '@api public', 76 | key: 'api', 77 | value: 'public' 78 | } 79 | ] 80 | }); 81 | }); 82 | 83 | it('should work with arbitrary markdown', function() { 84 | const tok = tokenize(fixtures.markdown); 85 | 86 | assert.deepEqual(tok, { 87 | description: 'Set a parser that can later be used to parse any given string.', 88 | footer: 'This is arbitrary text.\n\n * This is arbitrary text.\n * This is arbitrary text.\n * This is arbitrary text.\n\n**Example**\n\n{%= docs("example-parser.md") %}\n\nThis is a another description after the example.', 89 | examples: [{ 90 | type: 'gfm', 91 | language: 'js', 92 | description: '', 93 | raw: '```js\n// foo.parser(name, replacements)\nfoo.parser("foo", function(a, b, c) {\n // body...\n})\n```', 94 | value: '\n// foo.parser(name, replacements)\nfoo.parser("foo", function(a, b, c) {\n // body...\n})\n' 95 | }], 96 | tags: [{ 97 | type: 'tag', 98 | raw: '@param {String} `alpha`', 99 | key: 'param', 100 | value: '{String} `alpha`' 101 | }, { 102 | type: 'tag', 103 | raw: '@param {Object|Array} `arr` Object or array of replacement patterns to associate.', 104 | key: 'param', 105 | value: '{Object|Array} `arr` Object or array of replacement patterns to associate.' 106 | }, { 107 | type: 'tag', 108 | raw: '@property {String|RegExp} [arr] `pattern`', 109 | key: 'property', 110 | value: '{String|RegExp} [arr] `pattern`' 111 | }, { 112 | type: 'tag', 113 | raw: '@property {String|Function} [arr] `replacement`', 114 | key: 'property', 115 | value: '{String|Function} [arr] `replacement`' 116 | }, { 117 | type: 'tag', 118 | raw: '@param {String} `beta`', 119 | key: 'param', 120 | value: '{String} `beta`' 121 | }, { 122 | type: 'tag', 123 | raw: '@property {Array} [beta] `foo` This is foo option.', 124 | key: 'property', 125 | value: '{Array} [beta] `foo` This is foo option.' 126 | }, { 127 | type: 'tag', 128 | raw: '@property {Array} [beta] `bar` This is bar option', 129 | key: 'property', 130 | value: '{Array} [beta] `bar` This is bar option' 131 | }, { 132 | type: 'tag', 133 | raw: '@return {Strings} to allow chaining', 134 | key: 'return', 135 | value: '{Strings} to allow chaining' 136 | }, { 137 | type: 'tag', 138 | raw: '@api public', 139 | key: 'api', 140 | value: 'public' 141 | }] 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /lib/tokenize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isString = val => typeof val === 'string'; 4 | const Lexer = require('snapdragon-lexer'); 5 | const { define } = require('./utils'); 6 | 7 | module.exports = function(str, options, state) { 8 | const lexer = new Lexer(options); 9 | lexer.ast = { type: 'root', nodes: [] }; 10 | let stack = []; 11 | let type = 'description'; 12 | let prev = lexer.ast; 13 | let footer = 0; 14 | let prevType; 15 | 16 | function last(arr) { 17 | return arr[arr.length - 1]; 18 | } 19 | 20 | function append(prop, str) { 21 | last(stack)[prop] += str; 22 | } 23 | 24 | function update(token) { 25 | if (type === 'description') { 26 | if (stack.length === 0 || prevType === 'break' || prev.isExample === true) { 27 | stack.push(token); 28 | } else { 29 | append('value', token.value); 30 | } 31 | 32 | } else if (type === 'example' && prev.type === 'description' && stack.length > 1) { 33 | token.description += stack.pop().value; 34 | footer = stack.length; 35 | 36 | } else if (stack.length && (type === 'newline' || type === 'break')) { 37 | if (prev.isExample === true) { 38 | prev.description += token.value; 39 | } else { 40 | append('value', token.value); 41 | } 42 | 43 | } else if (!stack.length && !token.isExample) { 44 | state.description += token.value; 45 | 46 | } else { 47 | footer = stack.length; 48 | } 49 | } 50 | 51 | lexer.on('token', function(token) { 52 | type = token.type; 53 | 54 | switch (type) { 55 | case 'gfm': 56 | case 'javadoc': 57 | case 'indented': 58 | case 'example': 59 | define(token, 'isExample', true); 60 | if (prev.type === 'tag' && type === 'indented') { 61 | prev.value += token.value; 62 | prev.raw += token.value; 63 | break; 64 | } 65 | 66 | type = 'example'; 67 | update(token); 68 | state.examples.push(token); 69 | prev = token; 70 | break; 71 | 72 | case 'tag': 73 | state[type + 's'].push(token); 74 | prev = token; 75 | break; 76 | 77 | case 'description': 78 | // if "token.key" exists, it's a "@description" tag 79 | if (prev.type === 'tag' && !token.key) { 80 | prev.value += token.value; 81 | prev.raw += token.value; 82 | break; 83 | } 84 | update(token); 85 | prev = token; 86 | break; 87 | 88 | case 'newline': 89 | default: { 90 | if (prev.type === 'tag') { 91 | prev.value += token.value; 92 | prev.raw += token.value; 93 | break; 94 | } 95 | update(token); 96 | break; 97 | } 98 | } 99 | prevType = type; 100 | }); 101 | 102 | lexer 103 | .capture('break', /^\n{2,}/) 104 | .capture('newline', /^\n/) 105 | .set('gfm', function() { 106 | const loc = this.location(); 107 | const match = this.match(/^([^\S\n]{0,3})(`{3,4}|~{3,4})(.*)/); 108 | if (match) { 109 | const token = loc(this.token({ 110 | type: 'gfm', 111 | raw: match[0], 112 | description: '', 113 | language: match[3] || '', 114 | value: '' 115 | })); 116 | 117 | let fenceLen = match[2].length; 118 | let fence = fenceLen === 3 ? '```' : '````'; 119 | let idx = this.state.string.indexOf(fence); 120 | 121 | while (this.state.string[idx - 1] === '\\') { 122 | idx = this.state.string.indexOf(fence, idx + 1); 123 | } 124 | 125 | if (idx === -1) { 126 | throw new Error('missing closing "' + fence + '"'); 127 | } 128 | 129 | token.raw += this.state.string.slice(0, idx + fenceLen); 130 | token.value += this.state.string.slice(0, idx); 131 | this.state.string = this.state.string.slice(idx + fenceLen); 132 | 133 | if (match[1]) { 134 | let len = match[1].length; 135 | let segs = token.value.split('\n'); 136 | token.value = segs.map(ele => ele.slice(len)).join('\n'); 137 | } 138 | return token; 139 | } 140 | }) 141 | .set('indented', function() { 142 | let loc = this.location(); 143 | let match = this.match(/^ {4}([^\n]*\n?)/); 144 | if (match) { 145 | let token = loc(this.token({ 146 | type: 'indented', 147 | language: '', 148 | description: '', 149 | raw: match[0], 150 | value: match[1] 151 | })); 152 | 153 | let lines = this.state.string.split('\n'); 154 | let line = lines[0]; 155 | let len = 0; 156 | let i = 0; 157 | 158 | while (isString(line) && (line.slice(0, 4) === ' ' || line === '')) { 159 | token.value += line.slice(4) + '\n'; 160 | token.raw += line + '\n'; 161 | len += line.length + 1; 162 | line = lines[++i]; 163 | } 164 | 165 | token.value = token.value.replace(/\n+$/, '\n'); 166 | token.raw = token.raw.replace(/\n+$/, '\n'); 167 | 168 | this.state.string = this.state.string.slice(len); 169 | return token; 170 | } 171 | }) 172 | .set('javadoc', function() { 173 | let loc = this.location(); 174 | let match = this.match(/^@example *([^\n]*\n?)/); 175 | if (match) { 176 | let token = loc(this.token({ 177 | type: 'javadoc', 178 | language: '', 179 | description: '', 180 | raw: match[0], 181 | value: match[1] 182 | })); 183 | 184 | let lines = this.state.string.split('\n'); 185 | let line = lines[0]; 186 | let len = 0; 187 | let i = 0; 188 | 189 | while (isString(line) && (!/^\s*(@|`{3,4}|~{3,4})/.test(line) || line === '')) { 190 | token.value += line + '\n'; 191 | token.raw += line + '\n'; 192 | len += line.length + 1; 193 | line = lines[++i]; 194 | } 195 | 196 | token.value = token.value.replace(/\n+$/, '\n'); 197 | token.raw = token.raw.replace(/\n+$/, '\n'); 198 | this.state.string = this.state.string.slice(len); 199 | return token; 200 | } 201 | }) 202 | .set('tags', function() { 203 | let loc = this.location(); 204 | let match = this.match(/^ {0,3}@(?!example)(\S+) *([^\n]*)/); 205 | if (match) { 206 | let name = match[1] === 'description' ? match[1] : 'tag'; 207 | return loc(this.token({ 208 | type: name, 209 | raw: match[0], 210 | key: match[1], 211 | value: match[2] 212 | })); 213 | } 214 | }) 215 | 216 | .capture('description', /^[^\S\n]{0,3}(?!@|`{3,4}|~{3,4}| {4})[^\n]*/) 217 | 218 | /** 219 | * Lex the string 220 | */ 221 | 222 | let tokens = lexer.lex(str); 223 | 224 | if (footer && stack.length > 1 && footer < stack.length) { 225 | stack.slice(footer).forEach(token => (state.footer += token.value)); 226 | stack = stack.slice(0, footer); 227 | } 228 | 229 | stack.forEach(token => (state.description += token.value)); 230 | state.description = state.description.trim(); 231 | state.footer = state.footer.trim(); 232 | 233 | state.examples.forEach(function(example) { 234 | example.description = example.description.trim(); 235 | }); 236 | 237 | state.tags.forEach(function(tag) { 238 | tag.raw = tag.raw.trim(); 239 | tag.value = tag.value.trim(); 240 | }); 241 | return tokens; 242 | }; 243 | 244 | -------------------------------------------------------------------------------- /test/examples-javadoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const tokenize = require('..'); 6 | 7 | describe('javadoc', function() { 8 | it('should tokenize javadoc code examples', function() { 9 | const tok = tokenize([ 10 | '/**', 11 | ' * foo bar baz', 12 | ' * ', 13 | ' * @example', 14 | ' * var foo = "bar";', 15 | ' *', 16 | ' * @param {string} something', 17 | ' * @param {string} else', 18 | ' */' 19 | ].join('\n')); 20 | 21 | assert.deepEqual(tok, { 22 | description: 'foo bar baz', 23 | footer: '', 24 | examples: [{ 25 | type: 'javadoc', 26 | language: '', 27 | description: '', 28 | raw: '@example\nvar foo = "bar";\n', 29 | value: '\nvar foo = "bar";\n' 30 | }], 31 | tags: [{ 32 | type: 'tag', 33 | raw: '@param {string} something', 34 | key: 'param', 35 | value: '{string} something' 36 | }, { 37 | type: 'tag', 38 | raw: '@param {string} else', 39 | key: 'param', 40 | value: '{string} else' 41 | }] 42 | }); 43 | }); 44 | 45 | it('should tokenize javadoc example when it is the last tag', function() { 46 | const tok = tokenize([ 47 | '/**', 48 | ' * foo bar baz', 49 | ' * @param {string} something', 50 | ' * @param {string} else', 51 | ' * ', 52 | ' * @example', 53 | ' * var foo = "bar";', 54 | ' *', 55 | ' */' 56 | ].join('\n')); 57 | 58 | assert.deepEqual(tok, { 59 | description: 'foo bar baz', 60 | footer: '', 61 | examples: [{ 62 | type: 'javadoc', 63 | language: '', 64 | description: '', 65 | raw: '@example\nvar foo = "bar";\n', 66 | value: '\nvar foo = "bar";\n' 67 | }], 68 | tags: [{ 69 | type: 'tag', 70 | raw: '@param {string} something', 71 | key: 'param', 72 | value: '{string} something' 73 | }, { 74 | type: 'tag', 75 | raw: '@param {string} else', 76 | key: 'param', 77 | value: '{string} else' 78 | }] 79 | }); 80 | }); 81 | 82 | it('should tokenize multiple javadoc examples', function() { 83 | const tok = tokenize([ 84 | '/**', 85 | ' * foo bar baz', 86 | ' * @param {string} something', 87 | ' * @param {string} else', 88 | ' * ', 89 | ' * @example', 90 | ' * var foo = "bar";', 91 | ' * // inline comment', 92 | ' * var whatever = "something else";', 93 | ' * ', 94 | ' * @example', 95 | ' * var one = "two";', 96 | ' *', 97 | ' * ', 98 | ' * @example', 99 | ' * var abc = "xyz";', 100 | ' *', 101 | ' */' 102 | ].join('\n')); 103 | 104 | assert.deepEqual(tok, { 105 | description: 'foo bar baz', 106 | footer: '', 107 | examples: [{ 108 | type: 'javadoc', 109 | language: '', 110 | description: '', 111 | raw: '@example\nvar foo = \"bar\";\n // inline comment\nvar whatever = \"something else\";\n', 112 | value: '\nvar foo = \"bar\";\n // inline comment\nvar whatever = \"something else\";\n' 113 | },{ 114 | type: 'javadoc', 115 | language: '', 116 | description: '', 117 | raw: '@example\nvar one = "two";\n', 118 | value: '\nvar one = "two";\n' 119 | },{ 120 | type: 'javadoc', 121 | language: '', 122 | description: '', 123 | raw: '@example\nvar abc = "xyz";\n', 124 | value: '\nvar abc = "xyz";\n' 125 | }], 126 | tags: [{ 127 | type: 'tag', 128 | raw: '@param {string} something', 129 | key: 'param', 130 | value: '{string} something' 131 | }, { 132 | type: 'tag', 133 | raw: '@param {string} else', 134 | key: 'param', 135 | value: '{string} else' 136 | }] 137 | }); 138 | }); 139 | 140 | it('should preserve indentation in javadoc code examples', function() { 141 | const tok = tokenize([ 142 | '/**', 143 | ' * foo bar baz', 144 | ' * ', 145 | ' * @example', 146 | ' * var foo = "bar";', 147 | ' * var baz = "qux";', 148 | ' *', 149 | ' * @param {string} something', 150 | ' * @param {string} else', 151 | ' */' 152 | ].join('\n')); 153 | 154 | assert.deepEqual(tok, { 155 | description: 'foo bar baz', 156 | footer: '', 157 | examples: [{ 158 | type: 'javadoc', 159 | language: '', 160 | description: '', 161 | raw: '@example\n var foo = "bar";\n var baz = "qux";\n', 162 | value: '\n var foo = "bar";\n var baz = "qux";\n' 163 | }], 164 | tags: [{ 165 | type: 'tag', 166 | raw: '@param {string} something', 167 | key: 'param', 168 | value: '{string} something' 169 | }, { 170 | type: 'tag', 171 | raw: '@param {string} else', 172 | key: 'param', 173 | value: '{string} else' 174 | }] 175 | }); 176 | }); 177 | 178 | it('should detect a description for a javadoc code example', function() { 179 | const tok = tokenize([ 180 | '/**', 181 | ' * foo bar baz', 182 | ' * ', 183 | ' * This is a description for an example.', 184 | ' * @example', 185 | ' * var foo = "bar";', 186 | ' * var baz = "qux";', 187 | ' *', 188 | ' * @param {string} something', 189 | ' * @param {string} else', 190 | ' */' 191 | ].join('\n')); 192 | 193 | assert.deepEqual(tok, { 194 | description: 'foo bar baz', 195 | footer: '', 196 | examples: [{ 197 | type: 'javadoc', 198 | language: '', 199 | description: 'This is a description for an example.', 200 | raw: '@example\nvar foo = "bar";\nvar baz = "qux";\n', 201 | value: '\nvar foo = "bar";\nvar baz = "qux";\n' 202 | }], 203 | tags: [{ 204 | type: 'tag', 205 | raw: '@param {string} something', 206 | key: 'param', 207 | value: '{string} something' 208 | }, { 209 | type: 'tag', 210 | raw: '@param {string} else', 211 | key: 'param', 212 | value: '{string} else' 213 | }] 214 | }); 215 | }); 216 | 217 | it('should detect a description & leading newline for a javadoc example', function() { 218 | const tok = tokenize([ 219 | '/**', 220 | ' * foo bar baz', 221 | ' * ', 222 | ' * This is a description for an example.', 223 | ' *', 224 | ' * @example', 225 | ' * var foo = "bar";', 226 | ' * var baz = "qux";', 227 | ' *', 228 | ' * @param {string} something', 229 | ' * @param {string} else', 230 | ' */' 231 | ].join('\n')); 232 | 233 | assert.deepEqual(tok, { 234 | description: 'foo bar baz', 235 | footer: '', 236 | examples: [{ 237 | type: 'javadoc', 238 | language: '', 239 | description: 'This is a description for an example.', 240 | raw: '@example\nvar foo = "bar";\nvar baz = "qux";\n', 241 | value: '\nvar foo = "bar";\nvar baz = "qux";\n' 242 | }], 243 | tags: [{ 244 | type: 'tag', 245 | raw: '@param {string} something', 246 | key: 'param', 247 | value: '{string} something' 248 | }, { 249 | type: 'tag', 250 | raw: '@param {string} else', 251 | key: 'param', 252 | value: '{string} else' 253 | }] 254 | }); 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /test/examples-indented.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const tokenize = require('..'); 6 | 7 | describe('indented', function() { 8 | it('should tokenize indented code examples', function() { 9 | const tok = tokenize([ 10 | '/**', 11 | ' * Code:', 12 | ' * @foo', 13 | ' * @bar', 14 | ' * @baz', 15 | ' */' 16 | ].join('\n')); 17 | 18 | assert.deepEqual(tok, { 19 | description: 'Code:', 20 | footer: '', 21 | examples: [{ 22 | type: 'indented', 23 | language: '', 24 | description: '', 25 | raw: ' @foo\n @bar\n @baz\n', 26 | value: '@foo\n@bar\n@baz\n' 27 | }], 28 | tags: [] 29 | }); 30 | }); 31 | 32 | it('should work with extra indentation', function() { 33 | const tok = tokenize([ 34 | '/**', 35 | ' * Code:', 36 | ' * @foo', 37 | ' * @bar', 38 | ' * @baz', 39 | ' */' 40 | ].join('\n')); 41 | 42 | assert.deepEqual(tok, { 43 | description: 'Code:', 44 | footer: '', 45 | examples: [{ 46 | type: 'indented', 47 | language: '', 48 | description: '', 49 | raw: ' @foo\n @bar\n @baz\n', 50 | value: '@foo\n@bar\n@baz\n' 51 | }], 52 | tags: [] 53 | }); 54 | }); 55 | 56 | it('should work with comments not prefixed by stars', function() { 57 | const tok = tokenize([ 58 | '', 59 | ' Code:', 60 | ' @foo', 61 | ' @bar', 62 | ' @baz', 63 | '' 64 | ].join('\n')); 65 | 66 | assert.deepEqual(tok, { 67 | description: 'Code:', 68 | footer: '', 69 | examples: [{ 70 | type: 'indented', 71 | language: '', 72 | description: '', 73 | raw: ' @foo\n @bar\n @baz\n', 74 | value: '@foo\n@bar\n@baz\n' 75 | }], 76 | tags: [] 77 | }); 78 | }); 79 | 80 | it('should tokenize single-line indented code examples', function() { 81 | const tok = tokenize([ 82 | '/**', 83 | ' * foo bar baz', 84 | ' * ', 85 | ' * var foo = "bar";', 86 | ' *', 87 | ' * @param {string} something', 88 | ' * @param {string} else', 89 | ' */' 90 | ].join('\n')); 91 | 92 | assert.deepEqual(tok, { 93 | description: 'foo bar baz', 94 | footer: '', 95 | examples: [{ 96 | type: 'indented', 97 | language: '', 98 | description: '', 99 | raw: ' var foo = "bar";\n', 100 | value: 'var foo = "bar";\n' 101 | }], 102 | tags: [{ 103 | type: 'tag', 104 | raw: '@param {string} something', 105 | key: 'param', 106 | value: '{string} something' 107 | }, { 108 | type: 'tag', 109 | raw: '@param {string} else', 110 | key: 'param', 111 | value: '{string} else' 112 | }] 113 | }); 114 | }); 115 | 116 | it('should tokenize multi-line indented code examples', function() { 117 | const tok = tokenize([ 118 | '/**', 119 | ' * foo bar baz', 120 | ' * ', 121 | ' * var foo = "bar";', 122 | ' * var baz = "qux";', 123 | ' *', 124 | ' * @param {string} something', 125 | ' * @param {string} else', 126 | ' */' 127 | ].join('\n')); 128 | 129 | assert.deepEqual(tok, { 130 | description: 'foo bar baz', 131 | footer: '', 132 | examples: [{ 133 | type: 'indented', 134 | language: '', 135 | description: '', 136 | raw: ' var foo = "bar";\n var baz = "qux";\n', 137 | value: 'var foo = "bar";\nvar baz = "qux";\n' 138 | }], 139 | tags: [{ 140 | type: 'tag', 141 | raw: '@param {string} something', 142 | key: 'param', 143 | value: '{string} something' 144 | }, { 145 | type: 'tag', 146 | raw: '@param {string} else', 147 | key: 'param', 148 | value: '{string} else' 149 | }] 150 | }); 151 | }); 152 | 153 | it('should work with multiple newlines', function() { 154 | const tok = tokenize([ 155 | '/**', 156 | ' * foo bar baz', 157 | ' * ', 158 | ' * var foo = "bar";', 159 | ' * ', 160 | ' * ', 161 | ' * ', 162 | ' * var baz = "qux";', 163 | ' *', 164 | ' * @param {string} something', 165 | ' * @param {string} else', 166 | ' */' 167 | ].join('\n')); 168 | 169 | assert.deepEqual(tok, { 170 | description: 'foo bar baz', 171 | footer: '', 172 | examples: [{ 173 | type: 'indented', 174 | language: '', 175 | description: '', 176 | raw: ' var foo = \"bar\";\n\n\n\n var baz = \"qux\";\n', 177 | value: 'var foo = \"bar\";\n\n\n\nvar baz = \"qux\";\n' 178 | }], 179 | tags: [{ 180 | type: 'tag', 181 | raw: '@param {string} something', 182 | key: 'param', 183 | value: '{string} something' 184 | }, { 185 | type: 'tag', 186 | raw: '@param {string} else', 187 | key: 'param', 188 | value: '{string} else' 189 | }] 190 | }); 191 | }); 192 | 193 | it('should preserve indentation in indented code examples', function() { 194 | const tok = tokenize([ 195 | '/**', 196 | ' * foo bar baz', 197 | ' * ', 198 | ' * var foo = "bar";', 199 | ' * var baz = "qux";', 200 | ' *', 201 | ' * @param {string} something', 202 | ' * @param {string} else', 203 | ' */' 204 | ].join('\n')); 205 | 206 | assert.deepEqual(tok, { 207 | description: 'foo bar baz', 208 | footer: '', 209 | examples: [{ 210 | type: 'indented', 211 | language: '', 212 | description: '', 213 | raw: ' var foo = "bar";\n var baz = "qux";\n', 214 | value: ' var foo = "bar";\n var baz = "qux";\n' 215 | }], 216 | tags: [{ 217 | type: 'tag', 218 | raw: '@param {string} something', 219 | key: 'param', 220 | value: '{string} something' 221 | }, 222 | { 223 | type: 'tag', 224 | raw: '@param {string} else', 225 | key: 'param', 226 | value: '{string} else' 227 | } 228 | ] 229 | }); 230 | }); 231 | 232 | it('should detect a description for a indented code example', function() { 233 | const tok = tokenize([ 234 | '/**', 235 | ' * foo bar baz', 236 | ' * ', 237 | ' * This is a description for an example.', 238 | ' * var foo = "bar";', 239 | ' * var baz = "qux";', 240 | ' *', 241 | ' * @param {string} something', 242 | ' * @param {string} else', 243 | ' */' 244 | ].join('\n')); 245 | 246 | assert.deepEqual(tok, { 247 | description: 'foo bar baz', 248 | footer: '', 249 | examples: [{ 250 | type: 'indented', 251 | language: '', 252 | description: 'This is a description for an example.', 253 | raw: ' var foo = "bar";\n var baz = "qux";\n', 254 | value: 'var foo = "bar";\nvar baz = "qux";\n' 255 | }], 256 | tags: [{ 257 | type: 'tag', 258 | raw: '@param {string} something', 259 | key: 'param', 260 | value: '{string} something' 261 | }, { 262 | type: 'tag', 263 | raw: '@param {string} else', 264 | key: 'param', 265 | value: '{string} else' 266 | }] 267 | }); 268 | }); 269 | 270 | it('should detect a description & leading newline for a indented example', function() { 271 | const tok = tokenize([ 272 | '/**', 273 | ' * foo bar baz', 274 | ' * ', 275 | ' * This is a description for an example.', 276 | ' *', 277 | ' * var foo = "bar";', 278 | ' * var baz = "qux";', 279 | ' *', 280 | ' * @param {string} something', 281 | ' * @param {string} else', 282 | ' */' 283 | ].join('\n')); 284 | 285 | assert.deepEqual(tok, { 286 | description: 'foo bar baz', 287 | footer: '', 288 | examples: [ 289 | { 290 | type: 'indented', 291 | language: '', 292 | description: 'This is a description for an example.', 293 | raw: ' var foo = \"bar\";\n var baz = \"qux\";\n', 294 | value: 'var foo = "bar";\nvar baz = "qux";\n' 295 | } 296 | ], 297 | tags: [{ 298 | type: 'tag', 299 | raw: '@param {string} something', 300 | key: 'param', 301 | value: '{string} something' 302 | }, { 303 | type: 'tag', 304 | raw: '@param {string} else', 305 | key: 'param', 306 | value: '{string} else' 307 | }] 308 | }); 309 | }); 310 | }); 311 | -------------------------------------------------------------------------------- /test/tokenize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | var assert = require('assert'); 5 | var support = require('./support'); 6 | var tokenize = require('..'); 7 | 8 | var fixtures = support.files(__dirname, 'fixtures'); 9 | 10 | describe('tokenize', function() { 11 | it('should tokenize a block comment', function() { 12 | var tok = tokenize('/* foo */'); 13 | assert.deepEqual(tok, { description: 'foo', footer: '', examples: [], tags: [] }); 14 | }); 15 | 16 | it('should tokenize a comment with a multi-line description', function() { 17 | var tok = tokenize('/* foo\nbar\nbaz */'); 18 | assert.deepEqual(tok, { description: 'foo\nbar\nbaz', footer: '', examples: [], tags: [] }); 19 | }); 20 | 21 | it('should strip extraneous indentation from comments', function() { 22 | var tok = tokenize([ 23 | '/**', 24 | ' * foo bar baz', 25 | ' * ', 26 | ' * ', 27 | ' * @param {string} something', 28 | ' * @param {string} else', 29 | ' */' 30 | ].join('\n')); 31 | 32 | assert.deepEqual(tok, { 33 | description: 'foo bar baz', 34 | footer: '', 35 | examples: [], 36 | tags: [ 37 | { 38 | key: 'param', 39 | raw: '@param {string} something', 40 | type: 'tag', 41 | value: '{string} something' 42 | }, 43 | { 44 | key: 'param', 45 | raw: '@param {string} else', 46 | type: 'tag', 47 | value: '{string} else' 48 | } 49 | ] 50 | }); 51 | }); 52 | 53 | it('should work with comments that already have stars stripped', function() { 54 | var tok1 = tokenize([ 55 | '', 56 | ' foo bar baz', 57 | ' ', 58 | ' ', 59 | ' @param {string} something', 60 | ' @param {string} else', 61 | '' 62 | ].join('\n')); 63 | 64 | assert.deepEqual(tok1, { 65 | description: 'foo bar baz', 66 | footer: '', 67 | examples: [], 68 | tags: [ 69 | { 70 | key: 'param', 71 | raw: '@param {string} something', 72 | type: 'tag', 73 | value: '{string} something' 74 | }, 75 | { 76 | key: 'param', 77 | raw: '@param {string} else', 78 | type: 'tag', 79 | value: '{string} else' 80 | } 81 | ] 82 | }); 83 | 84 | var tok2 = tokenize([ 85 | 'foo bar baz', 86 | '', 87 | '', 88 | '@param {string} something', 89 | '@param {string} else' 90 | ].join('\n')); 91 | 92 | assert.deepEqual(tok2, { 93 | description: 'foo bar baz', 94 | footer: '', 95 | examples: [], 96 | tags: [ 97 | { 98 | key: 'param', 99 | raw: '@param {string} something', 100 | type: 'tag', 101 | value: '{string} something' 102 | }, 103 | { 104 | key: 'param', 105 | raw: '@param {string} else', 106 | type: 'tag', 107 | value: '{string} else' 108 | } 109 | ] 110 | }); 111 | }); 112 | 113 | it('should tokenize complicated comments', function() { 114 | var tok1 = tokenize(fixtures['example-large']); 115 | assert.deepEqual(tok1, { 116 | description: '', 117 | footer: '', 118 | examples: [{ 119 | type: 'javadoc', 120 | language: '', 121 | description: '', 122 | raw: '@example\n \n \n [contenteditable] {\n border: 1px solid black;\n background-color: white;\n min-height: 20px;\n }\n\n .ng-invalid {\n border: 1px solid red;\n }\n\n \n \n angular.module(\'customControl\', [\'ngSanitize\']).\n directive(\'contenteditable\', [\'$sce\', function($sce) {\n return {\n restrict: \'A\', // only activate on element attribute\n require: \'?ngModel\', // get a hold of NgModelController\n link: function(scope, element, attrs, ngModel) {\n if (!ngModel) return; // do nothing if no ng-model\n\n // Specify how UI should be updated\n ngModel.$render = function() {\n element.html($sce.getTrustedHtml(ngModel.$viewValue || \'\'));\n };\n\n // Listen for change events to enable binding\n element.on(\'blur keyup change\', function() {\n scope.$evalAsync(read);\n });\n read(); // initialize\n\n // Write data to the model\n function read() {\n var html = element.html();\n // When we clear the content editable the browser leaves a
behind\n // If strip-br attribute is provided then we strip this out\n if ( attrs.stripBr && html == \'
\' ) {\n html = \'\';\n }\n ngModel.$setViewValue(html);\n }\n }\n };\n }]);\n
\n \n
\n
Change me!
\n Required!\n
\n \n
\n
\n \n it(\'should data-bind and become invalid\', function() {\n if (browser.params.browser == \'safari\' || browser.params.browser == \'firefox\') {\n // SafariDriver can\'t handle contenteditable\n // and Firefox driver can\'t clear contenteditables very well\n return;\n }\n var contentEditable = element(by.css(\'[contenteditable]\'));\n var content = \'Change me!\';\n\n expect(contentEditable.getText()).toEqual(content);\n\n contentEditable.clear();\n contentEditable.sendKeys(protractor.Key.BACK_SPACE);\n expect(contentEditable.getText()).toEqual(\'\');\n expect(contentEditable.getAttribute(\'class\')).toMatch(/ng-invalid-required/);\n });\n \n
\n', 123 | value: '\n \n \n [contenteditable] {\n border: 1px solid black;\n background-color: white;\n min-height: 20px;\n }\n\n .ng-invalid {\n border: 1px solid red;\n }\n\n \n \n angular.module(\'customControl\', [\'ngSanitize\']).\n directive(\'contenteditable\', [\'$sce\', function($sce) {\n return {\n restrict: \'A\', // only activate on element attribute\n require: \'?ngModel\', // get a hold of NgModelController\n link: function(scope, element, attrs, ngModel) {\n if (!ngModel) return; // do nothing if no ng-model\n\n // Specify how UI should be updated\n ngModel.$render = function() {\n element.html($sce.getTrustedHtml(ngModel.$viewValue || \'\'));\n };\n\n // Listen for change events to enable binding\n element.on(\'blur keyup change\', function() {\n scope.$evalAsync(read);\n });\n read(); // initialize\n\n // Write data to the model\n function read() {\n var html = element.html();\n // When we clear the content editable the browser leaves a
behind\n // If strip-br attribute is provided then we strip this out\n if ( attrs.stripBr && html == \'
\' ) {\n html = \'\';\n }\n ngModel.$setViewValue(html);\n }\n }\n };\n }]);\n
\n \n
\n
Change me!
\n Required!\n
\n \n
\n
\n \n it(\'should data-bind and become invalid\', function() {\n if (browser.params.browser == \'safari\' || browser.params.browser == \'firefox\') {\n // SafariDriver can\'t handle contenteditable\n // and Firefox driver can\'t clear contenteditables very well\n return;\n }\n var contentEditable = element(by.css(\'[contenteditable]\'));\n var content = \'Change me!\';\n\n expect(contentEditable.getText()).toEqual(content);\n\n contentEditable.clear();\n contentEditable.sendKeys(protractor.Key.BACK_SPACE);\n expect(contentEditable.getText()).toEqual(\'\');\n expect(contentEditable.getAttribute(\'class\')).toMatch(/ng-invalid-required/);\n });\n \n
\n' 124 | }], 125 | tags: [] 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/tags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const support = require('./support'); 6 | const tokenize = require('..'); 7 | const fixtures = support.files(__dirname, 'fixtures'); 8 | 9 | describe('tags', function() { 10 | it('should tokenize a comment with a tag', function() { 11 | const tok = tokenize('/* foo\nbar\nbaz\n * \n@param {string} something */'); 12 | assert.deepEqual(tok, { 13 | description: 'foo\nbar\nbaz', 14 | footer: '', 15 | examples: [], 16 | tags: [ 17 | { 18 | type: 'tag', 19 | raw: '@param {string} something', 20 | key: 'param', 21 | value: '{string} something' 22 | } 23 | ] 24 | }); 25 | }); 26 | 27 | it('should tokenize a comment with multiple tags', function() { 28 | const tok = tokenize(` 29 | /** 30 | * foo bar baz 31 | * 32 | * @param {string} something 33 | * @param {string} else 34 | */ 35 | `); 36 | 37 | assert.deepEqual(tok, { 38 | description: 'foo bar baz', 39 | footer: '', 40 | examples: [], 41 | tags: [ 42 | { 43 | key: 'param', 44 | raw: '@param {string} something', 45 | type: 'tag', 46 | value: '{string} something' 47 | }, 48 | { 49 | key: 'param', 50 | raw: '@param {string} else', 51 | type: 'tag', 52 | value: '{string} else' 53 | } 54 | ] 55 | }); 56 | }); 57 | 58 | it('should work with malformed tags', function() { 59 | const tok = tokenize(fixtures['tags-malformed-middle']); 60 | 61 | assert.deepEqual(tok, { 62 | description: '', 63 | footer: '', 64 | examples: [], 65 | tags: [{ 66 | type: 'tag', 67 | raw: '@private', 68 | key: 'private', 69 | value: '' 70 | }, { 71 | type: 'tag', 72 | raw: '@param {*} obj', 73 | key: 'param', 74 | value: '{*} obj' 75 | }, { 76 | type: 'tag', 77 | raw: '@param {*} obj true if `obj` is an array or array-like object (NodeList, Arguments,\n String ...)', 78 | key: 'param', 79 | value: '{*} obj true if `obj` is an array or array-like object (NodeList, Arguments,\n String ...)' 80 | }, { 81 | type: 'tag', 82 | raw: '@return {boolean}', 83 | key: 'return', 84 | value: '{boolean}' 85 | }] 86 | }); 87 | }); 88 | 89 | it('should work with trailing malformed tags', function() { 90 | const tok = tokenize(fixtures['tags-malformed-trailing']); 91 | assert.deepEqual(tok, { 92 | description: '', 93 | footer: '', 94 | examples: [], 95 | tags: [ 96 | { 97 | type: 'tag', 98 | raw: '@private', 99 | key: 'private', 100 | value: '' 101 | }, 102 | { 103 | type: 'tag', 104 | raw: '@param {*} obj', 105 | key: 'param', 106 | value: '{*} obj' 107 | }, 108 | { 109 | type: 'tag', 110 | raw: '@return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,\n String ...)', 111 | key: 'return', 112 | value: '{boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,\n String ...)' 113 | } 114 | ] 115 | }); 116 | }); 117 | 118 | it('should tokenize a comment with no tags', function() { 119 | const tok = tokenize(fixtures['description-no-tags']); 120 | 121 | assert.deepEqual(tok, { 122 | description: 'documentMode is an IE-only property\nhttp://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx', 123 | footer: '', 124 | examples: [], 125 | tags: [] 126 | }); 127 | }); 128 | 129 | it('should tokenize multi-line tags', function() { 130 | const tok = tokenize([ 131 | '/**', 132 | ' * @param {string|', 133 | ' * number} userName', 134 | ' * }}', 135 | ' */' 136 | ].join('\n')); 137 | 138 | assert.deepEqual(tok, { 139 | description: '', 140 | footer: '', 141 | examples: [], 142 | tags: [ 143 | { 144 | key: 'param', 145 | raw: '@param {string|\n number} userName\n }}', 146 | type: 'tag', 147 | value: '{string|\n number} userName\n }}' 148 | } 149 | ] 150 | }); 151 | }); 152 | 153 | it('should tokenize a comment that starts with a @description tag', function() { 154 | const tok = tokenize(fixtures['description-tag'].replace(/\/\/[^\n]+/, '')); 155 | 156 | assert.deepEqual(tok, { 157 | description: 'This object provides a utility for producing rich Error messages within\n Angular. It can be called as follows:\n\n var exampleMinErr = minErr(\'example\');\n throw exampleMinErr(\'one\', \'This {0} is {1}\', foo, bar);\n\n The above creates an instance of minErr in the example namespace. The\n resulting error will have a namespaced error code of example.one. The\n resulting error will replace {0} with the value of foo, and {1} with the\n value of bar. The object is not restricted in the number of arguments it can\n take.\n\n If fewer arguments are specified than necessary for interpolation, the extra\n interpolation markers will be preserved in the final string.\n\n Since data will be parsed statically during a build step, some restrictions\n are applied with respect to how minErr instances are created and called.\n Instances should have names of the form namespaceMinErr for a minErr created\n using minErr(\'namespace\') . Error codes, namespaces and template strings\n should all be static strings, not variables or general expressions.', 158 | footer: '', 159 | examples: [], 160 | tags: [ 161 | { 162 | type: 'tag', 163 | raw: '@param {string} module The namespace to use for the new minErr instance.', 164 | key: 'param', 165 | value: '{string} module The namespace to use for the new minErr instance.' 166 | }, 167 | { 168 | type: 'tag', 169 | raw: '@param {function} ErrorConstructor Custom error constructor to be instantiated when returning\n error from returned function, for cases when a particular type of error is useful.', 170 | key: 'param', 171 | value: '{function} ErrorConstructor Custom error constructor to be instantiated when returning\n error from returned function, for cases when a particular type of error is useful.' 172 | }, 173 | { 174 | type: 'tag', 175 | raw: '@returns {function(code:string, template:string, ...templateArgs): Error} minErr instance', 176 | key: 'returns', 177 | value: '{function(code:string, template:string, ...templateArgs): Error} minErr instance' 178 | } 179 | ] 180 | }); 181 | }); 182 | 183 | it('should tokenize a comment with a @description tag in the middle', function() { 184 | const tok1 = tokenize(fixtures['description-tag-middle'].replace(/\/\/[^\n]+/, '')); 185 | 186 | assert.deepEqual(tok1, { 187 | description: '# ng (core module)\n The ng module is loaded by default when an AngularJS application is started. The module itself\n contains the essential components for an AngularJS application to function. The table below\n lists a high level breakdown of each of the services/factories, filters, directives and testing\n components available within this core module.\n\n
', 188 | footer: '', 189 | examples: [], 190 | tags: [ 191 | { 192 | key: 'ngdoc', 193 | raw: '@ngdoc module', 194 | type: 'tag', 195 | value: 'module' 196 | }, 197 | { 198 | key: 'name', 199 | raw: '@name ng', 200 | type: 'tag', 201 | value: 'ng' 202 | }, 203 | { 204 | key: 'module', 205 | raw: '@module ng', 206 | type: 'tag', 207 | value: 'ng' 208 | } 209 | ] 210 | }); 211 | 212 | const tok2 = tokenize(fixtures['description-tag-middle2'].replace(/\/\/[^\n]+/, '')); 213 | 214 | assert.deepEqual(tok2, { 215 | description: 'Converts the specified string to lowercase.', 216 | footer: '', 217 | examples: [], 218 | tags: [ 219 | { 220 | key: 'ngdoc', 221 | raw: '@ngdoc function', 222 | type: 'tag', 223 | value: 'function' 224 | }, 225 | { 226 | key: 'name', 227 | raw: '@name angular.lowercase', 228 | type: 'tag', 229 | value: 'angular.lowercase' 230 | }, 231 | { 232 | key: 'module', 233 | raw: '@module ng', 234 | type: 'tag', 235 | value: 'ng' 236 | }, 237 | { 238 | key: 'kind', 239 | raw: '@kind function', 240 | type: 'tag', 241 | value: 'function' 242 | }, 243 | { 244 | key: 'param', 245 | raw: '@param {string} string String to be converted to lowercase.', 246 | type: 'tag', 247 | value: '{string} string String to be converted to lowercase.' 248 | }, 249 | { 250 | key: 'returns', 251 | raw: '@returns {string} Lowercased string.', 252 | type: 'tag', 253 | value: '{string} Lowercased string.' 254 | } 255 | ] 256 | }); 257 | }); 258 | }); 259 | -------------------------------------------------------------------------------- /test/fixtures/huge.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @ngdoc type 4 | * @name ngModel.NgModelController 5 | * 6 | * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 7 | * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 8 | * is set. 9 | * @property {*} $modelValue The value in the model that the control is bound to. 10 | * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever 11 | the control reads value from the DOM. The functions are called in array order, each passing 12 | its return value through to the next. The last return value is forwarded to the 13 | {@link ngModel.NgModelController#$validators `$validators`} collection. 14 | 15 | Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 16 | `$viewValue`}. 17 | 18 | Returning `undefined` from a parser means a parse error occurred. In that case, 19 | no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 20 | will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 21 | is set to `true`. The parse error is stored in `ngModel.$error.parse`. 22 | 23 | * 24 | * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever 25 | the model value changes. The functions are called in reverse array order, each passing the value through to the 26 | next. The last return value is used as the actual DOM value. 27 | Used to format / convert values for display in the control. 28 | * ```js 29 | * function formatter(value) { 30 | * if (value) { 31 | * return value.toUpperCase(); 32 | * } 33 | * } 34 | * ngModel.$formatters.push(formatter); 35 | * ``` 36 | * 37 | * @property {Object.} $validators A collection of validators that are applied 38 | * whenever the model value changes. The key value within the object refers to the name of the 39 | * validator while the function refers to the validation operation. The validation operation is 40 | * provided with the model value as an argument and must return a true or false value depending 41 | * on the response of that validation. 42 | * 43 | * ```js 44 | * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 45 | * var value = modelValue || viewValue; 46 | * return /[0-9]+/.test(value) && 47 | * /[a-z]+/.test(value) && 48 | * /[A-Z]+/.test(value) && 49 | * /\W+/.test(value); 50 | * }; 51 | * ``` 52 | * 53 | * @property {Object.} $asyncValidators A collection of validations that are expected to 54 | * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 55 | * is expected to return a promise when it is run during the model validation process. Once the promise 56 | * is delivered then the validation status will be set to true when fulfilled and false when rejected. 57 | * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 58 | * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 59 | * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 60 | * will only run once all synchronous validators have passed. 61 | * 62 | * Please note that if $http is used then it is important that the server returns a success HTTP response code 63 | * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 64 | * 65 | * ```js 66 | * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 67 | * var value = modelValue || viewValue; 68 | * 69 | * // Lookup user by username 70 | * return $http.get('/api/users/' + value). 71 | * then(function resolved() { 72 | * //username exists, this means validation fails 73 | * return $q.reject('exists'); 74 | * }, function rejected() { 75 | * //username does not exist, therefore this validation passes 76 | * return true; 77 | * }); 78 | * }; 79 | * ``` 80 | * 81 | * @property {Array.} $viewChangeListeners Array of functions to execute whenever the 82 | * view value has changed. It is called with no arguments, and its return value is ignored. 83 | * This can be used in place of additional $watches against the model value. 84 | * 85 | * @property {Object} $error An object hash with all failing validator ids as keys. 86 | * @property {Object} $pending An object hash with all pending validator ids as keys. 87 | * 88 | * @property {boolean} $untouched True if control has not lost focus yet. 89 | * @property {boolean} $touched True if control has lost focus. 90 | * @property {boolean} $pristine True if user has not interacted with the control yet. 91 | * @property {boolean} $dirty True if user has already interacted with the control. 92 | * @property {boolean} $valid True if there is no error. 93 | * @property {boolean} $invalid True if at least one error on the control. 94 | * @property {string} $name The name attribute of the control. 95 | * 96 | * @description 97 | * 98 | * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 99 | * The controller contains services for data-binding, validation, CSS updates, and value formatting 100 | * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 101 | * listening to DOM events. 102 | * Such DOM related logic should be provided by other directives which make use of 103 | * `NgModelController` for data-binding to control elements. 104 | * Angular provides this DOM logic for most {@link input `input`} elements. 105 | * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 106 | * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 107 | * 108 | * @example 109 | * ### Custom Control Example 110 | * This example shows how to use `NgModelController` with a custom control to achieve 111 | * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 112 | * collaborate together to achieve the desired result. 113 | * 114 | * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 115 | * contents be edited in place by the user. 116 | * 117 | * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 118 | * module to automatically remove "bad" content like inline event listener (e.g. ``). 119 | * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 120 | * that content using the `$sce` service. 121 | * 122 | * 123 | 124 | [contenteditable] { 125 | border: 1px solid black; 126 | background-color: white; 127 | min-height: 20px; 128 | } 129 | 130 | .ng-invalid { 131 | border: 1px solid red; 132 | } 133 | 134 | 135 | 136 | angular.module('customControl', ['ngSanitize']). 137 | directive('contenteditable', ['$sce', function($sce) { 138 | return { 139 | restrict: 'A', // only activate on element attribute 140 | require: '?ngModel', // get a hold of NgModelController 141 | link: function(scope, element, attrs, ngModel) { 142 | if (!ngModel) return; // do nothing if no ng-model 143 | 144 | // Specify how UI should be updated 145 | ngModel.$render = function() { 146 | element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 147 | }; 148 | 149 | // Listen for change events to enable binding 150 | element.on('blur keyup change', function() { 151 | scope.$evalAsync(read); 152 | }); 153 | read(); // initialize 154 | 155 | // Write data to the model 156 | function read() { 157 | var html = element.html(); 158 | // When we clear the content editable the browser leaves a
behind 159 | // If strip-br attribute is provided then we strip this out 160 | if ( attrs.stripBr && html == '
' ) { 161 | html = ''; 162 | } 163 | ngModel.$setViewValue(html); 164 | } 165 | } 166 | }; 167 | }]); 168 |
169 | 170 |
171 |
Change me!
175 | Required! 176 |
177 | 178 |
179 |
180 | 181 | it('should data-bind and become invalid', function() { 182 | if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 183 | // SafariDriver can't handle contenteditable 184 | // and Firefox driver can't clear contenteditables very well 185 | return; 186 | } 187 | var contentEditable = element(by.css('[contenteditable]')); 188 | var content = 'Change me!'; 189 | 190 | expect(contentEditable.getText()).toEqual(content); 191 | 192 | contentEditable.clear(); 193 | contentEditable.sendKeys(protractor.Key.BACK_SPACE); 194 | expect(contentEditable.getText()).toEqual(''); 195 | expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 196 | }); 197 | 198 | *
199 | * 200 | * 201 | */ -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 |
2 | What does this do? 3 | 4 | This is a node.js library for tokenizing a single JavaScript block comment into an object with the following properties: 5 | 6 | - `description` 7 | - `examples` 8 | - `tags` 9 | - `footer` 10 | 11 |
12 | 13 | 14 |
15 | Why is this necessary? 16 | 17 | After working with a number of different comment parsers and documentation systems, including [dox][], [doctrine][], [jsdoc][], [catharsis][], [closure-compiler](https://github.com/google/closure-compiler), [verb][], [js-comments][], [parse-comments][], among others, a few things became clear: 18 | 19 | - There are certain features that are common to all of them, but they are implemented in totally different ways, and they all return completely different objects 20 | - None of them follow the same conventions 21 | - None of them have solid support for markdown formatting or examples in code comments (some have basic support, but weird conventions that need to be followed) 22 | 23 | doctrine is a good example the disparity. It's a great parser that produces reliable results. But if you review the doctrine issues you'll see mention of the need to adhere to "jsdoc specifications" quite often. Unfortunately: 24 | 25 | - jsdoc is not a specification and does not have anything close to a spec to follow. Only docs. 26 | - Even if jsdoc did have a real spec, it's an attempt at implementing a Javadoc parser in JavaScript, which itself does not have a specification. Similarly, Oracle has some documentation for Javadoc, but no specification (at least, I could not find a spec and was informed there wasn't one when I contacted support) 27 | - where jsdoc falls short, doctrine augments the "spec" with closure compiler conventions (which also is not a real specification) 28 | - doctrine throws errors on a great variety of nuances and edge cases that jsdoc itself has no problem with and will normalize out for you 29 | 30 | To be clear, I'm not picking on doctrine, it's one of the better parsers (and I'm using it to parse the tags returned by `tokenize-comment`). 31 | 32 | **The solution** 33 | 34 | By tokenizing the comment first, we achieve the following: 35 | 36 | - it's fast, since we're only interested in sifting out descriptions from examples and tags 37 | - we get better support for different flavors of code examples (we can write indented or gfm code examples, or use the javadoc `@example` tag if we want) 38 | - we use doctrine, catharsis, or any other comment parser to do further processing on any of the values. 39 | 40 | As a result, you can write code examples the way you want, and still follow jsdoc conventions for every other feature. 41 | 42 | **Example** 43 | 44 | Given the following comment: 45 | 46 | ```js 47 | /** 48 | * foo bar baz 49 | * 50 | * ```js 51 | * var foo = "bar"; 52 | * ``` 53 | * @param {string} something 54 | * @param {string} else 55 | */ 56 | ``` 57 | 58 | `tokenize-comment` would return something like this: 59 | 60 | ```js 61 | { 62 | description: 'foo bar baz', 63 | footer: '', 64 | examples: [ 65 | { 66 | type: 'gfm', 67 | val: '```js\nvar foo = "bar";\n```', 68 | description: '', 69 | language: 'js', 70 | code: '\nvar foo = "bar";\n' 71 | } 72 | ], 73 | tags: [ 74 | { 75 | type: 'tag', 76 | raw: '@param {string} something', 77 | key: 'param', 78 | val: '{string} something' 79 | }, 80 | { 81 | type: 'tag', 82 | raw: '@param {string} else', 83 | key: 'param', 84 | val: '{string} else' 85 | } 86 | ] 87 | } 88 | ``` 89 | 90 | We can now pass each tag to `doctrine.parseTag()` or `catharsis.parse()`, and format the resulting comment object to follow whatever convention we want. You can do something similar with the other properties as well. 91 | 92 |
93 | 94 | 95 | ## Usage 96 | 97 | The main export is a function that takes a string with a single javascript comment only, _no code_. 98 | 99 | ```js 100 | var tokenize = require('{%= name %}'); 101 | var token = tokenize(commentString); 102 | console.log(token); 103 | ``` 104 | 105 | The comment can be a "raw" comment with leading stars: 106 | 107 | ```js 108 | /** 109 | * foo bar baz 110 | * @param {String} abc Some description 111 | * @param {Object} def Another description 112 | */ 113 | ``` 114 | 115 | Or a comment with stars already stripped (with or without leading whitespace): 116 | 117 | ```js 118 | foo bar baz 119 | @param {String} abc Some description 120 | @param {Object} def Another description 121 | ``` 122 | 123 | ## Code examples 124 | 125 | Recognizes gfm, javadoc and indented code examples. See [the unit tests](tests) for a number of more complex examples. 126 | 127 | ### GitHub Flavored Markdown 128 | 129 | Supports [GFM style code examples](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown). The following comment: 130 | 131 | ```js 132 | /** 133 | * foo bar baz 134 | * 135 | * ```js 136 | * var foo = "bar"; 137 | * ``` 138 | * @param {string} something 139 | * @param {string} else 140 | */ 141 | ``` 142 | 143 | Results in: 144 | 145 | ```js 146 | { 147 | description: 'foo bar baz', 148 | footer: '', 149 | examples: [ 150 | { 151 | type: 'gfm', 152 | val: '```js\nvar foo = "bar";\n```', 153 | description: '', 154 | language: 'js', 155 | code: '\nvar foo = "bar";\n' 156 | } 157 | ], 158 | tags: [ 159 | { 160 | type: 'tag', 161 | raw: '@param {string} something', 162 | key: 'param', 163 | val: '{string} something' 164 | }, 165 | { 166 | type: 'tag', 167 | raw: '@param {string} else', 168 | key: 'param', 169 | val: '{string} else' 170 | } 171 | ] 172 | } 173 | ``` 174 | 175 | ### Indented code 176 | 177 | Supports indented code examples: 178 | 179 | ```js 180 | /** 181 | * foo bar baz 182 | * 183 | * var foo = "bar"; 184 | * 185 | * @param {string} something 186 | * @param {string} else 187 | */ 188 | ``` 189 | 190 | 191 | ### javadoc (jsdoc) 192 | 193 | Supports [javadoc-style](https://en.wikipedia.org/wiki/Javadoc) code examples: 194 | 195 | ```js 196 | /** 197 | * foo bar baz 198 | * 199 | * @example 200 | * var foo = "bar"; 201 | * var baz = "qux"; 202 | * 203 | * @param {string} something 204 | * @param {string} else 205 | */ 206 | ``` 207 | 208 | Results in: 209 | 210 | ```js 211 | { 212 | description: 'foo bar baz', 213 | footer: '', 214 | examples: [ 215 | { 216 | type: 'javadoc', 217 | language: '', 218 | description: '', 219 | raw: '@example\nvar foo = "bar";\nvar baz = "qux";\n', 220 | val: '\nvar foo = "bar";\nvar baz = "qux";\n' 221 | } 222 | ], 223 | tags: [ 224 | { 225 | type: 'tag', 226 | raw: '@param {string} something', 227 | key: 'param', 228 | val: '{string} something' 229 | }, 230 | { 231 | type: 'tag', 232 | raw: '@param {string} else', 233 | key: 'param', 234 | val: '{string} else' 235 | } 236 | ] 237 | } 238 | ``` 239 | 240 | ## Mixture 241 | 242 | It will also recognize a mixture of formats (javadoc-style examples must always be last): 243 | 244 | ````js 245 | /** 246 | * This is a comment with 247 | * several lines of text. 248 | * 249 | * An example 250 | * 251 | * ```js 252 | * var foo = bar; 253 | * var foo = bar; 254 | * var foo = bar; 255 | * ``` 256 | * 257 | * Another example 258 | * 259 | * var baz = fez; 260 | * var baz = fez; 261 | * var baz = fez; 262 | * 263 | * Another example 264 | * 265 | * var baz = fez; 266 | * var baz = fez; 267 | * 268 | * 269 | * 270 | * And another example 271 | * 272 | * ```js 273 | * var foo = bar; 274 | * var foo = bar; 275 | * ``` 276 | * 277 | * Another example 278 | * 279 | * @example 280 | * var baz = fez; 281 | * 282 | * @example 283 | * // this is a comment 284 | * var alalla = zzzz; 285 | * 286 | * @param {String} foo bar 287 | * @returns {Object} Instance of Foo 288 | * @api public 289 | */ 290 | ```` 291 | 292 | Results in: 293 | 294 | ```js 295 | { 296 | description: 'This is a comment with\nseveral lines of text.', 297 | footer: '', 298 | examples: [ 299 | { 300 | type: 'gfm', 301 | language: 'js', 302 | description: 'An example', 303 | raw: '```js\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n```', 304 | val: '\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n' 305 | }, 306 | { 307 | type: 'indented', 308 | language: '', 309 | description: 'Another example', 310 | raw: ' var baz = fez;\n var baz = fez;\n var baz = fez;\n', 311 | val: 'var baz = fez;\nvar baz = fez;\nvar baz = fez;\n' 312 | }, 313 | { 314 | type: 'indented', 315 | language: '', 316 | description: 'Another example', 317 | raw: ' var baz = fez;\n var baz = fez;\n', 318 | val: 'var baz = fez;\nvar baz = fez;\n' 319 | }, 320 | { 321 | type: 'gfm', 322 | language: 'js', 323 | description: 'And another example', 324 | raw: '```js\nvar foo = bar;\nvar foo = bar;\n```', 325 | val: '\nvar foo = bar;\nvar foo = bar;\n' 326 | }, 327 | { 328 | type: 'javadoc', 329 | language: '', 330 | description: 'Another example', 331 | raw: '@example\nvar baz = fez;\n', 332 | val: '\nvar baz = fez;\n' 333 | }, 334 | { 335 | type: 'javadoc', 336 | language: '', 337 | description: '', 338 | raw: '@example\n// this is a comment\nvar alalla = zzzz;\n', 339 | val: '\n// this is a comment\nvar alalla = zzzz;\n' 340 | } 341 | ], 342 | tags: [ 343 | { 344 | type: 'tag', 345 | raw: '@param {String} foo bar', 346 | key: 'param', 347 | val: '{String} foo bar' 348 | }, 349 | { 350 | type: 'tag', 351 | raw: '@returns {Object} Instance of Foo', 352 | key: 'returns', 353 | val: '{Object} Instance of Foo' 354 | }, 355 | { 356 | type: 'tag', 357 | raw: '@api public', 358 | key: 'api', 359 | val: 'public' 360 | } 361 | ] 362 | } 363 | ``` 364 | -------------------------------------------------------------------------------- /test/multiline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const extract = require('extract-comments'); 6 | const support = require('./support'); 7 | const tokenize = require('..'); 8 | const fixtures = support.files(__dirname, 'fixtures'); 9 | 10 | describe('multi-line tags', function() { 11 | it('should tokenize multi-line tags', function() { 12 | const comments = extract(fixtures['multiline']) 13 | .filter(function(comment) { 14 | return comment.type === 'BlockComment'; 15 | }); 16 | 17 | // support.generate('multiline', 'multiline-units.js'); 18 | 19 | /** 20 | * only 21 | * 22 | * @sample 23 | * one 24 | * two 25 | * three 26 | */ 27 | 28 | assert.deepEqual(tokenize(comments[0].raw), { 29 | description: 'only', 30 | footer: '', 31 | examples: [], 32 | tags: [ 33 | { 34 | type: 'tag', 35 | raw: '@sample\none\ntwo\nthree', 36 | key: 'sample', 37 | value: 'one\ntwo\nthree' 38 | } 39 | ] 40 | }); 41 | 42 | /** 43 | * first 44 | * 45 | * @sample 46 | * one 47 | * two 48 | * three 49 | * @bar last 50 | */ 51 | 52 | assert.deepEqual(tokenize(comments[1].raw), { 53 | description: 'first', 54 | footer: '', 55 | examples: [], 56 | tags: [ 57 | { 58 | type: 'tag', 59 | raw: '@sample\none\ntwo\nthree', 60 | key: 'sample', 61 | value: 'one\ntwo\nthree' 62 | }, 63 | { 64 | type: 'tag', 65 | raw: '@bar last', 66 | key: 'bar', 67 | value: 'last' 68 | } 69 | ] 70 | }); 71 | 72 | /** 73 | * last 74 | * 75 | * @foo first 76 | * @sample 77 | * one 78 | * two 79 | * three 80 | */ 81 | 82 | assert.deepEqual(tokenize(comments[2].raw), { 83 | description: 'last', 84 | footer: '', 85 | examples: [], 86 | tags: [ 87 | { 88 | type: 'tag', 89 | raw: '@foo first', 90 | key: 'foo', 91 | value: 'first' 92 | }, 93 | { 94 | type: 'tag', 95 | raw: '@sample\none\ntwo\nthree', 96 | key: 'sample', 97 | value: 'one\ntwo\nthree' 98 | } 99 | ] 100 | }); 101 | 102 | /** 103 | * mid 104 | * 105 | * @foo first 106 | * @sample 107 | * one 108 | * two 109 | * three 110 | * @bar last 111 | */ 112 | 113 | assert.deepEqual(tokenize(comments[3].raw), { 114 | description: 'mid', 115 | footer: '', 116 | examples: [], 117 | tags: [ 118 | { 119 | type: 'tag', 120 | raw: '@foo first', 121 | key: 'foo', 122 | value: 'first' 123 | }, 124 | { 125 | type: 'tag', 126 | raw: '@sample\none\ntwo\nthree', 127 | key: 'sample', 128 | value: 'one\ntwo\nthree' 129 | }, 130 | { 131 | type: 'tag', 132 | raw: '@bar last', 133 | key: 'bar', 134 | value: 'last' 135 | } 136 | ] 137 | }); 138 | 139 | /** 140 | * only 141 | * 142 | * @param {String} foo 143 | * one 144 | * two 145 | * three 146 | */ 147 | 148 | assert.deepEqual(tokenize(comments[4].raw), { 149 | description: 'only', 150 | footer: '', 151 | examples: [], 152 | tags: [ 153 | { 154 | type: 'tag', 155 | raw: '@param {String} foo\none\ntwo\nthree', 156 | key: 'param', 157 | value: '{String} foo\none\ntwo\nthree' 158 | } 159 | ] 160 | }); 161 | 162 | /** 163 | * first 164 | * 165 | * @param {String} foo 166 | * one 167 | * two 168 | * three 169 | * @bar last 170 | */ 171 | 172 | assert.deepEqual(tokenize(comments[5].raw), { 173 | description: 'first', 174 | footer: '', 175 | examples: [], 176 | tags: [ 177 | { 178 | type: 'tag', 179 | raw: '@param {String} foo\none\ntwo\nthree', 180 | key: 'param', 181 | value: '{String} foo\none\ntwo\nthree' 182 | }, 183 | { 184 | type: 'tag', 185 | raw: '@bar last', 186 | key: 'bar', 187 | value: 'last' 188 | } 189 | ] 190 | }); 191 | 192 | /** 193 | * last 194 | * 195 | * @foo first 196 | * @param {String} foo 197 | * one 198 | * two 199 | * three 200 | */ 201 | 202 | assert.deepEqual(tokenize(comments[6].raw), { 203 | description: 'last', 204 | footer: '', 205 | examples: [], 206 | tags: [ 207 | { 208 | type: 'tag', 209 | raw: '@foo first', 210 | key: 'foo', 211 | value: 'first' 212 | }, 213 | { 214 | type: 'tag', 215 | raw: '@param {String} foo\none\ntwo\nthree', 216 | key: 'param', 217 | value: '{String} foo\none\ntwo\nthree' 218 | } 219 | ] 220 | }); 221 | 222 | /** 223 | * mid 224 | * 225 | * @foo first 226 | * @param {String} foo 227 | * one 228 | * two 229 | * three 230 | * @bar last 231 | */ 232 | 233 | assert.deepEqual(tokenize(comments[7].raw), { 234 | description: 'mid', 235 | footer: '', 236 | examples: [], 237 | tags: [ 238 | { 239 | type: 'tag', 240 | raw: '@foo first', 241 | key: 'foo', 242 | value: 'first' 243 | }, 244 | { 245 | type: 'tag', 246 | raw: '@param {String} foo\none\ntwo\nthree', 247 | key: 'param', 248 | value: '{String} foo\none\ntwo\nthree' 249 | }, 250 | { 251 | type: 'tag', 252 | raw: '@bar last', 253 | key: 'bar', 254 | value: 'last' 255 | } 256 | ] 257 | }); 258 | 259 | /** 260 | * only 261 | * 262 | * @return {String} 263 | * one 264 | * two 265 | * three 266 | */ 267 | 268 | assert.deepEqual(tokenize(comments[8].raw), { 269 | description: 'only', 270 | footer: '', 271 | examples: [], 272 | tags: [ 273 | { 274 | type: 'tag', 275 | raw: '@return {String}\none\ntwo\nthree', 276 | key: 'return', 277 | value: '{String}\none\ntwo\nthree' 278 | } 279 | ] 280 | }); 281 | 282 | /** 283 | * first 284 | * 285 | * @return {String} 286 | * one 287 | * two 288 | * three 289 | * @bar last 290 | */ 291 | 292 | assert.deepEqual(tokenize(comments[9].raw), { 293 | description: 'first', 294 | footer: '', 295 | examples: [], 296 | tags: [ 297 | { 298 | type: 'tag', 299 | raw: '@return {String}\none\ntwo\nthree', 300 | key: 'return', 301 | value: '{String}\none\ntwo\nthree' 302 | }, 303 | { 304 | type: 'tag', 305 | raw: '@bar last', 306 | key: 'bar', 307 | value: 'last' 308 | } 309 | ] 310 | }); 311 | 312 | /** 313 | * last 314 | * 315 | * @foo first 316 | * @return {String} 317 | * one 318 | * two 319 | * three 320 | */ 321 | 322 | assert.deepEqual(tokenize(comments[10].raw), { 323 | description: 'last', 324 | footer: '', 325 | examples: [], 326 | tags: [ 327 | { 328 | type: 'tag', 329 | raw: '@foo first', 330 | key: 'foo', 331 | value: 'first' 332 | }, 333 | { 334 | type: 'tag', 335 | raw: '@return {String}\none\ntwo\nthree', 336 | key: 'return', 337 | value: '{String}\none\ntwo\nthree' 338 | } 339 | ] 340 | }); 341 | 342 | /** 343 | * mid 344 | * 345 | * @foo first 346 | * @return {String} 347 | * one 348 | * two 349 | * three 350 | * @bar last 351 | */ 352 | 353 | assert.deepEqual(tokenize(comments[11].raw), { 354 | description: 'mid', 355 | footer: '', 356 | examples: [], 357 | tags: [ 358 | { 359 | type: 'tag', 360 | raw: '@foo first', 361 | key: 'foo', 362 | value: 'first' 363 | }, 364 | { 365 | type: 'tag', 366 | raw: '@return {String}\none\ntwo\nthree', 367 | key: 'return', 368 | value: '{String}\none\ntwo\nthree' 369 | }, 370 | { 371 | type: 'tag', 372 | raw: '@bar last', 373 | key: 'bar', 374 | value: 'last' 375 | } 376 | ] 377 | }); 378 | 379 | /** 380 | * example 381 | * 382 | * @example 383 | * test(one); 384 | * test(two); 385 | */ 386 | 387 | assert.deepEqual(tokenize(comments[12].raw), { 388 | description: 'example', 389 | footer: '', 390 | examples: [ 391 | { 392 | type: 'javadoc', 393 | language: '', 394 | description: '', 395 | raw: '@example\n test(one);\n test(two);\n', 396 | value: '\n test(one);\n test(two);\n' 397 | } 398 | ], 399 | tags: [] 400 | }); 401 | 402 | /** 403 | * @tag-1 foo 404 | * @tag-2 bar 405 | * 406 | * @tag-3 baz 407 | */ 408 | 409 | assert.deepEqual(tokenize(comments[13].raw), { 410 | description: '', 411 | footer: '', 412 | examples: [], 413 | tags: [ 414 | { 415 | type: 'tag', 416 | raw: '@tag-1 foo', 417 | key: 'tag-1', 418 | value: 'foo' 419 | }, 420 | { 421 | type: 'tag', 422 | raw: '@tag-2 bar', 423 | key: 'tag-2', 424 | value: 'bar' 425 | }, 426 | { 427 | type: 'tag', 428 | raw: '@tag-3 baz', 429 | key: 'tag-3', 430 | value: 'baz' 431 | } 432 | ] 433 | }); 434 | 435 | /** 436 | * @tag-1 437 | * foo 438 | * @tag-2 439 | * bar 440 | * 441 | * @tag-3 442 | * baz 443 | */ 444 | 445 | assert.deepEqual(tokenize(comments[14].raw), { 446 | description: '', 447 | footer: '', 448 | examples: [], 449 | tags: [ 450 | { 451 | type: 'tag', 452 | raw: '@tag-1\n foo', 453 | key: 'tag-1', 454 | value: 'foo' 455 | }, 456 | { 457 | type: 'tag', 458 | raw: '@tag-2\n bar', 459 | key: 'tag-2', 460 | value: 'bar' 461 | }, 462 | { 463 | type: 'tag', 464 | raw: '@tag-3\n baz', 465 | key: 'tag-3', 466 | value: 'baz' 467 | } 468 | ] 469 | }); 470 | }); 471 | }); 472 | -------------------------------------------------------------------------------- /test/examples-gfm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const support = require('./support'); 6 | const tokenize = require('..'); 7 | const fixtures = support.files(__dirname, 'fixtures'); 8 | 9 | describe('gfm', function() { 10 | it('should throw an error when a fence is missing', function() { 11 | assert.throws(function() { 12 | tokenize([ 13 | '/**', 14 | ' * foo bar baz', 15 | ' * ', 16 | ' * ```js', 17 | ' * var foo = "bar";', 18 | ' *', 19 | ' *', 20 | ' * @param {string} something', 21 | ' * @param {string} else', 22 | ' */' 23 | ].join('\n')); 24 | }); 25 | 26 | assert.throws(function() { 27 | tokenize([ 28 | '/**', 29 | ' * foo bar baz', 30 | ' * ', 31 | ' * ````js', 32 | ' * var foo = "bar";', 33 | ' *', 34 | ' *', 35 | ' * @param {string} something', 36 | ' * @param {string} else', 37 | ' */' 38 | ].join('\n')); 39 | }); 40 | }); 41 | 42 | it('should tokenize gfm code examples', function() { 43 | const tok = tokenize([ 44 | '/**', 45 | ' * foo bar baz', 46 | ' * ', 47 | ' * ```js', 48 | ' * var foo = "bar";', 49 | ' * ```', 50 | ' *', 51 | ' * @param {string} something', 52 | ' * @param {string} else', 53 | ' */' 54 | ].join('\n')); 55 | 56 | assert.deepEqual(tok, { 57 | description: 'foo bar baz', 58 | footer: '', 59 | examples: [ 60 | { 61 | type: 'gfm', 62 | language: 'js', 63 | description: '', 64 | raw: '```js\nvar foo = "bar";\n```', 65 | value: '\nvar foo = "bar";\n' 66 | } 67 | ], 68 | tags: [ 69 | { 70 | type: 'tag', 71 | raw: '@param {string} something', 72 | key: 'param', 73 | value: '{string} something' 74 | }, 75 | { 76 | type: 'tag', 77 | raw: '@param {string} else', 78 | key: 'param', 79 | value: '{string} else' 80 | } 81 | ] 82 | }); 83 | }); 84 | 85 | it('should tokenize gfm code examples with four backticks', function() { 86 | const tok = tokenize([ 87 | '/**', 88 | ' * foo bar baz', 89 | ' * ', 90 | ' * ````js', 91 | ' * var foo = "bar";', 92 | ' * ````', 93 | ' *', 94 | ' * @param {string} something', 95 | ' * @param {string} else', 96 | ' */' 97 | ].join('\n')); 98 | 99 | assert.deepEqual(tok, { 100 | description: 'foo bar baz', 101 | footer: '', 102 | examples: [ 103 | { 104 | type: 'gfm', 105 | language: 'js', 106 | description: '', 107 | raw: '````js\nvar foo = "bar";\n````', 108 | value: '\nvar foo = "bar";\n' 109 | } 110 | ], 111 | tags: [ 112 | { 113 | type: 'tag', 114 | raw: '@param {string} something', 115 | key: 'param', 116 | value: '{string} something' 117 | }, 118 | { 119 | type: 'tag', 120 | raw: '@param {string} else', 121 | key: 'param', 122 | value: '{string} else' 123 | } 124 | ] 125 | }); 126 | }); 127 | 128 | it('should preserve indentation in gfm code examples', function() { 129 | const tok = tokenize([ 130 | '/**', 131 | ' * foo bar baz', 132 | ' * ', 133 | ' * ```js', 134 | ' * var foo = "bar";', 135 | ' * var baz = "qux";', 136 | ' * ```', 137 | ' *', 138 | ' * @param {string} something', 139 | ' * @param {string} else', 140 | ' */' 141 | ].join('\n')); 142 | 143 | assert.deepEqual(tok, { 144 | description: 'foo bar baz', 145 | footer: '', 146 | examples: [ 147 | { 148 | type: 'gfm', 149 | language: 'js', 150 | description: '', 151 | raw: '```js\n var foo = "bar";\n var baz = "qux";\n```', 152 | value: '\n var foo = "bar";\n var baz = "qux";\n' 153 | } 154 | ], 155 | tags: [ 156 | { 157 | type: 'tag', 158 | raw: '@param {string} something', 159 | key: 'param', 160 | value: '{string} something' 161 | }, 162 | { 163 | type: 'tag', 164 | raw: '@param {string} else', 165 | key: 'param', 166 | value: '{string} else' 167 | } 168 | ] 169 | }); 170 | }); 171 | 172 | it('should detect a description for a gfm code example', function() { 173 | const tok = tokenize([ 174 | '/**', 175 | ' * foo bar baz', 176 | ' * ', 177 | ' * This is a description for an example.', 178 | ' * ```js', 179 | ' * var foo = "bar";', 180 | ' * var baz = "qux";', 181 | ' * ```', 182 | ' *', 183 | ' * @param {string} something', 184 | ' * @param {string} else', 185 | ' */' 186 | ].join('\n')); 187 | 188 | assert.deepEqual(tok, { 189 | description: 'foo bar baz', 190 | footer: '', 191 | examples: [ 192 | { 193 | type: 'gfm', 194 | language: 'js', 195 | description: 'This is a description for an example.', 196 | raw: '```js\nvar foo = "bar";\nvar baz = "qux";\n```', 197 | value: '\nvar foo = "bar";\nvar baz = "qux";\n' 198 | } 199 | ], 200 | tags: [ 201 | { 202 | type: 'tag', 203 | raw: '@param {string} something', 204 | key: 'param', 205 | value: '{string} something' 206 | }, 207 | { 208 | type: 'tag', 209 | raw: '@param {string} else', 210 | key: 'param', 211 | value: '{string} else' 212 | } 213 | ] 214 | }); 215 | }); 216 | 217 | it('should detect a description & leading newline for a gfm example', function() { 218 | const tok = tokenize([ 219 | '/**', 220 | ' * foo bar baz', 221 | ' * ', 222 | ' * This is a description for an example.', 223 | ' *', 224 | ' * ```js', 225 | ' * var foo = "bar";', 226 | ' * var baz = "qux";', 227 | ' * ```', 228 | ' *', 229 | ' * @param {string} something', 230 | ' * @param {string} else', 231 | ' */' 232 | ].join('\n')); 233 | 234 | assert.deepEqual(tok, { 235 | description: 'foo bar baz', 236 | footer: '', 237 | examples: [ 238 | { 239 | type: 'gfm', 240 | language: 'js', 241 | description: 'This is a description for an example.', 242 | raw: '```js\nvar foo = "bar";\nvar baz = "qux";\n```', 243 | value: '\nvar foo = "bar";\nvar baz = "qux";\n' 244 | } 245 | ], 246 | tags: [ 247 | { 248 | type: 'tag', 249 | raw: '@param {string} something', 250 | key: 'param', 251 | value: '{string} something' 252 | }, 253 | { 254 | type: 'tag', 255 | raw: '@param {string} else', 256 | key: 'param', 257 | value: '{string} else' 258 | } 259 | ] 260 | }); 261 | }); 262 | 263 | it('should support gfm examples without extra leading/trailing newlines', function() { 264 | const tok = tokenize([ 265 | '/**', 266 | ' * foo bar baz', 267 | ' * ', 268 | ' * This is a description for an example.', 269 | ' * ```js', 270 | ' * var foo = "bar";', 271 | ' * var baz = "qux";', 272 | ' * ```', 273 | ' * @param {string} something', 274 | ' * @param {string} else', 275 | ' */' 276 | ].join('\n')); 277 | 278 | assert.deepEqual(tok, { 279 | description: 'foo bar baz', 280 | footer: '', 281 | examples: [ 282 | { 283 | type: 'gfm', 284 | language: 'js', 285 | description: 'This is a description for an example.', 286 | raw: '```js\nvar foo = "bar";\nvar baz = "qux";\n```', 287 | value: '\nvar foo = "bar";\nvar baz = "qux";\n' 288 | } 289 | ], 290 | tags: [ 291 | { 292 | type: 'tag', 293 | raw: '@param {string} something', 294 | key: 'param', 295 | value: '{string} something' 296 | }, 297 | { 298 | type: 'tag', 299 | raw: '@param {string} else', 300 | key: 'param', 301 | value: '{string} else' 302 | } 303 | ] 304 | }); 305 | }); 306 | 307 | it('should work when no stars prefix the gfm example', function() { 308 | const tok = tokenize(fixtures['examples-gfm-no-stars']); 309 | 310 | assert.deepEqual(tok, { 311 | description: 'Invokes the `iterator` function once for each item in `obj` collection, which can be either an\n object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`\n is the value of an object property or an array element, `key` is the object property key or\n array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.\n\n It is worth noting that `.forEach` does not iterate over inherited properties because it filters\n using the `hasOwnProperty` method.', 312 | footer: '', 313 | examples: [{ 314 | type: 'gfm', 315 | language: 'js', 316 | description: 'Unlike ES262\'s\n [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),\n Providing \'undefined\' or \'null\' values for `obj` will not throw a TypeError, but rather just\n return the value provided.', 317 | raw: ' ```js\n var values = {name: \'misko\', gender: \'male\'};\n var log = [];\n angular.forEach(values, function(value, key) {\n this.push(key + \': \' + value);\n }, log);\n expect(log).toEqual([\'name: misko\', \'gender: male\']);\n ```', 318 | value: '\n var values = {name: \'misko\', gender: \'male\'};\n var log = [];\n angular.forEach(values, function(value, key) {\n this.push(key + \': \' + value);\n }, log);\n expect(log).toEqual([\'name: misko\', \'gender: male\']);\n' 319 | }], 320 | tags: [{ 321 | type: 'tag', 322 | raw: '@ngdoc function', 323 | key: 'ngdoc', 324 | value: 'function' 325 | }, { 326 | type: 'tag', 327 | raw: '@name angular.forEach', 328 | key: 'name', 329 | value: 'angular.forEach' 330 | }, { 331 | type: 'tag', 332 | raw: '@module ng', 333 | key: 'module', 334 | value: 'ng' 335 | }, { 336 | type: 'tag', 337 | raw: '@kind function', 338 | key: 'kind', 339 | value: 'function' 340 | }, { 341 | type: 'tag', 342 | raw: '@param {Object|Array} obj Object to iterate over.', 343 | key: 'param', 344 | value: '{Object|Array} obj Object to iterate over.' 345 | }, { 346 | type: 'tag', 347 | raw: '@param {Function} iterator Iterator function.', 348 | key: 'param', 349 | value: '{Function} iterator Iterator function.' 350 | }, { 351 | type: 'tag', 352 | raw: '@param {Object=} context Object to become context (`this`) for the iterator function.', 353 | key: 'param', 354 | value: '{Object=} context Object to become context (`this`) for the iterator function.' 355 | }, { 356 | type: 'tag', 357 | raw: '@returns {Object|Array} Reference to `obj`.', 358 | key: 'returns', 359 | value: '{Object|Array} Reference to `obj`.' 360 | }] 361 | }); 362 | }); 363 | }); 364 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tokenize-comment [![NPM version](https://img.shields.io/npm/v/tokenize-comment.svg?style=flat)](https://www.npmjs.com/package/tokenize-comment) [![NPM monthly downloads](https://img.shields.io/npm/dm/tokenize-comment.svg?style=flat)](https://npmjs.org/package/tokenize-comment) [![NPM total downloads](https://img.shields.io/npm/dt/tokenize-comment.svg?style=flat)](https://npmjs.org/package/tokenize-comment) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/tokenize-comment.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/tokenize-comment) 2 | 3 | > Uses snapdragon to tokenize a single JavaScript block comment into an object, with description, tags, and code example sections that can be passed to any other comment parsers for further parsing. 4 | 5 | ## Install 6 | 7 | Install with [npm](https://www.npmjs.com/): 8 | 9 | ```sh 10 | $ npm install --save tokenize-comment 11 | ``` 12 | 13 |
14 | What does this do? 15 | 16 | This is a node.js library for tokenizing a single JavaScript block comment into an object with the following properties: 17 | 18 | * `description` 19 | * `examples` 20 | * `tags` 21 | * `footer` 22 | 23 |
24 | 25 |
26 | Why is this necessary? 27 | 28 | After working with a number of different comment parsers and documentation systems, including [dox](https://github.com/tj/dox), [doctrine](https://github.com/eslint/doctrine), [jsdoc](https://github.com/jsdoc3/jsdoc), [catharsis](https://github.com/hegemonic/catharsis), [closure-compiler](https://github.com/google/closure-compiler), [verb](https://github.com/verbose/verb), [js-comments](https://github.com/jonschlinkert/js-comments), [parse-comments](https://github.com/jonschlinkert/parse-comments), among others, a few things became clear: 29 | 30 | * There are certain features that are common to all of them, but they are implemented in totally different ways, and they all return completely different objects 31 | * None of them follow the same conventions 32 | * None of them have solid support for markdown formatting or examples in code comments (some have basic support, but weird conventions that need to be followed) 33 | 34 | doctrine is a good example the disparity. It's a great parser that produces reliable results. But if you review the doctrine issues you'll see mention of the need to adhere to "jsdoc specifications" quite often. Unfortunately: 35 | 36 | * jsdoc is not a specification and does not have anything close to a spec to follow. Only docs. 37 | * Even if jsdoc did have a real spec, it's an attempt at implementing a Javadoc parser in JavaScript, which itself does not have a specification. Similarly, Oracle has some documentation for Javadoc, but no specification (at least, I could not find a spec and was informed there wasn't one when I contacted support) 38 | * where jsdoc falls short, doctrine augments the "spec" with closure compiler conventions (which also is not a real specification) 39 | * doctrine throws errors on a great variety of nuances and edge cases that jsdoc itself has no problem with and will normalize out for you 40 | 41 | To be clear, I'm not picking on doctrine, it's one of the better parsers (and I'm using it to parse the tags returned by `tokenize-comment`). 42 | 43 | **The solution** 44 | 45 | By tokenizing the comment first, we achieve the following: 46 | 47 | * it's fast, since we're only interested in sifting out descriptions from examples and tags 48 | * we get better support for different flavors of code examples (we can write indented or gfm code examples, or use the javadoc `@example` tag if we want) 49 | * we use doctrine, catharsis, or any other comment parser to do further processing on any of the values. 50 | 51 | As a result, you can write code examples the way you want, and still follow jsdoc conventions for every other feature. 52 | 53 | **Example** 54 | 55 | Given the following comment: 56 | 57 | ```js 58 | /** 59 | * foo bar baz 60 | * 61 | * ```js 62 | * var foo = "bar"; 63 | * ``` 64 | * @param {string} something 65 | * @param {string} else 66 | */ 67 | ``` 68 | 69 | `tokenize-comment` would return something like this: 70 | 71 | ```js 72 | { 73 | description: 'foo bar baz', 74 | footer: '', 75 | examples: [ 76 | { 77 | type: 'gfm', 78 | val: '```js\nvar foo = "bar";\n```', 79 | description: '', 80 | language: 'js', 81 | code: '\nvar foo = "bar";\n' 82 | } 83 | ], 84 | tags: [ 85 | { 86 | type: 'tag', 87 | raw: '@param {string} something', 88 | key: 'param', 89 | val: '{string} something' 90 | }, 91 | { 92 | type: 'tag', 93 | raw: '@param {string} else', 94 | key: 'param', 95 | val: '{string} else' 96 | } 97 | ] 98 | } 99 | ``` 100 | 101 | We can now pass each tag to `doctrine.parseTag()` or `catharsis.parse()`, and format the resulting comment object to follow whatever convention we want. You can do something similar with the other properties as well. 102 | 103 |
104 | 105 | ## Usage 106 | 107 | The main export is a function that takes a string with a single javascript comment only, _no code_. 108 | 109 | ```js 110 | var tokenize = require('tokenize-comment'); 111 | var token = tokenize(commentString); 112 | console.log(token); 113 | ``` 114 | 115 | The comment can be a "raw" comment with leading stars: 116 | 117 | ```js 118 | /** 119 | * foo bar baz 120 | * @param {String} abc Some description 121 | * @param {Object} def Another description 122 | */ 123 | ``` 124 | 125 | Or a comment with stars already stripped (with or without leading whitespace): 126 | 127 | ```js 128 | foo bar baz 129 | @param {String} abc Some description 130 | @param {Object} def Another description 131 | ``` 132 | 133 | ## Code examples 134 | 135 | Recognizes gfm, javadoc and indented code examples. See [the unit tests](tests) for a number of more complex examples. 136 | 137 | ### GitHub Flavored Markdown 138 | 139 | Supports [GFM style code examples](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown). The following comment: 140 | 141 | ```js 142 | /** 143 | * foo bar baz 144 | * 145 | * ```js 146 | * var foo = "bar"; 147 | * ``` 148 | * @param {string} something 149 | * @param {string} else 150 | */ 151 | ``` 152 | 153 | Results in: 154 | 155 | ```js 156 | { 157 | description: 'foo bar baz', 158 | footer: '', 159 | examples: [ 160 | { 161 | type: 'gfm', 162 | val: '```js\nvar foo = "bar";\n```', 163 | description: '', 164 | language: 'js', 165 | code: '\nvar foo = "bar";\n' 166 | } 167 | ], 168 | tags: [ 169 | { 170 | type: 'tag', 171 | raw: '@param {string} something', 172 | key: 'param', 173 | val: '{string} something' 174 | }, 175 | { 176 | type: 'tag', 177 | raw: '@param {string} else', 178 | key: 'param', 179 | val: '{string} else' 180 | } 181 | ] 182 | } 183 | ``` 184 | 185 | ### Indented code 186 | 187 | Supports indented code examples: 188 | 189 | ```js 190 | /** 191 | * foo bar baz 192 | * 193 | * var foo = "bar"; 194 | * 195 | * @param {string} something 196 | * @param {string} else 197 | */ 198 | ``` 199 | 200 | ### javadoc (jsdoc) 201 | 202 | Supports [javadoc-style](https://en.wikipedia.org/wiki/Javadoc) code examples: 203 | 204 | ```js 205 | /** 206 | * foo bar baz 207 | * 208 | * @example 209 | * var foo = "bar"; 210 | * var baz = "qux"; 211 | * 212 | * @param {string} something 213 | * @param {string} else 214 | */ 215 | ``` 216 | 217 | Results in: 218 | 219 | ```js 220 | { 221 | description: 'foo bar baz', 222 | footer: '', 223 | examples: [ 224 | { 225 | type: 'javadoc', 226 | language: '', 227 | description: '', 228 | raw: '@example\nvar foo = "bar";\nvar baz = "qux";\n', 229 | val: '\nvar foo = "bar";\nvar baz = "qux";\n' 230 | } 231 | ], 232 | tags: [ 233 | { 234 | type: 'tag', 235 | raw: '@param {string} something', 236 | key: 'param', 237 | val: '{string} something' 238 | }, 239 | { 240 | type: 'tag', 241 | raw: '@param {string} else', 242 | key: 'param', 243 | val: '{string} else' 244 | } 245 | ] 246 | } 247 | ``` 248 | 249 | ## Mixture 250 | 251 | It will also recognize a mixture of formats (javadoc-style examples must always be last): 252 | 253 | ````js 254 | /** 255 | * This is a comment with 256 | * several lines of text. 257 | * 258 | * An example 259 | * 260 | * ```js 261 | * var foo = bar; 262 | * var foo = bar; 263 | * var foo = bar; 264 | * ``` 265 | * 266 | * Another example 267 | * 268 | * var baz = fez; 269 | * var baz = fez; 270 | * var baz = fez; 271 | * 272 | * Another example 273 | * 274 | * var baz = fez; 275 | * var baz = fez; 276 | * 277 | * 278 | * 279 | * And another example 280 | * 281 | * ```js 282 | * var foo = bar; 283 | * var foo = bar; 284 | * ``` 285 | * 286 | * Another example 287 | * 288 | * @example 289 | * var baz = fez; 290 | * 291 | * @example 292 | * // this is a comment 293 | * var alalla = zzzz; 294 | * 295 | * @param {String} foo bar 296 | * @returns {Object} Instance of Foo 297 | * @api public 298 | */ 299 | ```` 300 | 301 | Results in: 302 | 303 | ```js 304 | { 305 | description: 'This is a comment with\nseveral lines of text.', 306 | footer: '', 307 | examples: [ 308 | { 309 | type: 'gfm', 310 | language: 'js', 311 | description: 'An example', 312 | raw: '```js\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n```', 313 | val: '\nvar foo = bar;\nvar foo = bar;\nvar foo = bar;\n' 314 | }, 315 | { 316 | type: 'indented', 317 | language: '', 318 | description: 'Another example', 319 | raw: ' var baz = fez;\n var baz = fez;\n var baz = fez;\n', 320 | val: 'var baz = fez;\nvar baz = fez;\nvar baz = fez;\n' 321 | }, 322 | { 323 | type: 'indented', 324 | language: '', 325 | description: 'Another example', 326 | raw: ' var baz = fez;\n var baz = fez;\n', 327 | val: 'var baz = fez;\nvar baz = fez;\n' 328 | }, 329 | { 330 | type: 'gfm', 331 | language: 'js', 332 | description: 'And another example', 333 | raw: '```js\nvar foo = bar;\nvar foo = bar;\n```', 334 | val: '\nvar foo = bar;\nvar foo = bar;\n' 335 | }, 336 | { 337 | type: 'javadoc', 338 | language: '', 339 | description: 'Another example', 340 | raw: '@example\nvar baz = fez;\n', 341 | val: '\nvar baz = fez;\n' 342 | }, 343 | { 344 | type: 'javadoc', 345 | language: '', 346 | description: '', 347 | raw: '@example\n// this is a comment\nvar alalla = zzzz;\n', 348 | val: '\n// this is a comment\nvar alalla = zzzz;\n' 349 | } 350 | ], 351 | tags: [ 352 | { 353 | type: 'tag', 354 | raw: '@param {String} foo bar', 355 | key: 'param', 356 | val: '{String} foo bar' 357 | }, 358 | { 359 | type: 'tag', 360 | raw: '@returns {Object} Instance of Foo', 361 | key: 'returns', 362 | val: '{Object} Instance of Foo' 363 | }, 364 | { 365 | type: 'tag', 366 | raw: '@api public', 367 | key: 'api', 368 | val: 'public' 369 | } 370 | ] 371 | } 372 | ``` 373 | 374 | ## About 375 | 376 | ### Related projects 377 | 378 | * [parse-comments](https://www.npmjs.com/package/parse-comments): Parse code comments from JavaScript or any language that uses the same format. | [homepage](https://github.com/jonschlinkert/parse-comments "Parse code comments from JavaScript or any language that uses the same format.") 379 | * [snapdragon](https://www.npmjs.com/package/snapdragon): Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map… [more](https://github.com/jonschlinkert/snapdragon) | [homepage](https://github.com/jonschlinkert/snapdragon "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.") 380 | * [strip-comments](https://www.npmjs.com/package/strip-comments): Strip comments from code. Removes line comments, block comments, the first comment only, or all… [more](https://github.com/jonschlinkert/strip-comments) | [homepage](https://github.com/jonschlinkert/strip-comments "Strip comments from code. Removes line comments, block comments, the first comment only, or all comments. Optionally leave protected comments unharmed.") 381 | 382 | ### Contributing 383 | 384 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). 385 | 386 | Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards. 387 | 388 | ### Building docs 389 | 390 | _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ 391 | 392 | To generate the readme, run the following command: 393 | 394 | ```sh 395 | $ npm install -g verbose/verb#dev verb-generate-readme && verb 396 | ``` 397 | 398 | ### Running tests 399 | 400 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: 401 | 402 | ```sh 403 | $ npm install && npm test 404 | ``` 405 | 406 | ### Author 407 | 408 | **Jon Schlinkert** 409 | 410 | * [github/jonschlinkert](https://github.com/jonschlinkert) 411 | * [twitter/jonschlinkert](https://twitter.com/jonschlinkert) 412 | 413 | ### License 414 | 415 | Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert). 416 | Released under the [MIT License](LICENSE). 417 | 418 | *** 419 | 420 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.4.3, on March 16, 2017._ --------------------------------------------------------------------------------