|
1 | 1 | var assert = require('assert');
|
2 | 2 | var commentParser = require('comment-parser');
|
| 3 | + |
3 | 4 | var TypeParser = require('jsdoctypeparser').Parser;
|
4 | 5 | var TypeBuilder = require('jsdoctypeparser').Builder;
|
5 | 6 |
|
6 | 7 | // wtf but it needed to stop writing warnings to stdout
|
7 | 8 | // and revert exceptions functionality
|
8 | 9 | TypeBuilder.ENABLE_EXCEPTIONS = true;
|
9 | 10 |
|
| 11 | +// jscs:disable requireCamelCaseOrUpperCaseIdentifiers |
| 12 | +var PARSERS = { |
| 13 | + tag: commentParser.PARSERS.parse_tag, |
| 14 | + type: commentParser.PARSERS.parse_type, |
| 15 | + description: commentParser.PARSERS.parse_description, |
| 16 | +}; |
| 17 | +// jscs:enable |
| 18 | +var RE_SPACE = /\s/; |
| 19 | + |
10 | 20 | module.exports = {
|
11 | 21 |
|
12 | 22 | /**
|
@@ -94,6 +104,8 @@ function DocTag(tag, loc) {
|
94 | 104 | this.id = tag.tag;
|
95 | 105 | this.line = tag.line;
|
96 | 106 | this.value = tag.value;
|
| 107 | + this.name = undefined; |
| 108 | + this.type = undefined; |
97 | 109 |
|
98 | 110 | this.description = tag.description;
|
99 | 111 | this.optional = tag.optional;
|
@@ -193,107 +205,114 @@ DocLocation.prototype.shift = function(line, column) {
|
193 | 205 | * @private
|
194 | 206 | */
|
195 | 207 | function _parseComment(comment) {
|
196 |
| - var notCleanTagRe = /[^\w\d_]/i; |
197 |
| - var parsed = commentParser(comment, { |
198 |
| - 'line_numbers': true, |
199 |
| - 'raw_value': true |
| 208 | + return commentParser(comment, { |
| 209 | + parsers: [ |
| 210 | + // parse tag |
| 211 | + function(str) { |
| 212 | + var res = PARSERS.tag.call(this, str); |
| 213 | + res.data.value = str; |
| 214 | + return res; |
| 215 | + }, |
| 216 | + PARSERS.type, |
| 217 | + _parseName, |
| 218 | + PARSERS.description, |
| 219 | + ] |
200 | 220 | })[0];
|
201 |
| - |
202 |
| - if (parsed && Array.isArray(parsed.tags)) { |
203 |
| - // additional tag.name parsing logic |
204 |
| - parsed.tags = parsed.tags.map(function(tag) { |
205 |
| - if (!tag.name || !notCleanTagRe.test(tag.name)) { |
206 |
| - return tag; |
207 |
| - } |
208 |
| - |
209 |
| - var node = _parseNameAgain(tag.name); |
210 |
| - if (node.error) { |
211 |
| - tag.error = node.error; |
212 |
| - return tag; |
213 |
| - } |
214 |
| - if (node.ticked) { |
215 |
| - tag.ticked = node.ticked; |
216 |
| - } |
217 |
| - if (node.default) { |
218 |
| - tag.default = node.default; |
219 |
| - } |
220 |
| - if (node.required) { |
221 |
| - tag.required = node.required; |
222 |
| - } |
223 |
| - |
224 |
| - tag.name = node.name; |
225 |
| - |
226 |
| - return tag; |
227 |
| - }); |
228 |
| - } |
229 |
| - |
230 |
| - return parsed; |
231 | 221 | }
|
232 | 222 |
|
233 | 223 | /**
|
234 |
| - * Additional name parsing logic for our purposes |
235 |
| - * @param {string} str - unparsed name thing |
236 |
| - * @returns {Object} |
| 224 | + * analogue of str.match(/@(\S+)(?:\s+\{([^\}]+)\})?(?:\s+(\S+))?(?:\s+([^$]+))?/); |
| 225 | + * @param {string} str raw jsdoc string |
| 226 | + * @returns {?Object} parsed tag node |
237 | 227 | */
|
238 |
| -function _parseNameAgain(str) { // (?:\s+(\S+))? |
239 |
| - var out = {}; |
240 |
| - |
241 |
| - // strip ticks (support for ticked variables) |
242 |
| - if (str[0] === '`' && str[str.length - 1] === '`') { |
243 |
| - out.ticked = true; |
244 |
| - str = str.substr(1, str.length - 2); |
245 |
| - } |
246 |
| - |
247 |
| - // strip chevrons |
248 |
| - if (str[0] === '<' && str[str.length - 1] === '>') { |
249 |
| - out.required = true; |
250 |
| - str = str.substr(1, str.length - 2); |
251 |
| - if (str.replace(/[\s\w\d]+/ig, '') === '') { |
252 |
| - out.name = str.replace(/^\s+|\s+$/g, ''); |
253 |
| - return out; |
254 |
| - } else { |
255 |
| - out.error = 'Unknown name format'; |
256 |
| - return out; |
257 |
| - } |
| 228 | +function _parseName(str, data) { |
| 229 | + if (data.errors && data.errors.length) { |
| 230 | + return null; |
258 | 231 | }
|
259 | 232 |
|
260 |
| - // parsing [name="mix"] |
261 |
| - var ch; |
262 |
| - var res = ''; |
| 233 | + var l = str.length; |
| 234 | + var pos = _skipws(str); |
| 235 | + var ch = ''; |
| 236 | + var name = ''; |
263 | 237 | var brackets = 0;
|
264 |
| - var re = /\s/; |
265 |
| - var l = str.length; |
266 |
| - var pos = 0; |
| 238 | + var chevrons = 0; |
| 239 | + var ticks = 0; |
| 240 | + |
267 | 241 | while (pos < l) {
|
268 | 242 | ch = str[pos];
|
269 | 243 | brackets += ch === '[' ? 1 : ch === ']' ? -1 : 0;
|
270 |
| - res += ch; |
271 |
| - pos ++; |
272 |
| - if (brackets === 0 && re.test(str[pos])) { |
| 244 | + chevrons += ch === '<' ? 1 : ch === '>' ? -1 : 0; |
| 245 | + if (ch === '`') { |
| 246 | + ticks++; |
| 247 | + } |
| 248 | + if (ticks % 2 === 0 && chevrons === 0 && brackets === 0 && RE_SPACE.test(ch)) { |
273 | 249 | break;
|
274 | 250 | }
|
| 251 | + name += ch; |
| 252 | + pos ++; |
| 253 | + } |
| 254 | + |
| 255 | + if (brackets !== 0) { throw Error('Invalid `name`, unpaired brackets'); } |
| 256 | + if (chevrons !== 0) { throw Error('Invalid `name`, unpaired chevrons'); } |
| 257 | + if (ticks % 2 !== 0) { throw Error('Invalid `name`, unpaired ticks'); } |
| 258 | + |
| 259 | + var res = { |
| 260 | + name: name, |
| 261 | + default: undefined, |
| 262 | + optional: false, |
| 263 | + required: false, |
| 264 | + ticked: false |
| 265 | + }; |
| 266 | + |
| 267 | + // strip ticks (support for ticked variables) |
| 268 | + if (name[0] === '`' && name[name.length - 1] === '`') { |
| 269 | + res.ticked = true; |
| 270 | + name = name.slice(1, -1).trim(); |
275 | 271 | }
|
276 |
| - if (brackets) { |
277 |
| - // throw new Error('Unpaired curly in type doc'); |
278 |
| - out.error = 'Unpaired brackets in type doc'; |
279 |
| - return out; |
| 272 | + |
| 273 | + // strip chevrons |
| 274 | + if (name[0] === '<' && name[name.length - 1] === '>') { |
| 275 | + res.required = true; |
| 276 | + name = name.slice(1, -1).trim(); |
280 | 277 | }
|
281 | 278 |
|
282 |
| - if (res[0] === '[' && res[res.length - 1] === ']') { |
283 |
| - out.optional = true; |
284 |
| - res = res.substr(1, res.length - 2); |
285 |
| - var eqPos = res.indexOf('='); |
| 279 | + // strip brackets |
| 280 | + if (name[0] === '[' && name[name.length - 1] === ']') { |
| 281 | + res.optional = true; |
| 282 | + name = name.slice(1, -1).trim(); |
| 283 | + |
| 284 | + var eqPos = name.indexOf('='); |
286 | 285 | if (eqPos !== -1) {
|
287 |
| - out.name = res.substr(0, eqPos); |
288 |
| - out.default = res.substr(eqPos + 1).replace(/^(["'])(.+)(\1)$/, '$2'); |
289 |
| - } else { |
290 |
| - out.name = res; |
| 286 | + res.default = name.substr(eqPos + 1).trim().replace(/^(["'])(.+)(\1)$/, '$2'); |
| 287 | + name = name.substr(0, eqPos).trim(); |
291 | 288 | }
|
292 |
| - } else { |
293 |
| - out.name = res; |
294 | 289 | }
|
295 | 290 |
|
296 |
| - return out; |
| 291 | + res.name = name; |
| 292 | + |
| 293 | + return { |
| 294 | + source : str.substr(0, pos), |
| 295 | + data : res |
| 296 | + }; |
| 297 | + |
| 298 | + /** |
| 299 | + * Returns the next to whitespace char position in a string |
| 300 | + * |
| 301 | + * @param {string} str |
| 302 | + * @return {number} |
| 303 | + */ |
| 304 | + function _skipws(str) { |
| 305 | + var i = 0; |
| 306 | + var l = str.length; |
| 307 | + var ch; |
| 308 | + do { |
| 309 | + ch = str[i]; |
| 310 | + if (ch !== ' ' && ch !== '\t') { |
| 311 | + break; |
| 312 | + } |
| 313 | + } while (++i < l); |
| 314 | + return i; |
| 315 | + } |
297 | 316 | }
|
298 | 317 |
|
299 | 318 | /**
|
|
0 commit comments