├── .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 |
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 \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 \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 |
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 [](https://www.npmjs.com/package/tokenize-comment) [](https://npmjs.org/package/tokenize-comment) [](https://npmjs.org/package/tokenize-comment) [](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._
--------------------------------------------------------------------------------