Advertisement
rplantiko

JavaScript syntax tree traversal with context data

Aug 7th, 2014
679
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ---------------------------------------------------------------------
  2. // Tree traversal with context data
  3. // ---------------------------------------------------------------------
  4.  
  5. // This exercise shows how to traverse a tree with node-level context data
  6. // As example, we use the Esprima JavaScript parser in node.js
  7. // to find global usages and declarations of a JavaScript file
  8.  
  9. // Tree traversal usually is solved with recursion - like here
  10. // The stack-based execution model automatically ensures that local data
  11. // will be cleared when a function is left
  12. // This is exactly what is needed for scope-specific context data in tree traversal
  13.  
  14. // The worker function to be executed for each node has this signature:
  15. // func( node, nodeName, obj)
  16. // Here, node is the current node object with name nodeName.
  17. // obj is the context object attached to the node
  18. // The function func may return a new obj object as return value.
  19. // This will be used as "obj" parameter for all the child nodes of the current node
  20.  
  21.  // Also, it is possible to skip the traversal of a subtree
  22.  // by raising the special exception _skipNode
  23.  
  24. esprima = require( "esprima" );
  25.  
  26. _skipNode = "Skip Node exception";  // special exception for skipping node
  27.  
  28. function analyzeCode(code,func,obj) {
  29.  
  30.   var ast = esprima.parse(code);
  31.      
  32.   traverse(ast,"__root",func,obj);
  33.  
  34.   function traverse(node, nodeName, func, obj) {
  35.     try {
  36.       var _obj = func(node,nodeName,obj);
  37.       Object.keys(node).forEach(function(nodeName){
  38.         var child = node[nodeName];
  39.         if (typeof child === 'object' && child !== null) {
  40.           if (Array.isArray(child)) {
  41.             child.forEach(function(node,i) {
  42.                 traverse(node, nodeName + "[" + i + "]",func, _obj);
  43.               });
  44.           } else {
  45.             traverse(child, nodeName, func, _obj);
  46.           }
  47.         }
  48.       });
  49.       } catch (e) {
  50.           if (e!=_skipNode) throw e;
  51.           }
  52.    }
  53. }
  54.  
  55. function analyzeFile( fileName, func, obj, doAtEnd ) {
  56.   fs.readFile( fileName, 'utf8', function( err, data ) {
  57.     if (err) throw err;
  58.     analyzeCode( data, func, obj );
  59.     if (doAtEnd) doAtEnd( );
  60.     });
  61. }
  62.  
  63. function getSymbols( fileName, doAtEnd ) {
  64.   var globals = { declared:{}, used:{} };
  65.   if (!doAtEnd)
  66.     doAtEnd = function(globals) { console.log( globals ) };
  67.   analyzeFile( fileName,
  68.               _getNames,
  69.               globals,
  70.               function() {
  71.                 doAtEnd(globals);
  72.                 });
  73.   function _getNames(node,nodeName,obj) {
  74.     if (node.type == "FunctionDeclaration") {
  75.         obj.declared[node.id.name] = "f";
  76.         return { declared:{}, used:{}, parentObj:obj }  // with parentObj, we have a scope chain
  77.         }
  78.     if (node.type ==  "VariableDeclarator") {
  79.         obj.declared[node.id.name] = "v";
  80.         }
  81.        
  82.     if (nodeName == "key" || nodeName == "property") return obj;
  83.    
  84.     if (nodeName.match(/^params/) &&
  85.         node.type == "Identifier") {
  86.       obj.declared[node.name] = "p";
  87.       }
  88.    
  89.     if (node.type == "Identifier") {
  90.       setUsedSymbol(node.name,getScopeObjectForSymbol(node.name,obj));
  91.       }        
  92.     return obj;
  93.    
  94. // ----    
  95.    
  96.     function setUsedSymbol(name,obj) {
  97.       if (name in obj.used) obj.used[name]++;
  98.       else obj.used[name] = 1;
  99.       }
  100.      
  101.     function getScopeObjectForSymbol(name,obj) {
  102.       while (obj) {
  103.         if (name in obj.declared) return obj;
  104.         obj = obj.parentObj;
  105.         }
  106.       return globals;  // Not found - return global object
  107.       }  
  108.    
  109.     }
  110.   }
  111.  
  112. // If a naming convention prefixes local variables with l, the following filter finds
  113. // "local" symbol names used which aren't declared and hence are global erroneously
  114. function filterNames( symbols ) {
  115.   console.log( Object.keys(symbols.used).filter( function(name) { return !! name.match(/^l/)}) );  
  116.   }
  117.  
  118. getSymbols( "C:/Temp/vorbest.js") //, filterNames ); // <-- Place your javascript file here
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement