56759a78a65bb20623c020e8881b2ccfd0f6177f
[app.jsdoc] / JSDOC / ScopeNamer.js
1 XObject = imports.XObject.XObject;
2
3  
4 console     = imports.console.console; 
5
6 Symbol = imports.Symbol.Symbol;   
7
8 /**
9  * @class ScopeNamer
10  * @extends Collapse
11  * @scope JSDOC
12  * The point of this class is to iterate through the Collapsed tree
13  * and add the property 'scopedName' to the tokens.
14  *
15  *
16  *
17  
18   After spending some time looking at this, a few conclusions...
19   
20   - this will have to be the base class of specific implementations.
21   - each implementation will have to decide how to declare a new scope
22   
23   scopes are normally changed when these things occur:
24
25
26   globalScope(stmts) => statementsScope($global$, stmts)
27   
28
29   'object'
30   objectScope(sname, props  )
31   a = { .... } << scope is a  $this$={a}
32   
33      // property scopes???
34     foo: function() {} $this$={object scope}
35     foo : ( xxx ? function()  { } ? function() { ...} << conditional properties types
36     foo : (function() { return x })() << a property or function??? << depends on notes
37     foo : [ ] << a property.. do not drill down?
38     foo : xxxxx << a property..
39   
40   
41   'function' 
42   functionScope(sname,  stmts  )
43   function a() { .... } << scope is a  $this$={a}
44   a = function() { } << scope might be a  $this$={a}
45   
46   // registers 'this.*'
47   
48   
49   
50   'call' scopes
51   XX.yy(a,b, { .... }) << scope might be a or b..  $this$={a}
52   aa = XX.yy(a,b, { .... }) << scope might aa   $this$={a}
53  
54   callscope(name, pref, args)
55  
56   can do losts ofthings
57  
58    ?? classify me...
59       foo = new (function() { }
60      (function() { }    
61     RETURN function(...) {
62
63     
64  
65     
66     
67     
68     
69  *
70  *
71  * usage  :
72  * sn = new ScopeName({
73     tokens : ... result from tokenizer,
74     
75  })
76    sn.buildSymbols();
77  *
78  * @param {Array} tokens array of tokens (from collapse)
79  * @param {String} srcFile the original file
80  * @param {Array} args Local variables which do not need to be added to scope.
81  */ 
82  
83 ScopeNamer = XObject.define(
84     function (cfg)
85     {
86         
87         ScopeNamer.superclass.constructor.call(this, cfg);
88         this.args = this.args ? this.args.slice(0)  : [];
89         Symbol.srcFile = this.filename;
90         this.createJSDOC = true;
91         this.global = '_global_';
92        // console.dump(ar);
93         
94     }, 
95     imports.JSDOC.Collapse.Collapse, 
96     {
97         
98         global : false,
99         /**
100          * Call debugging
101          * add/remove ';' after return to configure at present..
102          * @param {String} str String to output
103          */
104         debugCall : function(str)  {
105             
106             if (this.filename.match(/Scope\.js/)) return print(str);
107             return;
108         }, 
109         
110         collapseTop : true,
111         
112         buildSymbols : function()
113         {
114             if (!this.statements) {
115                 this.statements = this.collapse(this.tokens);
116                 //print(JSON.stringify(this.s, null,4));
117             }
118             
119             //this.globalScope(this.statements);
120             this.debugCall("build Symbols");
121             //print (this.statements);
122             //print (JSON.stringify(this.statements, null,2));
123            
124             this.walkStatements('_global_', this.statements)
125             
126                 
127                 
128         },
129         
130         
131         ignore : false,
132          
133        
134          
135         
136         walkStatements: function(scope, statements)
137         {
138             this.debugCall("walkStatements :" + scope ) ;            
139             var _this = this;
140             var res = false;
141             var isGlobal = scope == '_global_';
142            
143             statements.some(function(st ) {
144                 // handle change of scope..
145                 if (isGlobal &&
146                         st[0].jsdoc &&
147                         st[0].jsdoc.getTag('scope').length
148                    ) {
149                     //print(st[0].jsdoc.getTag('scope'));
150                     scope = st[0].jsdoc.getTag('scope');
151                     // might be an array..
152                     if (typeof(scope) != 'String') {
153                         scope = scope[0].desc;
154                     }
155                 }
156                 
157                 
158                 res = _this.walkStatement(scope, st);
159                 if (res === true) {
160                     return true;
161                 }
162                 return false;
163             });
164         },
165         
166         walkStatement: function(scope, stmt)
167         {
168             this.tokens = stmt;
169             this.rewind();
170             this.debugCall("walkStatement :" + JSON.stringify(scope)+ '@' + this.tokens[0].line );
171              
172             var name;
173             var sn;
174             
175             var isGlobal = scope == '_global_';
176             
177             
178             
179             //if (this.filename.match(/pack\.js/) && isGlobal) {
180             //    print(JSON.stringify(stmt , null,4));
181             //}
182             // @ignore stops the parser..
183             if (isGlobal &&
184                     stmt[0].jsdoc &&
185                     stmt[0].jsdoc.getTag('ignore').length
186                ) {
187                 this.debugCall("walkStatement : ignore found - " + scope + '@' + this.tokens[0].line );
188                 print("GOT ignore?");
189                 return true;
190             }
191            
192             
193             while (null != (token = this.next())) {
194                 
195                 //'function' 
196                 //walkFunction(scope, name , args,  stmts  )
197                 //
198                 if (token.name == "FUNCTION") {
199                     // function a() { .... } << scope is a  $this$={a}
200                     if (this.lookTok(1).is('NAME')) {
201                         name = isGlobal ? this.lookTok(1).data : '';
202                         
203                         this.walkFunctionDef(scope, name, this.lookTok(2).args, this.lookTok(3).items, token);
204                         continue;
205                     }
206                      //a = function() { } << scope might be a  $this$={a}
207                     if (this.lookTok(-1).data == '=' &&
208                         this.lookTok(-2).is('NAME')
209                     ) {
210                         name = this.lookTok(-2).data;
211                         this.walkFunctionDef(scope, name, this.lookTok(1).args, this.lookTok(2).items, this.lookTok(-2));
212                         continue;
213                     }
214                     
215                     
216                     print("+++ FUNCTION unusual context" + token.file + ':' + token.line);
217                     continue;
218                      
219                 }
220                 
221                 // control flow ...
222                 
223                 
224                  
225                 // 'call' scopes
226                 // XX.yy(a,b, { .... }) << scope might be a or b..  $this$={a}
227                 // aa = XX.yy(a,b, { .... }) << scope might aa   $this$={a}
228                 
229                 if (token.is('NAME') && this.lookTok(1).data =='(') {
230                     var assign = false;
231                     var jsdoc = token.jsdoc;
232                     if ((this.lookTok(-1).data == '=') && this.lookTok(-2).is('NAME')) {
233                         assign = this.lookTok(-2).data
234                         jsdoc = jsdoc || this.lookTok(-2).jsdoc;
235                     }
236                     this.walkCall(scope, assign, token.data, this.lookTok(1).items, jsdoc);
237                     continue;
238                 }
239                 
240                 //  'object'
241                 //   a = { .... } << scope is a  $this$={a}
242                 if (token.data == '{' && this.lookTok(-1).data == '=' && this.lookTok(-2).is('NAME')) {
243                     
244                     // could be var x = ..
245                     var jd = this.lookTok(-2).jsdoc ? this.lookTok(-2) : this.lookTok(-3);
246                     
247                     var isVar = this.lookTok(-3).name == 'VAR';
248                     
249                     // only register names of objects if 
250                     var name = this.lookTok(-2).data;
251                     name = !isGlobal && isVar ? false : name;
252                     
253                     name = !isGlobal && name && !name.match(/^this\./) ? false : name;
254                     if (!isGlobal && name && name.match(/^this\./) ) { 
255                         // see if scope ends in prototype..
256                         print("SCOPE:" + JSON.stringify(scope));
257                         if (
258                             (scope.split('.').pop() == 'prototype') &&
259                             (name.split('.').length == 2)
260                         ){
261                             name = name.split('.').pop();
262                         } else {
263                             name = false;
264                         }
265                         
266                     }  else {
267                         name = false;
268                     }
269                     //print(JSON.stringify(token,null,4));
270                     this.walkObject(scope, name, token.props, jd);
271                     continue;
272                 }
273                  
274                 // this.xxxx = (with jsdoc...)
275                 
276                 
277                 
278                  
279                 // standard flow....
280                 if (token.data == '{') { 
281                     sn = new ScopeNamer(this);
282                     //print("GOT { - walkings statuements;}");
283                     if (!token.items) {
284                         continue; // object..!?!?!? = ignore ???
285                         print(JSON.stringify(token,null,4));
286                     }
287                     sn.walkStatements(scope, token.items);
288                     continue;
289                 }
290             }
291         },
292         
293         walkFunctionDef : function (inscope, name, args, stmts, jsdocTok)
294         {
295             this.debugCall("wallkFuncDef: " + inscope + '#' + name + '@' + this.look(0).line );
296             var scope = name.length ? (inscope + '.' + name) : inscope;
297             if (name.length) { 
298                 var symbol = new Symbol( scope , args || [] , "FUNCTION" ,  jsdocTok.jsdoc);
299                 symbol._token = jsdocTok;
300                 this.addSymbol(symbol, jsdocTok.jsdoc);
301             }
302             var sn = new ScopeNamer(this);
303             sn.walkStatements(scope, stmts);
304             
305         },            
306         
307         
308         
309         walkCall : function (inscope, assign, callname, items, jsdocTok)
310         {
311             this.debugCall("wallkCall : " + inscope  +':' + assign + '@' + this.look(0).line + ' ' + callname );
312             var scope = inscope + ( assign ? ('.' + assign) : '' );
313             //scope = scope.replace(/\^_global\$\./, '');
314             
315             
316             
317             
318             // add the handers for differnt methods in here....
319             switch (callname) {
320                 
321                 // somecall(BASE, { .. object ..})
322                 // or... x = somecall(BASE, { ... object..})
323                 case 'XObject.extend':
324                 case 'Roo.apply':
325                     //print(JSON.stringify(items,null,4));
326                     // SHOULD WE ADD SCOPE HERE???
327                     var topscope = scope.split('.').pop();
328                     
329                     scope = (topscope == items[0][0].data) ?
330                         items[0][0].data :
331                         scope + '.' + items[0][0].data;
332                     // 2nd arg is a object def
333                     if (items[1][0].data != '{') {
334                         return;
335                     }
336                     
337                     if (assign != false   && !scope.match(/\.prototype$/)) { 
338                         var symbol = new Symbol( scope , false , "OBJECT" ,  jsdocTok);
339                         symbol._token = jsdocTok;
340                         this.addSymbol(symbol, jsdocTok.jsdoc);
341                         
342                     }
343                     
344                     
345                     var sn = new ScopeNamer(this);
346                     
347                     //print(JSON.stringify(items,null,4));
348                     sn.walkObject(scope , false, items[1][0].props, jsdocTok );
349                     
350                     return;
351                 
352                 
353                     
354                     
355                 case 'XObject.define':
356                     // func, extends, props.
357                     
358                     var symbol = new Symbol( scope , items[0][1].args || [] , "CONSTRUCTOR" ,  jsdocTok);
359                     symbol._token = jsdocTok;
360                     // extends = arg[1]
361                     
362                     this.addSymbol(symbol, jsdocTok.jsdoc);
363                     var sn = new ScopeNamer(this);
364                     //print(JSON.stringify(items, null,4));
365                     // ctr statements.
366                     sn.walkStatements(scope + '.prototype', items[0][2].items);
367                     
368                     
369                     sn.walkObject(scope + '.prototype', false, items[2][0].props );
370                     return;
371                 
372                 
373                 
374             }
375             
376             
377              
378         },
379                             
380         walkObject : function(inscope, name, items, jsdocTok)
381         {
382             var scope = inscope + (( name === false) ? '' : ('.' + name));
383            
384             if (name !== false) {
385                 
386                 var symbol = new Symbol( scope , false , "OBJECT" ,  jsdocTok.jsdoc);
387                 symbol._token = jsdocTok;
388                 this.addSymbol(symbol, jsdocTok.jsdoc);
389                  
390             }
391             // items can be false in this scenaro: a = {};
392             if (!items) {
393                 return;
394             }
395             print(typeof(items));
396             this.debugCall("wallkObject : " + scope + '@' + items[Object.keys(items)[0]].key.line);
397             for( var k in items) {
398                 var key = items[k].key;
399                 var val = items[k].val;
400                 
401                 
402                 // x : function(....)
403                 if (val[0].name == 'FUNCTION' ) {
404                     
405                     
406                     this.walkFunctionDef (scope, k, val[1].args, val[2].items, key)
407
408                     
409                     continue;
410                 }
411                 
412                 // x: ( .... ) conditional  properties? maybe detect function?
413                 
414                 
415                 // x : something else - not a function..
416                 
417                 
418                 var symbol = new Symbol( scope +'.'+ k , val[1].args || [] , "PROPERTY" ,  key.jsdoc);
419                 symbol._token = key;
420                    
421                 this.addSymbol(symbol, key.jsdoc);
422                 continue;
423                  
424                 
425             }
426             
427             
428         },
429         
430         
431         
432         
433         
434         
435         addSymbol: function(symbol, comment)
436         {
437               //print("ADD symbol: " + JSON.stringify( symbol, null, 4));
438               //print("PARSER addSYMBOL : " + symbol.alias);
439             
440             // if a symbol alias is documented more than once the last one with the user docs wins
441             
442             // dumpe some invalid symbols..
443             var ptype_ar = symbol.alias.split(/#/);
444             if (ptype_ar.length > 2) {
445                 // multiple #
446                 return;
447             }
448             if (ptype_ar.length > 1 && ptype_ar[1].split(/\./).length > 1) {
449                 // multiple . after #
450                 return;
451             }
452             
453             ScopeNamer.prototype.debugCall("addSymbol : " + symbol.alias );       
454             
455             if (ScopeNamer.symbols.hasSymbol(symbol.alias)) {
456                 
457                 if (!comment) { // we do not have a comment, and it's registered.
458                     return;
459                 }
460                 var oldSymbol = ScopeNamer.symbols.getSymbol(symbol.alias);
461                 
462                 if (oldSymbol.comment && oldSymbol.comment.isUserComment && !oldSymbol.comment.hasTags) {
463                     if (symbol.comment.isUserComment) { // old and new are both documented
464                         this.LOG.warn("The symbol '"+symbol.alias+"' is documented more than once.");
465                     }
466                     else { // old is documented but new isn't
467                         return;
468                     }
469                 }
470             }
471             
472             // we don't document anonymous things
473             //if (this.conf.ignoreAnonymous && symbol.name.match(/\$anonymous\b/)) return;
474         
475             // uderscored things may be treated as if they were marked private, this cascades
476             //if (this.conf.treatUnderscoredAsPrivate && symbol.name.match(/[.#-]_[^.#-]+$/)) {
477             //    symbol.isPrivate = true;
478             //}
479             
480             // -p flag is required to document private things
481             if ((symbol.isInner || symbol.isPrivate) && !this.docPrivate) return;
482             
483             // ignored things are not documented, this doesn't cascade
484             if (symbol.isIgnored) return;
485             
486             
487             //print("ADD symbol: " + symbol.isa + ' => ' + symbol.alias );
488             //print("ADD symbol: " + JSON.stringify( symbol, null, 4));
489             
490             // add it to the file's list... (for dumping later..)
491             if (this.srcFile) {
492                 ScopeNamer.filesSymbols[Symbol.srcFile].addSymbol(symbol);
493             }
494             
495             ScopeNamer.symbols.addSymbol(symbol);
496         },
497         addBuiltin : function(name) {
498
499             var builtin = new Symbol(name, [], "CONSTRUCTOR", new imports.DocComment.DocComment(""));
500             builtin.isNamespace = false;
501             builtin.srcFile = "";
502             builtin.isPrivate = false;
503             this.addSymbol(builtin);
504             return builtin;
505         }
506         
507         
508          
509     }
510 );
511
512 ScopeNamer.symbols =  new  imports.SymbolSet.SymbolSet();
513
514
515
516
517     
518 ScopeNamer.addSymbol = ScopeNamer.prototype.addSymbol;
519 ScopeNamer.addBuiltin = ScopeNamer.prototype.addBuiltin;