comment-parser 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/parser.js CHANGED
@@ -1,131 +1,43 @@
1
1
 
2
- var RE_COMMENT_START = /^\s*\/\*\*\s*$/m;
3
- var RE_COMMENT_LINE = /^\s*\*(?:\s(\s*)|$)/m;
4
- var RE_COMMENT_END = /^\s*\*\/\s*$/m;
5
- var RE_COMMENT_1LINE = /^\s*\/\*\*\s*(.*)\s*\*\/\s*$/;
2
+ var PARSERS = require('./parsers')
3
+
4
+ var MARKER_START = '/**'
5
+ var MARKER_START_SKIP = '/***'
6
+ var MARKER_END = '*/'
6
7
 
7
8
  /* ------- util functions ------- */
8
9
 
9
- function merge(/* ...objects */) {
10
- var k, obj, res = {}, objs = Array.prototype.slice.call(arguments);
11
- while (objs.length) {
12
- obj = objs.shift();
13
- for (k in obj) { if (obj.hasOwnProperty(k)) {
14
- res[k] = obj[k];
15
- }}
16
- }
17
- return res;
18
- }
10
+ function merge (/* ...objects */) {
11
+ var k, obj
12
+ var res = {}
13
+ var objs = Array.prototype.slice.call(arguments)
19
14
 
20
- function find(list, filter) {
21
- var k, i = list.length, matchs = true;
22
- while (i--) {
23
- for (k in filter) { if (filter.hasOwnProperty(k)) {
24
- matchs = (filter[k] === list[i][k]) && matchs;
25
- }}
26
- if (matchs) { return list[i]; }
15
+ for (var i = 0, l = objs.length; i < l; i++) {
16
+ obj = objs[i]
17
+ for (k in obj) {
18
+ if (obj.hasOwnProperty(k)) {
19
+ res[k] = obj[k]
20
+ }
21
+ }
27
22
  }
28
- return null;
23
+ return res
29
24
  }
30
25
 
31
- function skipws(str) {
32
- var i = 0;
33
- do {
34
- if (str[i] !== ' ') { return i; }
35
- } while (++i < str.length);
36
- return i;
37
- }
38
-
39
- /* ------- default parsers ------- */
40
-
41
- var PARSERS = {};
42
-
43
- PARSERS.parse_tag = function parse_tag(str) {
44
- var result = str.match(/^\s*@(\S+)/);
45
-
46
- if (!result) { throw new Error('Invalid `@tag`, missing @ symbol'); }
47
-
48
- return {
49
- source : result[0],
50
- data : {tag: result[1]}
51
- };
52
- };
53
-
54
- PARSERS.parse_type = function parse_type(str, data) {
55
- if (data.errors && data.errors.length) { return null; }
56
-
57
- var pos = skipws(str);
58
- var res = '';
59
- var curlies = 0;
60
-
61
- if (str[pos] !== '{') { return null; }
62
-
63
- while (pos < str.length) {
64
- curlies += (str[pos] === '{' ? 1 : (str[pos] === '}' ? -1 : 0));
65
- res += str[pos];
66
- pos ++;
67
- if (curlies === 0) { break; }
68
- }
69
-
70
- if (curlies !== 0) { throw new Error('Invalid `{type}`, unpaired curlies'); }
71
-
72
- return {
73
- source : str.slice(0, pos),
74
- data : {type: res.slice(1, -1)}
75
- };
76
- };
77
-
78
- PARSERS.parse_name = function parse_name(str, data) {
79
- if (data.errors && data.errors.length) { return null; }
80
-
81
- var pos = skipws(str);
82
- var name = '';
83
- var brackets = 0;
84
-
85
- while (pos < str.length) {
86
- brackets += (str[pos] === '[' ? 1 : (str[pos] === ']' ? -1 : 0));
87
- name += str[pos];
88
- pos ++;
89
- if (brackets === 0 && /\s/.test(str[pos])) { break; }
90
- }
91
-
92
- if (brackets !== 0) { throw new Error('Invalid `name`, unpaired brackets'); }
93
-
94
- var res = {name: name, optional: false};
95
-
96
- if (name[0] === '[' && name[name.length - 1] === ']') {
97
- res.optional = true;
98
- name = name.slice(1, -1);
26
+ function find (list, filter) {
27
+ var k
28
+ var i = list.length
29
+ var matchs = true
99
30
 
100
- if (name.indexOf('=') !== -1) {
101
- var parts = name.split('=');
102
- name = parts[0];
103
- res.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2');
31
+ while (i--) {
32
+ for (k in filter) {
33
+ if (filter.hasOwnProperty(k)) {
34
+ matchs = (filter[k] === list[i][k]) && matchs
35
+ }
104
36
  }
37
+ if (matchs) { return list[i] }
105
38
  }
106
-
107
- res.name = name;
108
-
109
- return {
110
- source : str.slice(0, pos),
111
- data : res
112
- };
113
- };
114
-
115
- PARSERS.parse_description = function parse_description(str, data) {
116
- if (data.errors && data.errors.length) { return null; }
117
-
118
- var result = str.match(/^\s+((.|\s)+)?/);
119
-
120
- if (result) {
121
- return {
122
- source : result[0],
123
- data : {description: result[1] === undefined ? '' : result[1]}
124
- };
125
- }
126
-
127
- return null;
128
- };
39
+ return null
40
+ }
129
41
 
130
42
  /* ------- parsing ------- */
131
43
 
@@ -135,216 +47,238 @@ PARSERS.parse_description = function parse_description(str, data) {
135
47
  * @param {Array[function]} parsers Array of parsers to be applied to the source
136
48
  * @returns {object} parsed tag node
137
49
  */
138
- function parse_tag(str, parsers) {
139
- if (typeof str !== 'string' || str[0] !== '@') { return null; }
50
+ function parse_tag (str, parsers) {
51
+ if (typeof str !== 'string' || str[0] !== '@') { return null }
140
52
 
141
- var data = parsers.reduce(function(state, parser) {
142
- var result;
53
+ var data = parsers.reduce(function (state, parser) {
54
+ var result
143
55
 
144
56
  try {
145
- result = parser(state.source, merge({}, state.data));
57
+ result = parser(state.source, merge({}, state.data))
146
58
  } catch (err) {
147
59
  state.data.errors = (state.data.errors || [])
148
- .concat(parser.name + ': ' + err.message);
60
+ .concat(parser.name + ': ' + err.message)
149
61
  }
150
62
 
151
63
  if (result) {
152
- state.source = state.source.slice(result.source.length);
153
- state.data = merge(state.data, result.data);
64
+ state.source = state.source.slice(result.source.length)
65
+ state.data = merge(state.data, result.data)
154
66
  }
155
67
 
156
- return state;
68
+ return state
157
69
  }, {
158
- source : str,
159
- data : {}
160
- }).data;
70
+ source: str,
71
+ data: {}
72
+ }).data
161
73
 
162
- data.optional = !!data.optional;
163
- data.type = data.type === undefined ? '' : data.type;
164
- data.name = data.name === undefined ? '' : data.name;
165
- data.description = data.description === undefined ? '' : data.description;
74
+ data.optional = !!data.optional
75
+ data.type = data.type === undefined ? '' : data.type
76
+ data.name = data.name === undefined ? '' : data.name
77
+ data.description = data.description === undefined ? '' : data.description
166
78
 
167
- return data;
79
+ return data
168
80
  }
169
81
 
170
82
  /**
171
83
  * Parses comment block (array of String lines)
172
84
  */
173
- function parse_block(source, opts) {
174
-
175
- function trim(s) {
176
- return opts.trim ? s.trim() : s;
177
- }
85
+ function parse_block (source, opts) {
86
+ var trim = opts.trim
87
+ ? function trim (s) { return s.trim() }
88
+ : function trim (s) { return s }
178
89
 
179
90
  var source_str = source
180
- .map(function(line) { return trim(line.source); })
181
- .join('\n');
91
+ .map(function (line) { return trim(line.source) })
92
+ .join('\n')
182
93
 
183
- source_str = trim(source_str);
94
+ source_str = trim(source_str)
184
95
 
185
- var start = source[0].number;
96
+ var start = source[0].number
186
97
 
187
98
  // merge source lines into tags
188
99
  // we assume tag starts with "@"
189
100
  source = source
190
- .reduce(function(tags, line) {
191
- line.source = trim(line.source);
101
+ .reduce(function (tags, line) {
102
+ line.source = trim(line.source)
192
103
 
193
- if (line.source.match(/^\s*@(\w+)/)) {
194
- tags.push({source: [line.source], line: line.number});
104
+ if (line.source.match(/^\s*@(\w+)/)) {
105
+ tags.push({source: [line.source], line: line.number})
195
106
  } else {
196
- var tag = tags[tags.length - 1];
197
- tag.source.push(line.source);
107
+ var tag = tags[tags.length - 1]
108
+ if (opts.join !== undefined && opts.join !== false && opts.join !== 0 &&
109
+ !line.startWithStar && tag.source.length > 0) {
110
+ var source
111
+ if (typeof opts.join === 'string') {
112
+ source = opts.join + line.source.replace(/^\s+/, '')
113
+ } else if (typeof opts.join === 'number') {
114
+ source = line.source
115
+ } else {
116
+ source = ' ' + line.source.replace(/^\s+/, '')
117
+ }
118
+ tag.source[tag.source.length - 1] += source
119
+ } else {
120
+ tag.source.push(line.source)
121
+ }
198
122
  }
199
123
 
200
- return tags;
124
+ return tags
201
125
  }, [{source: []}])
202
- .map(function(tag) {
203
- tag.source = trim(tag.source.join('\n'));
204
- return tag;
205
- });
126
+ .map(function (tag) {
127
+ tag.source = trim(tag.source.join('\n'))
128
+ return tag
129
+ })
206
130
 
207
131
  // Block description
208
- var description = source.shift();
132
+ var description = source.shift()
209
133
 
210
134
  // skip if no descriptions and no tags
211
- if (description.source === '' && source.length === 0) {
212
- return null;
135
+ if (description.source === '' && source.length === 0) {
136
+ return null
213
137
  }
214
138
 
215
- var tags = source.reduce(function(tags, tag) {
216
- var tag_node = parse_tag(tag.source, opts.parsers);
139
+ var tags = source.reduce(function (tags, tag) {
140
+ var tag_node = parse_tag(tag.source, opts.parsers)
217
141
 
218
- if (!tag_node) { return tags; }
142
+ if (!tag_node) { return tags }
219
143
 
220
- tag_node.line = tag.line;
221
- tag_node.source = tag.source;
144
+ tag_node.line = tag.line
145
+ tag_node.source = tag.source
222
146
 
223
147
  if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
224
- var parent_name;
225
- var parent_tag;
226
- var parent_tags = tags;
227
- var parts = tag_node.name.split('.');
148
+ var parent_name
149
+ var parent_tag
150
+ var parent_tags = tags
151
+ var parts = tag_node.name.split('.')
228
152
 
229
153
  while (parts.length > 1) {
230
- parent_name = parts.shift();
231
- parent_tag = find(parent_tags, {
232
- tag : tag_node.tag,
233
- name : parent_name
234
- });
154
+ parent_name = parts.shift()
155
+ parent_tag = find(parent_tags, {
156
+ tag: tag_node.tag,
157
+ name: parent_name
158
+ })
235
159
 
236
160
  if (!parent_tag) {
237
161
  parent_tag = {
238
- tag : tag_node.tag,
239
- line : Number(tag_node.line),
240
- name : parent_name,
241
- type : '',
242
- description : ''
243
- };
244
- parent_tags.push(parent_tag);
162
+ tag: tag_node.tag,
163
+ line: Number(tag_node.line),
164
+ name: parent_name,
165
+ type: '',
166
+ description: ''
167
+ }
168
+ parent_tags.push(parent_tag)
245
169
  }
246
170
 
247
- parent_tag.tags = parent_tag.tags || [];
248
- parent_tags = parent_tag.tags;
171
+ parent_tag.tags = parent_tag.tags || []
172
+ parent_tags = parent_tag.tags
249
173
  }
250
174
 
251
- tag_node.name = parts[0];
252
- parent_tags.push(tag_node);
253
- return tags;
175
+ tag_node.name = parts[0]
176
+ parent_tags.push(tag_node)
177
+ return tags
254
178
  }
255
179
 
256
- return tags.concat(tag_node);
257
- }, []);
258
-
180
+ return tags.concat(tag_node)
181
+ }, [])
182
+
259
183
  return {
260
- tags : tags,
261
- line : start,
262
- description : description.source,
263
- source : source_str
264
- };
184
+ tags: tags,
185
+ line: start,
186
+ description: description.source,
187
+ source: source_str
188
+ }
265
189
  }
266
190
 
267
191
  /**
268
192
  * Produces `extract` function with internal state initialized
269
193
  */
270
- function mkextract(opts) {
271
- var chunk = null;
272
- var number = 0;
194
+ function mkextract (opts) {
195
+ var chunk = null
196
+ var indent = 0
197
+ var number = 0
273
198
 
274
199
  opts = merge({}, {
275
- trim : true,
276
- dotted_names : false,
277
- parsers : [
200
+ trim: true,
201
+ dotted_names: false,
202
+ parsers: [
278
203
  PARSERS.parse_tag,
279
204
  PARSERS.parse_type,
280
205
  PARSERS.parse_name,
281
206
  PARSERS.parse_description
282
207
  ]
283
- }, opts || {});
208
+ }, opts || {})
284
209
 
285
210
  /**
286
- * Cumulatively reading lines until they make one comment block
287
- * Returns block object or null.
211
+ * Read lines until they make a block
212
+ * Return parsed block once fullfilled or null otherwise
288
213
  */
289
- return function extract(line) {
290
-
291
- // if oneliner
292
- // then parse it immediately
293
- if (line.match(RE_COMMENT_1LINE)) {
294
- return parse_block([{
295
- source: line.replace(RE_COMMENT_1LINE, '$1'),
296
- number: number}], opts);
214
+ return function extract (line) {
215
+ var result = null
216
+ var startPos = line.indexOf(MARKER_START)
217
+ var endPos = line.indexOf(MARKER_END)
218
+
219
+ // if open marker detected and it's not skip one
220
+ if (startPos !== -1 && line.indexOf(MARKER_START_SKIP) !== startPos) {
221
+ chunk = []
222
+ indent = startPos + MARKER_START.length
297
223
  }
298
224
 
299
- number += 1;
300
-
301
- // if start of comment
302
- // then init the chunk
303
- if (line.match(RE_COMMENT_START)) {
304
- chunk = [{source: line.replace(RE_COMMENT_START, ''), number: number - 1}];
305
- return null;
306
- }
225
+ // if we are on middle of comment block
226
+ if (chunk) {
227
+ var lineStart = indent
228
+ var startWithStar = false
229
+
230
+ // figure out if we slice from opening marker pos
231
+ // or line start is shifted to the left
232
+ var nonSpaceChar = line.match(/\S/)
233
+
234
+ // skip for the first line starting with /** (fresh chunk)
235
+ // it always has the right indentation
236
+ if (chunk.length > 0 && nonSpaceChar) {
237
+ if (nonSpaceChar[0] === '*') {
238
+ lineStart = nonSpaceChar.index + 2
239
+ startWithStar = true
240
+ } else if (nonSpaceChar.index < indent) {
241
+ lineStart = nonSpaceChar.index
242
+ }
243
+ }
307
244
 
308
- // if comment line and chunk started
309
- // then append
310
- if (chunk && line.match(RE_COMMENT_LINE)) {
245
+ // slice the line until end or until closing marker start
311
246
  chunk.push({
312
- number: number - 1,
313
- source: line.replace(RE_COMMENT_LINE, opts.trim ? '' : '$1')
314
- });
315
- return null;
316
- }
317
-
318
- // if comment end and chunk started
319
- // then parse the chunk and push
320
- if (chunk && line.match(RE_COMMENT_END)) {
321
- chunk.push({source: line.replace(RE_COMMENT_END, ''), number: number - 1});
322
- return parse_block(chunk, opts);
247
+ number: number,
248
+ startWithStar: startWithStar,
249
+ source: line.slice(lineStart, endPos === -1 ? line.length : endPos)
250
+ })
251
+
252
+ // finalize block if end marker detected
253
+ if (endPos !== -1) {
254
+ result = parse_block(chunk, opts)
255
+ chunk = null
256
+ indent = 0
257
+ }
323
258
  }
324
259
 
325
- // if non-comment line
326
- // then reset the chunk
327
- chunk = null;
328
- };
260
+ number += 1
261
+ return result
262
+ }
329
263
  }
330
264
 
331
265
  /* ------- Public API ------- */
332
266
 
333
- module.exports = function parse(source, opts) {
334
- var block;
335
- var blocks = [];
336
- var extract = mkextract(opts);
337
- var lines = source.split(/\n/);
267
+ module.exports = function parse (source, opts) {
268
+ var block
269
+ var blocks = []
270
+ var extract = mkextract(opts)
271
+ var lines = source.split(/\n/)
338
272
 
339
- while (lines.length) {
340
- block = extract(lines.shift());
273
+ for (var i = 0, l = lines.length; i < l; i++) {
274
+ block = extract(lines.shift())
341
275
  if (block) {
342
- blocks.push(block);
276
+ blocks.push(block)
343
277
  }
344
278
  }
345
279
 
346
- return blocks;
347
- };
280
+ return blocks
281
+ }
348
282
 
349
- module.exports.PARSERS = PARSERS;
350
- module.exports.mkextract = mkextract;
283
+ module.exports.PARSERS = PARSERS
284
+ module.exports.mkextract = mkextract
package/parsers.js ADDED
@@ -0,0 +1,100 @@
1
+ function skipws (str) {
2
+ var i = 0
3
+ do {
4
+ if (str[i] !== ' ') { return i }
5
+ } while (++i < str.length)
6
+ return i
7
+ }
8
+
9
+ /* ------- default parsers ------- */
10
+
11
+ var PARSERS = {}
12
+
13
+ PARSERS.parse_tag = function parse_tag (str) {
14
+ var result = str.match(/^\s*@(\S+)/)
15
+
16
+ if (!result) { throw new Error('Invalid `@tag`, missing @ symbol') }
17
+
18
+ return {
19
+ source: result[0],
20
+ data: {tag: result[1]}
21
+ }
22
+ }
23
+
24
+ PARSERS.parse_type = function parse_type (str, data) {
25
+ if (data.errors && data.errors.length) { return null }
26
+
27
+ var pos = skipws(str)
28
+ var res = ''
29
+ var curlies = 0
30
+
31
+ if (str[pos] !== '{') { return null }
32
+
33
+ while (pos < str.length) {
34
+ curlies += (str[pos] === '{' ? 1 : (str[pos] === '}' ? -1 : 0))
35
+ res += str[pos]
36
+ pos++
37
+ if (curlies === 0) { break }
38
+ }
39
+
40
+ if (curlies !== 0) { throw new Error('Invalid `{type}`, unpaired curlies') }
41
+
42
+ return {
43
+ source: str.slice(0, pos),
44
+ data: {type: res.slice(1, -1)}
45
+ }
46
+ }
47
+
48
+ PARSERS.parse_name = function parse_name (str, data) {
49
+ if (data.errors && data.errors.length) { return null }
50
+
51
+ var pos = skipws(str)
52
+ var name = ''
53
+ var brackets = 0
54
+
55
+ while (pos < str.length) {
56
+ brackets += (str[pos] === '[' ? 1 : (str[pos] === ']' ? -1 : 0))
57
+ name += str[pos]
58
+ pos++
59
+ if (brackets === 0 && /\s/.test(str[pos])) { break }
60
+ }
61
+
62
+ if (brackets !== 0) { throw new Error('Invalid `name`, unpaired brackets') }
63
+
64
+ var res = {name: name, optional: false}
65
+
66
+ if (name[0] === '[' && name[name.length - 1] === ']') {
67
+ res.optional = true
68
+ name = name.slice(1, -1)
69
+
70
+ if (name.indexOf('=') !== -1) {
71
+ var parts = name.split('=')
72
+ name = parts[0]
73
+ res.default = parts[1].replace(/^(["'])(.+)(\1)$/, '$2')
74
+ }
75
+ }
76
+
77
+ res.name = name
78
+
79
+ return {
80
+ source: str.slice(0, pos),
81
+ data: res
82
+ }
83
+ }
84
+
85
+ PARSERS.parse_description = function parse_description (str, data) {
86
+ if (data.errors && data.errors.length) { return null }
87
+
88
+ var result = str.match(/^\s+((.|\s)+)?/)
89
+
90
+ if (result) {
91
+ return {
92
+ source: result[0],
93
+ data: {description: result[1] === undefined ? '' : result[1]}
94
+ }
95
+ }
96
+
97
+ return null
98
+ }
99
+
100
+ module.exports = PARSERS