├── .babelrc ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json └── src ├── Docblock.js ├── __tests__ └── docblockParser-test.js ├── consumeTil.js ├── consumers.js ├── defaultConfig.js ├── index.js └── predicates.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /node_modules 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "eqnull": true, 4 | "esnext": true 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 4 5 | - stable 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Felix Kling 2 | 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docblock-parser [![Build Status](https://travis-ci.org/fkling/docblock-parser.svg?branch=master)](https://travis-ci.org/fkling/docblock-parser) 2 | 3 | A line based doc block parser. 4 | 5 | ## Motivation 6 | 7 | I wasn't able to find a standalone, not opinionated docblock parser. Most 8 | parsers seem to make fixed assumptions about the value of a tag. 9 | This is a slightly less opinionated parser. It allows you to specify which parts 10 | of a tag make up its value, on a line basis. 11 | 12 | ## Quick example 13 | 14 | ```js 15 | > var docblockParser = require('docblock-parser'); 16 | > docblockParser.parse('/** @type {Object} */'); 17 | { text: '', 18 | tags: { type: '{Object} Description' } } 19 | ``` 20 | 21 | ## Terminology 22 | 23 | The parser is built around the idea of *consuming lines*. If the parser 24 | encounters a line that starts with a tag pattern (`@tagname`), it delegates the 25 | consumption of the following lines to a tag specific **consumer** or the default 26 | consumer. If the line doesn't start with a tag, it delegates to the text 27 | consumer. 28 | 29 | A **consumer** is a function that accepts a `Docblock` object and returns a 30 | value which is associated with the tag or free text. 31 | 32 | A **`Docblock`** object is just a stack of lines, with which a consumer can 33 | `.peek()` at the current line or `.pop()` it from the stack to consume it. 34 | A consumer `.pop()`s lines until a condition is met (or there no lines left). 35 | 36 | ## Install 37 | 38 | ```sh 39 | npm install docblock-parser 40 | ``` 41 | ## API 42 | 43 | #### `docblockParser.parse(docstring)` 44 | 45 | This parses `docstring` with the default configuration (`defaultConfig.js`) 46 | which provides sensible defaults for [jsdoc 47 | tags](https://code.google.com/p/jsdoc-toolkit/wiki/TagReference). 48 | 49 | #### `docblockParser(config).parse(docstring)` 50 | 51 | Allows you to specific your own consumers and tags. 52 | 53 | ##### `config.docBlockPattern` 54 | 55 | Type: `RegExp` 56 | 57 | The pattern to validate a docblock. Defaults to `/^\/\*\*|^\s*\* ?/m`. 58 | 59 | ##### `config.startPattern` 60 | 61 | Type: `RegExp` 62 | 63 | The start of docblock. The match will be removed before parsing. Defaults to `/^\s*\/\*\*\s?/`. 64 | 65 | ##### `config.endPattern` 66 | 67 | Type: `RegExp` 68 | 69 | The end of docblock. The match will be removed before parsing. Defaults to `/\*\/\s*$/`. 70 | 71 | ##### `config.linePattern` 72 | 73 | Type: `RegExp` 74 | 75 | Start of a line in a docblock. The match will be removed while parsing aline. Defaults to `/^\s*\* ?/`. 76 | 77 | ##### `config.text` 78 | 79 | Type: `function` 80 | 81 | A consumer for all free text inside the doc block. I.e. text not associated with 82 | a specific tag. 83 | 84 | ##### `config.default` 85 | 86 | Type: `function` 87 | 88 | The fallback consumer used for tags not listed in `config.tags`. 89 | 90 | ##### `config.tags` 91 | 92 | Type: `object` 93 | 94 | A `tag name -> consumer` mapping that allows to use different strategies for 95 | different tags. 96 | 97 | ##### Returns `{text: (Array|string), tags {tagname: (Array|?), ...}}` 98 | 99 | ###### `text` 100 | 101 | Is an array if the doc block contains multiple sections of free text, else a 102 | single string. 103 | 104 | ###### `tags` 105 | 106 | Is an object of `tagname -> value` mappings. The type of the value depends on 107 | the consumer being used for the tag, but it will definitely be an array if the 108 | tag appeared multiple times in the doc block (e.g. `@param`). 109 | 110 | ## Examples 111 | 112 | ### Custom Tags 113 | ```js 114 | var docblockParser = require('./'); 115 | var docstring = [ 116 | '/**', 117 | ' * Some free text', 118 | ' *', 119 | ' * @public', 120 | ' * @extends', 121 | ' * SuperLongClassName', 122 | ' * @multiline-example', 123 | ' * E.g. for example code', 124 | ' *', 125 | ' * var foo = bar;', 126 | ' *', 127 | ' * With description.', 128 | ' *', 129 | ' * @param {string} foo', 130 | ' * @param {number} bar', 131 | ' * @returns {boolean} Some desciption', 132 | ' */', 133 | ].join('\n'); 134 | 135 | // config.text and config.default is provided through the default config 136 | var result = docblockParser({ 137 | tags: { 138 | public: docblockParser.booleanTag, 139 | extends: docblockParser.singleParameterTag, 140 | 'multiline-example': docblockParser.multilineTilTag, 141 | param: docblockParser.multilineTilTag, 142 | returns: docblockParser.multilineTilTag, 143 | } 144 | }).parse(docstring); 145 | ``` 146 | 147 | returns 148 | 149 | ```js 150 | { 151 | text: 'Some free text', 152 | tags: { 153 | public: true, 154 | extends: 'SuperLongClassName', 155 | 'multiline-example': 'E.g. for example code\n\n var foo = bar;\n\nWith description.', 156 | param: [ '{string} foo', '{number} bar' ], 157 | returns: '{boolean} Some desciption' 158 | } 159 | } 160 | ``` 161 | Note that there is no line break after "Some free text" and "With description". 162 | If you are using the built-in default consumer (`consumeTil`)(which all of the 163 | built-in consumer do), leading and trailing lines will be removed form the 164 | value. 165 | 166 | In this example we specified our own tag consumers. We could have also used the 167 | default ones and just called `docblockParser.parse(docstring)`. 168 | 169 | ### Custom format 170 | ```js 171 | var docblockParser = require('./'); 172 | var docstring = '{##\nSome free text\n@memberOf test\n##}'; 173 | var result = docblockParser({ 174 | docBlockPattern: /\{##([^#]*)##\}/ig, 175 | startPattern: /^\s*\{##\s?/, 176 | endPattern: /##\}\s*$/ 177 | }).parse(docstring); 178 | ``` 179 | 180 | returns 181 | 182 | ```js 183 | { 184 | text: 'Some free text', 185 | tags: { 186 | memberOf: 'test' 187 | } 188 | } 189 | ``` 190 | 191 | ### Built-in consumers 192 | 193 | The parser comes with consumers for the most common use cases: 194 | 195 | - `multilineTilTag` 196 | - Consumes as many lines until it encounters a line starting with the 197 | @tagname` pattern. 198 | - Returns: The collected lines. This is usually the safest to use, since it 199 | may not always be possible to format the values of tags in a specific way. 200 | 201 | - `multilineTilEmptyLineOrTag` 202 | - Consumes as many lines until it encounters an empty line or a tag. 203 | - Returns: The collected lines. 204 | 205 | - `booleanTag` 206 | - Consumes just the current line. 207 | - Returns: True. The mere fact that the function is called means that the tag 208 | exists. 209 | 210 | - `singleParameterTag` 211 | - Consumes lines until it finds a non-empty line. 212 | - Returns: The collected line. That is the value of the tag. 213 | 214 | - `multiParameterTag(delimiter)` 215 | - Based on `multilineTilEmptyLineOrTag` 216 | - Returns: Returns an array of values. The values come from 217 | `multilineTilEmptyLineOrTag` split by `delimiter`. 218 | 219 | ## License 220 | 221 | ISC 222 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docblock-parser", 3 | "version": "1.0.0", 4 | "description": "A standalone docblock parser", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/fkling/docblock-parser.git" 8 | }, 9 | "homepage": "https://github.com/fkling/docblock-parser", 10 | "bugs": "https://github.com/fkling/docblock-parser/issues", 11 | "main": "lib/index.js", 12 | "scripts": { 13 | "test": "jest", 14 | "prepublish": "babel src --out-dir lib" 15 | }, 16 | "keywords": [ 17 | "docs", 18 | "documention", 19 | "docblock", 20 | "parser", 21 | "type", 22 | "annotation", 23 | "jsdoc" 24 | ], 25 | "author": "Felix Kling", 26 | "license": "ISC", 27 | "devDependencies": { 28 | "babel-cli": "^6.3.17", 29 | "babel-jest": "^6.0.1", 30 | "babel-preset-es2015": "^6.3.13", 31 | "jest-cli": "^0.8.1" 32 | }, 33 | "dependencies": { 34 | "lodash.assign": "^3.2.0" 35 | }, 36 | "jest": { 37 | "scriptPreprocessor": "/node_modules/babel-jest", 38 | "testPathDirs": [ 39 | "src/" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Docblock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * This class is used to process a docblock line by line. 5 | */ 6 | class Docblock { 7 | constructor(lines, startPattern, endPattern, linePattern) { 8 | this._startPattern = startPattern || /^\s*\/\*\*\s?/; 9 | this._endPattern = endPattern || /\*\/\s*$/; 10 | this._linePattern = linePattern || /^\s*\* ?/; 11 | this._current = 0; 12 | this._lines = lines; 13 | this._lines[0] = lines[0].replace(this._startPattern, ''); 14 | this._lines[lines.length - 1] = 15 | lines[lines.length - 1].replace(this._endPattern, ''); 16 | } 17 | 18 | /** 19 | * Returns the current line. 20 | * 21 | * @return {string} 22 | */ 23 | peek() { 24 | if (this._current < this._lines.length) { 25 | return this._lines[this._current].replace(this._linePattern, ''); 26 | } 27 | return null; 28 | } 29 | 30 | /** 31 | * Returns the current line and advances to the next line. 32 | * 33 | * @return {string} 34 | */ 35 | pop() { 36 | var line = this.peek(); 37 | if (line != null) { 38 | this._current += 1; 39 | return line; 40 | } 41 | return null; 42 | } 43 | 44 | /** 45 | * Allows to replace the current line with this value. This shouldn't be 46 | * called by consumers. 47 | */ 48 | replace(line) { 49 | this._lines[this._current] = line; 50 | } 51 | 52 | /** 53 | * Returns true if the all of docblock was processed. 54 | * 55 | * @return {boolean} 56 | */ 57 | isExhausted() { 58 | return this._current >= this._lines.length; 59 | } 60 | } 61 | 62 | module.exports = Docblock; 63 | -------------------------------------------------------------------------------- /src/__tests__/docblockParser-test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | 3 | function docstring(lines) { 4 | lines = lines.map(function(line) { 5 | return ' *' + line; 6 | }); 7 | lines.unshift('/**'); 8 | lines.push(' */'); 9 | return lines.join('\n'); 10 | } 11 | 12 | describe('docblock-parser', function() { 13 | var docblockParser; 14 | 15 | beforeEach(function() { 16 | docblockParser = require('../'); 17 | }); 18 | 19 | describe('API', function() { 20 | 21 | it('exposes a parse method', function() { 22 | expect(typeof docblockParser.parse).toBe('function'); 23 | }); 24 | 25 | it('accepts a docblock', function() { 26 | expect(function() { 27 | docblockParser.parse('/** foo */'); 28 | }).not.toThrow(); 29 | }); 30 | 31 | it('allows to configure a custom docblock format', function() { 32 | expect(function() { 33 | var config = { 34 | docBlockPattern: /\{##([^#]*)##\}/ig, 35 | startPattern: /^\s*\{##\s?/, 36 | endPattern: /##\}\s*$/ 37 | }; 38 | docblockParser(config).parse('{## foo ##}'); 39 | }).not.toThrow(); 40 | }); 41 | 42 | it('throws if the argument is not a docblock', function() { 43 | expect(function() { 44 | docblockParser.parse('foo'); 45 | }).toThrow(); 46 | }); 47 | 48 | }); 49 | 50 | describe('Parsing', function() { 51 | describe('free text', function() { 52 | it('extracts text from a single line', function() { 53 | expect(docblockParser().parse('/** foo bar */').text).toEqual('foo bar'); 54 | }); 55 | 56 | it('extracts text from multiple lines', function() { 57 | var text = 'Some multiline\ntext with empty\n\nlines.'; 58 | var ds = docstring(text.split('\n')); 59 | expect(docblockParser.parse(ds).text).toEqual(text); 60 | }); 61 | 62 | it('extracts text from a single line with a custom format', function() { 63 | var config = { 64 | docBlockPattern: /\{##([^#]*)##\}/ig, 65 | startPattern: /^\s*\{##\s?/, 66 | endPattern: /##\}\s*$/ 67 | }; 68 | expect(docblockParser(config).parse('{## foo bar ##}').text).toEqual('foo bar'); 69 | }); 70 | 71 | it('extracts text from multiple lines with a custom format', function() { 72 | var config = { 73 | docBlockPattern: /\{##([^#]*)##\}/ig, 74 | startPattern: /^\s*\{##\s?/, 75 | endPattern: /##\}\s*$/ 76 | }; 77 | var text = 'Some multiline\ntext with empty\n\nlines.'; 78 | var doc = '{##\n' + text + '\n##}'; 79 | expect(docblockParser(config).parse(doc).text).toEqual(text); 80 | }); 81 | }); 82 | 83 | describe('tags', function() { 84 | it('extracts boolean tags', function() { 85 | var ds = docstring(['@foo', '@bar']); 86 | var tags = docblockParser({ 87 | tags: { 88 | foo: docblockParser.booleanTag, 89 | bar: docblockParser.booleanTag 90 | } 91 | }).parse(ds).tags; 92 | expect(tags).toEqual({foo: true, bar: true}); 93 | }); 94 | 95 | it('extracts single parameter tags', function() { 96 | var ds = docstring([ 97 | '@foo bar', 98 | 'Some text in between', 99 | '@bar', 100 | 'baz', 101 | '@tricky', 102 | '@value' 103 | ]); 104 | 105 | var result = docblockParser({ 106 | tags: { 107 | foo: docblockParser.singleParameterTag, 108 | bar: docblockParser.singleParameterTag, 109 | tricky: docblockParser.singleParameterTag 110 | } 111 | }).parse(ds); 112 | 113 | expect(result) 114 | .toEqual({ 115 | text: 'Some text in between', 116 | tags: {foo: 'bar', bar: 'baz', tricky: '@value'} 117 | }); 118 | }); 119 | 120 | it('extracts multi parameter tags', function() { 121 | var ds = docstring([ 122 | '@foo x y z', 123 | '@bar a,', 124 | 'b,c,', 125 | 'd', 126 | ]); 127 | 128 | var result = docblockParser({ 129 | tags: { 130 | foo: docblockParser.multiParameterTag(' '), 131 | bar: docblockParser.multiParameterTag(/,\s*/) 132 | } 133 | }).parse(ds); 134 | 135 | expect(result) 136 | .toEqual({ 137 | text: '', tags: {foo: ['x', 'y', 'z'], bar: ['a', 'b', 'c', 'd']} 138 | }); 139 | }); 140 | 141 | it('extracts multline tags', function() { 142 | var ds = docstring([ 143 | '@foo', 144 | 'some text', 145 | 'with', 146 | '', 147 | 'empty line', 148 | '@bar some text', 149 | 'with', 150 | '', 151 | 'empty line' 152 | ]); 153 | 154 | var result = docblockParser({ 155 | tags: { 156 | foo: docblockParser.multilineTilEmptyLineOrTag, 157 | bar: docblockParser.multilineTilTag, 158 | } 159 | }).parse(ds); 160 | 161 | expect(result) 162 | .toEqual({ 163 | text: 'empty line', 164 | tags: {foo: 'some text\nwith', bar: 'some text\nwith\n\nempty line'} 165 | }); 166 | 167 | }); 168 | 169 | it('groups some tags', function() { 170 | var ds = docstring([ 171 | '@foo bar', 172 | '@foo baz' 173 | ]); 174 | 175 | var result = docblockParser({ 176 | tags: { 177 | foo: docblockParser.singleParameterTag, 178 | bar: docblockParser.singleParameterTag, 179 | } 180 | }).parse(ds); 181 | 182 | expect(result) 183 | .toEqual({ 184 | text: '', 185 | tags: {foo: ['bar', 'baz']} 186 | }); 187 | }); 188 | }); 189 | }); 190 | 191 | }); 192 | -------------------------------------------------------------------------------- /src/consumeTil.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var identity = x => x; 3 | var trimTag = x => x.replace(/^\s*@[^\s]+ ?/, ''); 4 | 5 | /** 6 | * This function takes a predicate and a filter and returns a consumer. 7 | * 8 | * The consumer accepts a Docblock object and consumes lines as long as 9 | * `predicate` does not return `true` for a line. The consumed lines are passed 10 | * through `filter` which can be used to further process each line. 11 | * 12 | * @param {function} predicate A function that should return `true` if 13 | * consumption should stop. 14 | * @param {function} filter Process line before consumed. Can return `null` to 15 | * ignore the line. 16 | * @return {string} The concatenation of collected lines. 17 | */ 18 | function consumeTil(predicate, filter) { 19 | filter = filter || identity; 20 | return function(docblock) { 21 | /*jshint validthis:true */ 22 | var lines = []; 23 | var i = 0; 24 | while (docblock.peek() != null && !predicate(docblock.peek(), i)) { 25 | i += 1; 26 | var line = filter(docblock.pop()); 27 | if (line != null) { 28 | lines.push(line); 29 | } 30 | } 31 | // we remove empty first and last lines 32 | if (lines.length && !lines[0].trim()) { 33 | lines = lines.slice(1); 34 | } 35 | if (lines.length && !lines[lines.length - 1].trim()) { 36 | lines = lines.slice(0, -1); 37 | } 38 | 39 | return lines.join('\n'); 40 | }; 41 | } 42 | 43 | module.exports = consumeTil; 44 | -------------------------------------------------------------------------------- /src/consumers.js: -------------------------------------------------------------------------------- 1 | var consumeTil = require('./consumeTil'); 2 | var predicates = require('./predicates'); 3 | 4 | var rtrim = x => x.replace(/\s*$/, ''); 5 | 6 | var emptyLines = consumeTil(x => x.trim() !== ''); 7 | var multilineTilTag = consumeTil(predicates.isTagLine, rtrim); 8 | var multilineTilEmptyLineOrTag = consumeTil( 9 | (x, i) => i > 0 && predicates.isEmptyLine(x) || predicates.isTagLine(x), 10 | rtrim 11 | ); 12 | 13 | var booleanTag = function(docblock) { 14 | docblock.pop(); 15 | return true; 16 | }; 17 | 18 | var singleParameterTag = function(docblock) { 19 | var hasSeenNonEmptyLine = false; 20 | var value = consumeTil(function(line) { 21 | var prev = hasSeenNonEmptyLine; 22 | hasSeenNonEmptyLine = line.trim() !== ''; 23 | return prev; 24 | }, rtrim)(docblock); 25 | return value; 26 | }; 27 | 28 | var multiParameterTag = function(delimiter) { 29 | return function(docblock) { 30 | return multilineTilEmptyLineOrTag(docblock) 31 | .replace('\n', ' ') 32 | .split(delimiter); 33 | }; 34 | }; 35 | 36 | module.exports = { 37 | emptyLines, 38 | multilineTilTag, 39 | multilineTilEmptyLineOrTag, 40 | booleanTag, 41 | singleParameterTag, 42 | multiParameterTag 43 | }; 44 | -------------------------------------------------------------------------------- /src/defaultConfig.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var { 4 | multilineTilTag, 5 | multilineTilEmptyLineOrTag, 6 | booleanTag, 7 | singleParameterTag 8 | } = require('./consumers'); 9 | 10 | module.exports = { 11 | text: multilineTilTag, 12 | default: multilineTilTag, 13 | tags: { 14 | augments: singleParameterTag, 15 | author: multilineTilEmptyLineOrTag, 16 | borrows: multilineTilEmptyLineOrTag, 17 | class: multilineTilTag, 18 | constant: booleanTag, 19 | constructor: booleanTag, 20 | constructs: booleanTag, 21 | default: singleParameterTag, 22 | deprecated: multilineTilEmptyLineOrTag, 23 | desciption: multilineTilTag, 24 | event: booleanTag, 25 | example: multilineTilTag, 26 | extends: singleParameterTag, 27 | field: booleanTag, 28 | fileOverview: multilineTilTag, 29 | function: booleanTag, 30 | ignore: booleanTag, 31 | inner: booleanTag, 32 | lends: singleParameterTag, 33 | memberOf: singleParameterTag, 34 | name: booleanTag, 35 | namespace: booleanTag, 36 | param: multilineTilEmptyLineOrTag, 37 | private: booleanTag, 38 | property: multilineTilEmptyLineOrTag, 39 | public: booleanTag, 40 | requires: multilineTilEmptyLineOrTag, 41 | returns: multilineTilEmptyLineOrTag, 42 | see: singleParameterTag, 43 | since: singleParameterTag, 44 | static: booleanTag, 45 | throws: multilineTilEmptyLineOrTag, 46 | type: singleParameterTag, 47 | version: multilineTilEmptyLineOrTag 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Docblock = require('./Docblock'); 4 | 5 | var assign = require('lodash.assign'); 6 | var consumers = require('./consumers'); 7 | var defaultConfig = require('./defaultConfig'); 8 | 9 | function docblockParser(config={}) { 10 | if (!config.text) { 11 | config.text = defaultConfig.text; 12 | } 13 | if (!config.default) { 14 | config.default = defaultConfig.default; 15 | } 16 | if (!config.docBlockPattern) { 17 | config.docBlockPattern = /^\/\*\*|^\s*\* ?/m; 18 | } 19 | if (!config.tagPattern) { 20 | config.tagPattern = /^\s*@([^\s]+)\s?/; 21 | } 22 | if (!config.startPattern) { 23 | config.startPattern = /^\s*\/\*\*\s?/; 24 | } 25 | if (!config.endPattern) { 26 | config.endPattern = /\*\/\s*$/; 27 | } 28 | if (!config.linePattern) { 29 | config.linePattern = /^\s*\* ?/; 30 | } 31 | 32 | function parse(docstring) { 33 | if (!docstring || !config.docBlockPattern.test(docstring)) { 34 | throw new TypeError( 35 | "Argument does not appear to be a valid docstring. A docstring " + 36 | "usually starts with /**, ends with */ and every line in between " + 37 | "starts with a single *. This is what I got instead:\n\n" + 38 | docstring 39 | ); 40 | } 41 | 42 | var text = []; 43 | var tags = {}; 44 | var tag; 45 | 46 | var docblock = new Docblock(docstring.split('\n'), config.startPattern, config.endPattern, config.linePattern); 47 | while (!docblock.isExhausted()) { 48 | consumers.emptyLines(docblock); 49 | var line = docblock.peek(); 50 | if (!line) { continue;} 51 | var match = line.match(config.tagPattern); 52 | if (match) { 53 | docblock.replace(line.replace(config.tagPattern, '')); 54 | tag = match[1]; 55 | var consumer = config.tags[tag] || config.default; 56 | var tagValue = consumer(docblock); 57 | if (!tags.hasOwnProperty(tag)) { 58 | tags[tag] = []; 59 | } 60 | tags[tag].push(tagValue); 61 | } 62 | else { 63 | text.push(config.text(docblock)); 64 | } 65 | } 66 | 67 | // simplify entries with single values 68 | for (tag in tags) { 69 | if (tags.hasOwnProperty(tag) && tags[tag].length === 1) { 70 | tags[tag] = tags[tag][0]; 71 | } 72 | } 73 | // simplify text 74 | if (text.length === 1) { 75 | text = text[0]; 76 | } 77 | else if (text.length === 0) { 78 | text = ''; 79 | } 80 | 81 | return {text: text, tags: tags}; 82 | } 83 | 84 | return {parse: parse}; 85 | } 86 | 87 | docblockParser.parse = docblockParser(defaultConfig).parse; 88 | docblockParser.consumeTil = require('./consumeTil'); 89 | docblockParser.defaultConfig = defaultConfig; 90 | assign(docblockParser, consumers); 91 | 92 | module.exports = docblockParser; 93 | -------------------------------------------------------------------------------- /src/predicates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var TAG_PATTERN = /^\s*@[^\s]+/; 3 | 4 | module.exports = { 5 | isEmptyLine: line => /^\s*$/.test(line), 6 | isTagLine: line => TAG_PATTERN.test(line), 7 | }; 8 | --------------------------------------------------------------------------------