|
90 | 90 | title === 'public' || title === 'private' || title === 'protected'; |
91 | 91 | } |
92 | 92 |
|
| 93 | + // A regex character class that contains all whitespace except linebreak characters (\r, \n, \u2028, \u2029) |
| 94 | + var WHITESPACE = '[ \\f\\t\\v\\u00a0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\ufeff]'; |
| 95 | + |
| 96 | + var STAR_MATCHER = '(' + WHITESPACE + '*(?:\\*' + WHITESPACE + '?)?)(.+|[\r\n\u2028\u2029])'; |
| 97 | + |
93 | 98 | function unwrapComment(doc) { |
94 | 99 | // JSDoc comment is following form |
95 | 100 | // /** |
96 | 101 | // * ....... |
97 | 102 | // */ |
98 | | - // remove /**, */ and * |
99 | | - var BEFORE_STAR = 0, |
100 | | - STAR = 1, |
101 | | - AFTER_STAR = 2, |
102 | | - index, |
103 | | - len, |
104 | | - mode, |
105 | | - result, |
106 | | - ch; |
107 | | - |
108 | | - doc = doc.replace(/^\/\*\*?/, '').replace(/\*\/$/, ''); |
109 | | - index = 0; |
110 | | - len = doc.length; |
111 | | - mode = BEFORE_STAR; |
112 | | - result = ''; |
113 | | - |
114 | | - while (index < len) { |
115 | | - ch = doc.charCodeAt(index); |
116 | | - switch (mode) { |
117 | | - case BEFORE_STAR: |
118 | | - if (esutils.code.isLineTerminator(ch)) { |
119 | | - result += String.fromCharCode(ch); |
120 | | - } else if (ch === 0x2A /* '*' */) { |
121 | | - mode = STAR; |
122 | | - } else if (!esutils.code.isWhiteSpace(ch)) { |
123 | | - result += String.fromCharCode(ch); |
124 | | - mode = AFTER_STAR; |
125 | | - } |
126 | | - break; |
127 | 103 |
|
128 | | - case STAR: |
129 | | - if (!esutils.code.isWhiteSpace(ch)) { |
130 | | - result += String.fromCharCode(ch); |
131 | | - } |
132 | | - mode = esutils.code.isLineTerminator(ch) ? BEFORE_STAR : AFTER_STAR; |
133 | | - break; |
| 104 | + return doc. |
| 105 | + // remove /** |
| 106 | + replace(/^\/\*\*?/, ''). |
| 107 | + // remove */ |
| 108 | + replace(/\*\/$/, ''). |
| 109 | + // remove ' * ' at the beginning of a line |
| 110 | + replace(new RegExp(STAR_MATCHER, 'g'), '$2'). |
| 111 | + // remove trailing whitespace |
| 112 | + replace(/\s*$/, ''); |
| 113 | + } |
134 | 114 |
|
135 | | - case AFTER_STAR: |
136 | | - result += String.fromCharCode(ch); |
137 | | - if (esutils.code.isLineTerminator(ch)) { |
138 | | - mode = BEFORE_STAR; |
139 | | - } |
140 | | - break; |
| 115 | + /** |
| 116 | + * Converts an index in an "unwrapped" JSDoc comment to the corresponding index in the original "wrapped" version |
| 117 | + * @param {string} originalSource The original wrapped comment |
| 118 | + * @param {number} unwrappedIndex The index of a character in the unwrapped string |
| 119 | + * @returns {number} The index of the corresponding character in the original wrapped string |
| 120 | + */ |
| 121 | + function convertUnwrappedCommentIndex(originalSource, unwrappedIndex) { |
| 122 | + var replacedSource = originalSource.replace(/^\/\*\*?/, ''); |
| 123 | + var numSkippedChars = 0; |
| 124 | + var matcher = new RegExp(STAR_MATCHER, 'g'); |
| 125 | + var match; |
| 126 | + |
| 127 | + while ((match = matcher.exec(replacedSource))) { |
| 128 | + numSkippedChars += match[1].length; |
| 129 | + |
| 130 | + if (match.index + match[0].length > unwrappedIndex + numSkippedChars) { |
| 131 | + return unwrappedIndex + numSkippedChars + originalSource.length - replacedSource.length; |
141 | 132 | } |
142 | | - index += 1; |
143 | 133 | } |
144 | 134 |
|
145 | | - return result.replace(/\s+$/, ''); |
| 135 | + return originalSource.replace(/\*\/$/, '').replace(/\s*$/, '').length; |
146 | 136 | } |
147 | 137 |
|
148 | 138 | // JSDoc Tag Parser |
|
153 | 143 | lineNumber, |
154 | 144 | length, |
155 | 145 | source, |
| 146 | + originalSource, |
156 | 147 | recoverable, |
157 | 148 | sloppy, |
158 | 149 | strict; |
|
203 | 194 | // { { ok: string } } |
204 | 195 | // |
205 | 196 | // therefore, scanning type expression with balancing braces. |
206 | | - function parseType(title, last) { |
207 | | - var ch, brace, type, direct = false; |
| 197 | + function parseType(title, last, addRange) { |
| 198 | + var ch, brace, type, startIndex, direct = false; |
208 | 199 |
|
209 | 200 |
|
210 | 201 | // search '{' |
|
244 | 235 | } else if (ch === 0x7B /* '{' */) { |
245 | 236 | brace += 1; |
246 | 237 | } |
| 238 | + if (type === '') { |
| 239 | + startIndex = index; |
| 240 | + } |
247 | 241 | type += advance(); |
248 | 242 | } |
249 | 243 | } |
|
254 | 248 | } |
255 | 249 |
|
256 | 250 | if (isAllowedOptional(title)) { |
257 | | - return typed.parseParamType(type); |
| 251 | + return typed.parseParamType(type, {startIndex: convertIndex(startIndex), range: addRange}); |
258 | 252 | } |
259 | 253 |
|
260 | | - return typed.parseType(type); |
| 254 | + return typed.parseType(type, {startIndex: convertIndex(startIndex), range: addRange}); |
261 | 255 | } |
262 | 256 |
|
263 | 257 | function scanIdentifier(last) { |
|
402 | 396 | return true; |
403 | 397 | } |
404 | 398 |
|
| 399 | + function convertIndex(rangeIndex) { |
| 400 | + if (source === originalSource) { |
| 401 | + return rangeIndex; |
| 402 | + } |
| 403 | + return convertUnwrappedCommentIndex(originalSource, rangeIndex); |
| 404 | + } |
| 405 | + |
405 | 406 | function TagParser(options, title) { |
406 | 407 | this._options = options; |
407 | 408 | this._title = title.toLowerCase(); |
|
412 | 413 | if (this._options.lineNumbers) { |
413 | 414 | this._tag.lineNumber = lineNumber; |
414 | 415 | } |
| 416 | + this._first = index - title.length - 1; |
415 | 417 | this._last = 0; |
416 | 418 | // space to save special information for title parsers. |
417 | 419 | this._extra = { }; |
|
442 | 444 | // type required titles |
443 | 445 | if (isTypeParameterRequired(this._title)) { |
444 | 446 | try { |
445 | | - this._tag.type = parseType(this._title, this._last); |
| 447 | + this._tag.type = parseType(this._title, this._last, this._options.range); |
446 | 448 | if (!this._tag.type) { |
447 | 449 | if (!isParamTitle(this._title) && !isReturnTitle(this._title)) { |
448 | 450 | if (!this.addError('Missing or invalid tag type')) { |
|
459 | 461 | } else if (isAllowedType(this._title)) { |
460 | 462 | // optional types |
461 | 463 | try { |
462 | | - this._tag.type = parseType(this._title, this._last); |
| 464 | + this._tag.type = parseType(this._title, this._last, this._options.range); |
463 | 465 | } catch (e) { |
464 | 466 | //For optional types, lets drop the thrown error when we hit the end of the file |
465 | 467 | } |
|
751 | 753 | // Seek to content last index. |
752 | 754 | this._last = seekContent(this._title); |
753 | 755 |
|
| 756 | + if (this._options.range) { |
| 757 | + this._tag.range = [this._first, source.slice(0, this._last).replace(/\s*$/, '').length].map(convertIndex); |
| 758 | + } |
| 759 | + |
754 | 760 | if (hasOwnProperty(Rules, this._title)) { |
755 | 761 | sequences = Rules[this._title]; |
756 | 762 | } else { |
|
831 | 837 | source = comment; |
832 | 838 | } |
833 | 839 |
|
| 840 | + originalSource = comment; |
| 841 | + |
834 | 842 | // array of relevant tags |
835 | 843 | if (options.tags) { |
836 | 844 | if (Array.isArray(options.tags)) { |
|
0 commit comments