1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | var _ = require('underscore');
|
10 | var escape = require('escape-string-regexp');
|
11 |
|
12 | var hasOwnProp = Object.prototype.hasOwnProperty;
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | var LONGNAMES = exports.LONGNAMES = {
|
22 |
|
23 | ANONYMOUS: '<anonymous>',
|
24 |
|
25 | GLOBAL: '<global>'
|
26 | };
|
27 |
|
28 |
|
29 | var MODULE_NAMESPACE = 'module:';
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | var SCOPE = exports.SCOPE = {
|
39 | NAMES: {
|
40 | GLOBAL: 'global',
|
41 | INNER: 'inner',
|
42 | INSTANCE: 'instance',
|
43 | STATIC: 'static'
|
44 | },
|
45 | PUNC: {
|
46 | INNER: '~',
|
47 | INSTANCE: '#',
|
48 | STATIC: '.'
|
49 | }
|
50 | };
|
51 |
|
52 |
|
53 | var scopeToPunc = exports.scopeToPunc = {
|
54 | 'inner': SCOPE.PUNC.INNER,
|
55 | 'instance': SCOPE.PUNC.INSTANCE,
|
56 | 'static': SCOPE.PUNC.STATIC
|
57 | };
|
58 | var puncToScope = exports.puncToScope = _.invert(scopeToPunc);
|
59 |
|
60 | var DEFAULT_SCOPE = SCOPE.NAMES.STATIC;
|
61 | var SCOPE_PUNC = _.values(SCOPE.PUNC);
|
62 | var SCOPE_PUNC_STRING = '[' + SCOPE_PUNC.join() + ']';
|
63 | var REGEXP_LEADING_SCOPE = new RegExp('^(' + SCOPE_PUNC_STRING + ')');
|
64 | var REGEXP_TRAILING_SCOPE = new RegExp('(' + SCOPE_PUNC_STRING + ')$');
|
65 |
|
66 | var DESCRIPTION = '(?:(?:[ \\t]*\\-\\s*|\\s+)(\\S[\\s\\S]*))?$';
|
67 | var REGEXP_DESCRIPTION = new RegExp(DESCRIPTION);
|
68 | var REGEXP_NAME_DESCRIPTION = new RegExp('^(\\[[^\\]]+\\]|\\S+)' + DESCRIPTION);
|
69 |
|
70 | function nameIsLongname(name, memberof) {
|
71 | var regexp = new RegExp('^' + escape(memberof) + SCOPE_PUNC_STRING);
|
72 |
|
73 | return regexp.test(name);
|
74 | }
|
75 |
|
76 | function prototypeToPunc(name) {
|
77 |
|
78 | if (name === 'prototype') {
|
79 | return name;
|
80 | }
|
81 |
|
82 | return name.replace(/(?:^|\.)prototype\.?/g, SCOPE.PUNC.INSTANCE);
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | exports.resolve = function(doclet) {
|
91 | var about = {};
|
92 | var memberof = doclet.memberof || '';
|
93 | var name = doclet.name ? String(doclet.name) : '';
|
94 |
|
95 | var parentDoc;
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | if (name && doclet.kind) {
|
101 | name = prototypeToPunc(name);
|
102 | }
|
103 | doclet.name = name;
|
104 |
|
105 |
|
106 | if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) {
|
107 | name = doclet.longname = doclet.meta.code.funcscope + SCOPE.PUNC.INNER + name;
|
108 | }
|
109 |
|
110 | if (memberof || doclet.forceMemberof) {
|
111 | memberof = prototypeToPunc(memberof);
|
112 |
|
113 |
|
114 | if (name && nameIsLongname(name, memberof) && name !== memberof) {
|
115 | about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
|
116 | }
|
117 |
|
118 |
|
119 | else if (name && name === memberof && name.indexOf(MODULE_NAMESPACE) === 0) {
|
120 | about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
|
121 | }
|
122 |
|
123 | else if (name && name === memberof) {
|
124 | doclet.scope = doclet.scope || DEFAULT_SCOPE;
|
125 | name = memberof + scopeToPunc[doclet.scope] + name;
|
126 | about = exports.shorten(name, (doclet.forceMemberof ? memberof : undefined));
|
127 | }
|
128 |
|
129 | else if (name && REGEXP_TRAILING_SCOPE.test(memberof) ) {
|
130 | about = exports.shorten(memberof + name, (doclet.forceMemberof ? memberof : undefined));
|
131 | }
|
132 | else if (name && doclet.scope) {
|
133 | about = exports.shorten(memberof + (scopeToPunc[doclet.scope] || '') + name,
|
134 | (doclet.forceMemberof ? memberof : undefined));
|
135 | }
|
136 | }
|
137 | else {
|
138 | about = exports.shorten(name);
|
139 | }
|
140 |
|
141 | if (about.name) {
|
142 | doclet.name = about.name;
|
143 | }
|
144 |
|
145 | if (about.memberof) {
|
146 | doclet.setMemberof(about.memberof);
|
147 | }
|
148 |
|
149 | if (about.longname && (!doclet.longname || doclet.longname === doclet.name)) {
|
150 | doclet.setLongname(about.longname);
|
151 | }
|
152 |
|
153 | if (doclet.scope === SCOPE.NAMES.GLOBAL) {
|
154 | doclet.setLongname(doclet.name);
|
155 | delete doclet.memberof;
|
156 | }
|
157 | else if (about.scope) {
|
158 | if (about.memberof === LONGNAMES.GLOBAL) {
|
159 | doclet.scope = SCOPE.NAMES.GLOBAL;
|
160 | }
|
161 | else {
|
162 | doclet.scope = puncToScope[about.scope];
|
163 | }
|
164 | }
|
165 | else if (doclet.name && doclet.memberof && !doclet.longname) {
|
166 | if ( REGEXP_LEADING_SCOPE.test(doclet.name) ) {
|
167 | doclet.scope = puncToScope[RegExp.$1];
|
168 | doclet.name = doclet.name.substr(1);
|
169 | }
|
170 | else {
|
171 | doclet.scope = DEFAULT_SCOPE;
|
172 | }
|
173 |
|
174 | doclet.setLongname(doclet.memberof + scopeToPunc[doclet.scope] + doclet.name);
|
175 | }
|
176 |
|
177 | if (about.variation) {
|
178 | doclet.variation = about.variation;
|
179 | }
|
180 |
|
181 |
|
182 | if (!doclet.longname) {
|
183 | doclet.longname = '';
|
184 | }
|
185 | };
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | exports.applyNamespace = function(longname, ns) {
|
194 | var nameParts = exports.shorten(longname),
|
195 | name = nameParts.name;
|
196 | longname = nameParts.longname;
|
197 |
|
198 | if ( !/^[a-zA-Z]+?:.+$/i.test(name) ) {
|
199 | longname = longname.replace( new RegExp(escape(name) + '$'), ns + ':' + name );
|
200 | }
|
201 |
|
202 | return longname;
|
203 | };
|
204 |
|
205 |
|
206 | exports.stripNamespace = function(longname) {
|
207 | return longname.replace(/^[a-zA-Z]+:/, '');
|
208 | };
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | exports.hasAncestor = function(parent, child) {
|
218 | var hasAncestor = false;
|
219 | var memberof = child;
|
220 |
|
221 | if (!parent || !child) {
|
222 | return hasAncestor;
|
223 | }
|
224 |
|
225 |
|
226 | if (child.indexOf(parent) !== 0) {
|
227 | return hasAncestor;
|
228 | }
|
229 |
|
230 | do {
|
231 | memberof = exports.shorten(memberof).memberof;
|
232 |
|
233 | if (memberof === parent) {
|
234 | hasAncestor = true;
|
235 | }
|
236 | } while (!hasAncestor && memberof);
|
237 |
|
238 | return hasAncestor;
|
239 | };
|
240 |
|
241 |
|
242 | function atomize(longname, sliceChars, forcedMemberof) {
|
243 | var i;
|
244 | var memberof = '';
|
245 | var name = '';
|
246 | var parts;
|
247 | var partsRegExp;
|
248 | var scopePunc = '';
|
249 | var token;
|
250 | var tokens = [];
|
251 | var variation;
|
252 |
|
253 |
|
254 | longname = longname.replace(/(\[?["'].+?["']\]?)/g, function($) {
|
255 | var dot = '';
|
256 | if ( /^\[/.test($) ) {
|
257 | dot = '.';
|
258 | $ = $.replace( /^\[/g, '' ).replace( /\]$/g, '' );
|
259 | }
|
260 |
|
261 | token = '@{' + tokens.length + '}@';
|
262 | tokens.push($);
|
263 |
|
264 | return dot + token;
|
265 | });
|
266 |
|
267 | longname = prototypeToPunc(longname);
|
268 |
|
269 | if (forcedMemberof !== undefined) {
|
270 | partsRegExp = new RegExp('^(.*?)([' + sliceChars.join() + ']?)$');
|
271 | name = longname.substr(forcedMemberof.length);
|
272 | parts = forcedMemberof.match(partsRegExp);
|
273 |
|
274 | if (parts[1]) {
|
275 | memberof = parts[1] || forcedMemberof;
|
276 | }
|
277 | if (parts[2]) {
|
278 | scopePunc = parts[2];
|
279 | }
|
280 | }
|
281 | else if (longname) {
|
282 | parts = (longname.match(new RegExp('^(:?(.+)([' + sliceChars.join() + ']))?(.+?)$')) || [])
|
283 | .reverse();
|
284 | name = parts[0] || '';
|
285 | scopePunc = parts[1] || '';
|
286 | memberof = parts[2] || '';
|
287 | }
|
288 |
|
289 |
|
290 | if ( /(.+)\(([^)]+)\)$/.test(name) ) {
|
291 | name = RegExp.$1;
|
292 | variation = RegExp.$2;
|
293 | }
|
294 |
|
295 |
|
296 | i = tokens.length;
|
297 | while (i--) {
|
298 | longname = longname.replace('@{' + i + '}@', tokens[i]);
|
299 | memberof = memberof.replace('@{' + i + '}@', tokens[i]);
|
300 | scopePunc = scopePunc.replace('@{' + i + '}@', tokens[i]);
|
301 | name = name.replace('@{' + i + '}@', tokens[i]);
|
302 | }
|
303 |
|
304 | return {
|
305 | longname: longname,
|
306 | memberof: memberof,
|
307 | scope: scopePunc,
|
308 | name: name,
|
309 | variation: variation
|
310 | };
|
311 | }
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | exports.shorten = function(longname, forcedMemberof) {
|
322 | return atomize(longname, SCOPE_PUNC, forcedMemberof);
|
323 | };
|
324 |
|
325 |
|
326 | exports.combine = function(parts) {
|
327 | return '' +
|
328 | (parts.memberof || '') +
|
329 | (parts.scope || '') +
|
330 | (parts.name || '') +
|
331 | (parts.variation || '');
|
332 | };
|
333 |
|
334 |
|
335 | exports.stripVariation = function(name) {
|
336 | var parts = exports.shorten(name);
|
337 |
|
338 | parts.variation = '';
|
339 |
|
340 | return exports.combine(parts);
|
341 | };
|
342 |
|
343 | function splitLongname(longname, options) {
|
344 | var chunks = [];
|
345 | var currentNameInfo;
|
346 | var nameInfo = {};
|
347 | var previousName = longname;
|
348 | var splitters = SCOPE_PUNC.concat('/');
|
349 |
|
350 | options = _.defaults(options || {}, {
|
351 | includeVariation: true
|
352 | });
|
353 |
|
354 | do {
|
355 | if (!options.includeVariation) {
|
356 | previousName = exports.stripVariation(previousName);
|
357 | }
|
358 | currentNameInfo = nameInfo[previousName] = atomize(previousName, splitters);
|
359 | previousName = currentNameInfo.memberof;
|
360 | chunks.push(currentNameInfo.scope + currentNameInfo.name);
|
361 | } while (previousName);
|
362 |
|
363 | return {
|
364 | chunks: chunks.reverse(),
|
365 | nameInfo: nameInfo
|
366 | };
|
367 | }
|
368 |
|
369 |
|
370 | exports.longnamesToTree = function longnamesToTree(longnames, doclets) {
|
371 | var splitOptions = { includeVariation: false };
|
372 | var tree = {};
|
373 |
|
374 | longnames.forEach(function(longname) {
|
375 | var currentLongname = '';
|
376 | var currentParent = tree;
|
377 | var nameInfo;
|
378 | var processed;
|
379 |
|
380 |
|
381 | if (!longname) {
|
382 | return;
|
383 | }
|
384 |
|
385 | processed = splitLongname(longname, splitOptions);
|
386 | nameInfo = processed.nameInfo;
|
387 |
|
388 | processed.chunks.forEach(function(chunk) {
|
389 | currentLongname += chunk;
|
390 |
|
391 | if (currentParent !== tree) {
|
392 | currentParent.children = currentParent.children || {};
|
393 | currentParent = currentParent.children;
|
394 | }
|
395 |
|
396 | if (!hasOwnProp.call(currentParent, chunk)) {
|
397 | currentParent[chunk] = nameInfo[currentLongname];
|
398 | }
|
399 |
|
400 | if (currentParent[chunk]) {
|
401 | currentParent[chunk].doclet = doclets ? doclets[currentLongname] : null;
|
402 | currentParent = currentParent[chunk];
|
403 | }
|
404 | });
|
405 | });
|
406 |
|
407 | return tree;
|
408 | };
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | function splitNameMatchingBrackets(nameDesc) {
|
418 | var buffer = [];
|
419 | var c;
|
420 | var stack = 0;
|
421 | var stringEnd = null;
|
422 |
|
423 | for (var i = 0; i < nameDesc.length; ++i) {
|
424 | c = nameDesc[i];
|
425 | buffer.push(c);
|
426 |
|
427 | if (stringEnd) {
|
428 | if (c === '\\' && i + 1 < nameDesc.length) {
|
429 | buffer.push(nameDesc[++i]);
|
430 | } else if (c === stringEnd) {
|
431 | stringEnd = null;
|
432 | }
|
433 | } else if (c === '"' || c === "'") {
|
434 | stringEnd = c;
|
435 | } else if (c === '[') {
|
436 | ++stack;
|
437 | } else if (c === ']') {
|
438 | if (--stack === 0) {
|
439 | break;
|
440 | }
|
441 | }
|
442 | }
|
443 |
|
444 | if (stack || stringEnd) {
|
445 | return null;
|
446 | }
|
447 |
|
448 | nameDesc.substr(i).match(REGEXP_DESCRIPTION);
|
449 | return {
|
450 | name: buffer.join(''),
|
451 | description: RegExp.$1
|
452 | };
|
453 | }
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 | exports.splitName = function(nameDesc) {
|
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 | var result = null;
|
469 | if (nameDesc[0] === '[') {
|
470 | result = splitNameMatchingBrackets(nameDesc);
|
471 | if (result !== null) {
|
472 | return result;
|
473 | }
|
474 | }
|
475 |
|
476 | nameDesc.match(REGEXP_NAME_DESCRIPTION);
|
477 | return {
|
478 | name: RegExp.$1,
|
479 | description: RegExp.$2
|
480 | };
|
481 | };
|