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