JSDOC/ScopeParser.js
authoralan <alan@alanfast.akbkhome.com>
Mon, 19 Apr 2010 03:41:20 +0000 (11:41 +0800)
committeralan <alan@alanfast.akbkhome.com>
Mon, 19 Apr 2010 03:41:20 +0000 (11:41 +0800)
JSDOC/ScopeParser.js

index e69de29..7bd7b00 100644 (file)
@@ -0,0 +1,809 @@
+//<Script type="text/javascript">
+
+Scope = imports['JSDOC/Scope.js'].Scope;
+
+/**
+* Scope stuff
+* 
+* // FIXME - I need this to do next() without doccomments..
+*/
+
+ScopeParser = function(ts) {
+    this.ts = ts;
+    this.warnings = [];
+    this.scopes = [];
+    this.indexedScopes = {};
+    this.timer = new Date() * 1;
+   
+}
+
+// list of keywords that should not be used in object literals.
+ScopeParser.idents = [
+       "break",         
+        "case",                 
+        "continue",    
+        "default",     
+        "delete",      
+        "do",           
+       "else",         
+       "export",       
+       "false",        
+       "for",          
+       "function",     
+       "if",           
+       "import",       
+       "in",           
+       "new",          
+       "null",         
+       "return",       
+       "switch",       
+       "this",         
+       "true",         
+       "typeof",       
+       "var",          
+       "void",         
+       "while",        
+       "with",         
+
+       "catch",        
+       "class",        
+       "const",        
+       "debugger",     
+       "enum",         
+       "extends",      
+       "finally",      
+       "super",        
+        "throw",        
+        "try",         
+
+        "abstract",    
+        "boolean",     
+        "byte",                
+        "char",                
+        "double",      
+        "final",       
+        "float",       
+        "goto",                
+        "implements", 
+        "instanceof",
+        "int",          
+        "interface",    
+        "long",                 
+        "native",      
+        "package",     
+        "private",     
+        "protected",    
+        "public",       
+        "short",       
+        "static",      
+        "synchronized",         
+        "throws",       
+        "transient",    
+               "include",       
+               "undefined"
+];
+
+
+ScopeParser.prototype = {
+    timer: 0,
+    timerPrint: function (str) {
+        var ntime = new Date() * 1;
+        var tdif =  ntime -this.timer;
+        this.timer = ntime;
+        var pref = '';
+        if (tdif > 100) { //slower ones..
+            pref = '***';
+        }
+        println(pref+'['+tdif+']'+str);
+        
+    },
+    warn: function(s) {
+        this.warnings.push(s);
+        //println("WARNING:" + htmlescape(s) + "<BR>");
+    },
+    // defaults should not be initialized here =- otherwise they get duped on new, rather than initalized..
+    warnings : false,
+    ts : false,
+    scopes : false,
+    global : false,
+    mode : "", //"BUILDING_SYMBOL_TREE",
+    braceNesting : 0,
+    indexedScopes : false,
+    munge: true,
+
+
+
+
+
+    buildSymbolTree : function()
+    {
+        //println("<PRE>");
+        
+        this.ts.rewind();
+        this.braceNesting = 0;
+        this.scopes = [];
+        
+        
+        
+        
+        this.globalScope = new JSDOC.Scope(-1, false, -1, '');
+        indexedScopes = { 0 : this.globalScope };
+        
+        this.mode = 'BUILDING_SYMBOL_TREE';
+        this.parseScope(this.globalScope);
+    },
+    mungeSymboltree : function()
+    {
+
+        if (!this.munge) {
+            return;
+        }
+
+        // One problem with obfuscation resides in the use of undeclared
+        // and un-namespaced global symbols that are 3 characters or less
+        // in length. Here is an example:
+        //
+        //     var declaredGlobalVar;
+        //
+        //     function declaredGlobalFn() {
+        //         var localvar;
+        //         localvar = abc; // abc is an undeclared global symbol
+        //     }
+        //
+        // In the example above, there is a slim chance that localvar may be
+        // munged to 'abc', conflicting with the undeclared global symbol
+        // abc, creating a potential bug. The following code detects such
+        // global symbols. This must be done AFTER the entire file has been
+        // parsed, and BEFORE munging the symbol tree. Note that declaring
+        // extra symbols in the global scope won't hurt.
+        //
+        // Note: Since we go through all the tokens to do this, we also use
+        // the opportunity to count how many times each identifier is used.
+
+        this.ts.rewind();
+        this.braceNesting = 0;
+        this.scopes= [];
+        this.mode = 'CHECKING_SYMBOL_TREE';
+        
+        //println("MUNGING?");
+        
+        this.parseScope(this.globalScope);
+        this.globalScope.munge();
+    },
+
+
+    log : function(str)
+    {
+          //println("<B>LOG:</B>" + htmlescape(str) + "<BR/>\n");
+    },
+    logR : function(str)
+    {
+            //println("<B>LOG:</B>" + str + "<BR/>");
+    },
+
+
+
+
+
+
+    parseScope : function(scope) // parse a token stream..
+    {
+        //this.timerPrint("parseScope EnterScope"); 
+        var symbol;
+        var token;
+        
+        var identifier;
+
+        var expressionBraceNesting = this.braceNesting;
+        var bracketNesting = 0;
+        var parensNesting = 0;
+        
+        var isObjectLitAr = [ false ];
+        
+        this.scopes.push(scope);
+        token = this.ts.lookT();
+        while (token) {
+          //  this.timerPrint("parseScope AFTER lookT: " + token.toString()); 
+             
+            //println("START<i>"+token.data+"</i>");
+            switch(token.tokN) {
+                case Script.TOKvar:
+                case Script.TOKconst:
+                    
+                    //this.log("parseScope GOT VAR/CONST : " + token.toString()); 
+                    while (true) {
+                        token = this.ts.nextT();
+                        
+                        if (token.tokN == Script.TOKvar) { // kludge..
+                            continue;
+                        }
+                        if (!token) { // can return false at EOF!
+                            break;
+                        }
+                        //this.logR("parseScope GOT VAR  : <B>" + token.toString() + "</B>"); 
+                        if (!token.isType('identifier')) {
+                            println(token.toString());
+                            throw "var without ident";
+                        }
+                        
+
+                        if (this.mode == "BUILDING_SYMBOL_TREE") {
+                            identifier = scope.getIdentifier(token.data) ;
+                            
+                            if (identifier == false) {
+                                scope.declareIdentifier(token.data,token);
+                            } else {
+                                token.identifier = identifier;
+                                this.warn("The variable " + symbol + " has already been declared in the same scope...");
+                            }
+                        }
+
+                        token = this.ts.nextT();
+                        /*
+                        assert token.getType() == Token.SEMI ||
+                                token.getType() == Token.ASSIGN ||
+                                token.getType() == Token.COMMA ||
+                                token.getType() == Token.IN;
+                        */
+                        if (token.isType('in')) {
+                            break;
+                        } else {
+                            this.parseExpression();
+                            //this.logR("parseScope DONE  : <B>ParseExpression</B> - tok is:" + this.ts.lookT(0).toString()); 
+                            
+                            
+                            if (this.ts.lookT(0).isType('semicolon')) {
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case Script.TOKfunction:
+                    //println("<i>"+token.data+"</i>");
+                    this.parseFunctionDeclaration();
+                    break;
+
+                case Script.TOKlbrace: // {
+                    //println("<i>"+token.data+"</i>");
+                    isObjectLitAr.push(false);
+                    this.braceNesting++;
+                    break;
+
+                case Script.TOKrbrace: // }
+                    //println("<i>"+token.data+"</i>");
+                    this.braceNesting--;
+                    isObjectLitAr.pop();
+                        //assert braceNesting >= scope.getBraceNesting();
+                    if (this.braceNesting == scope.braceN) {
+                        var ls = this.scopes.pop();
+                        ls.getUsedSymbols();
+                        return;
+                    }
+                    break;
+
+                case Script.TOKwith:
+                    //println("<i>"+token.data+"</i>");   
+                    if (this.mode == "BUILDING_SYMBOL_TREE") {
+                        // Inside a 'with' block, it is impossible to figure out
+                        // statically whether a symbol is a local variable or an
+                        // object member. As a consequence, the only thing we can
+                        // do is turn the obfuscation off for the highest scope
+                        // containing the 'with' block.
+                        this.protectScopeFromObfuscation(scope);
+                        this.warn("Using 'with' is not recommended." + (this.munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
+                    }
+                    break;
+
+                case Script.TOKcatch:
+                    //println("<i>"+token.data+"</i>");
+                    this.parseCatch();
+                    break;
+                /*
+                case Token.SPECIALCOMMENT:
+                        if (mode == BUILDING_SYMBOL_TREE) {
+                            protectScopeFromObfuscation(scope);
+                            this.warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true);
+                        }
+                        break;
+                */
+                
+                case Script.TOKstring: // used for object lit detection..
+                    //println("<i>"+token.data+"</i>");
+                    if (this.ts.lookT(-1).isType('lbrace') && this.ts.lookT(1).isType('colon')) {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                    }
+                    var isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    if (isInObjectLitAr &&  this.ts.lookT(1).isType('colon') &&
+                        ( this.ts.lookT(-11).isType('lbrace') ||  this.ts.lookT(-1).isType('comma'))) {
+                        // see if we can replace..
+                        // remove the quotes..
+                        // should do a bit more checking!!!! (what about wierd char's in the string..
+                        var str = token.data.substring(1,token.data.length-1);
+                        if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
+                            token.outData = str;
+                        }
+                        
+                         
+                        
+                    }
+                    
+                    
+                    
+                    break;
+                
+                
+                
+                case Script.TOKidentifier:
+                    
+                    // look for  { ** : <- indicates obj literal.. ** this could occur with numbers ..
+                    if ((this.ts.lookT(-1).tokN ==Script.TOKlbrace) && (this.ts.lookT(1).tokN == Script.TOKcolon)) {
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                    var isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    
+                    if (isInObjectLitAr && (this.ts.lookT(1).tokN == Script.TOKcolon) && (this.ts.lookT(-1).tokN == Script.TOKcomma)) {
+                        // skip, it's an object lit key..
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                    
+                    
+                    // skip anyting with "." before it..!!
+                    
+                    if (this.ts.lookT(-1).isType('dot')) {
+                        // skip, it's an object prop.
+                        //println("<i>"+token.data+"</i>");
+                        break;
+                    }
+                    
+                    //println("<B>SID</B>");
+                   
+                
+                
+                    symbol = token.data;
+                    
+                     if (this.mode == 'BUILDING_SYMBOL_TREE') {
+
+                        if (symbol == "eval") {
+                            // look back one and see if we can find a comment!!!
+                            if (this.ts.look(-1).isDoc()) {
+                                // look for eval:var:noreplace\n
+                                var _t = this;
+                                this.ts.look(-1).data.replace(/eval:var:([a-z_]+)/ig, function(m, a) {
+                                    
+                                    var hi = _t.getIdentifier(a, scope);
+                                   // println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
+                                    if (hi) {
+                                     //   println("PROTECT "+a+" from munge");
+                                        hi.toMunge = false;
+                                    }
+                                    
+                                });
+                                
+                                
+                            } else {
+                                
+                            
+                                this.protectScopeFromObfuscation(scope);
+                                this.warn("Using 'eval' is not recommended. (use  eval:var:noreplace in comments to optimize) " + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+                            }
+
+                        }
+
+                    } else   if (this.mode == 'CHECKING_SYMBOL_TREE') {
+                        
+                        //println("GOT IDENT: -2 : " + this.ts.lookT(-2).toString() + " <BR> ..... -1 :  " +  this.ts.lookT(-1).toString() + " <BR> "); 
+                        
+                        
+                        
+                        //println("GOT IDENT: <B>" + symbol + "</B><BR/>");
+                             
+                            //println("GOT IDENT (2): <B>" + symbol + "</B><BR/>");
+                        identifier = this.getIdentifier(symbol, scope);
+                        
+                        if (identifier == false) {
+// BUG!find out where builtin is defined...
+                            if (symbol.length <= 3 && JSDOC.Scope.builtin.indexOf(symbol) < 0) {
+                                // Here, we found an undeclared and un-namespaced symbol that is
+                                // 3 characters or less in length. Declare it in the global scope.
+                                // We don't need to declare longer symbols since they won't cause
+                                // any conflict with other munged symbols.
+                                this.globalScope.declareIdentifier(symbol, token);
+                                this.warn("Found an undeclared symbol: " + symbol, true);
+                            }
+                            
+                            //println("GOT IDENT IGNORE(3): <B>" + symbol + "</B><BR/>");
+                        } else {
+                            token.identifier = identifier;
+                            identifier.refcount++;
+                        }
+                       
+                    }
+                    break;
+                //case Script.TOKsemicolon':
+                    //println("<br/>");
+                //    break;
+                default:
+                    //println("<i>"+token.data+"</i>");
+                    break;
+                
+            } // end switch
+            
+            
+            //this.timerPrint("parseScope TOK : " + token.toString()); 
+            token = this.ts.nextT();
+            //if (this.ts.nextT()) break;
+            
+        }
+    },
+
+
+    parseExpression : function() {
+
+        // Parse the expression until we encounter a comma or a semi-colon
+        // in the same brace nesting, bracket nesting and paren nesting.
+        // Parse functions if any...
+        //println("<i>EXP</i><BR/>");
+        var symbol;
+        var token;
+        var currentScope;
+        var identifier;
+
+        var expressionBraceNesting = this.braceNesting;
+        var bracketNesting = 0;
+        var parensNesting = 0;
+
+        var isObjectLitAr = [ false ];
+        while (token = this.ts.lookT()) {
+     
+
+            
+            currentScope = this.scopes[this.scopes.length-1];
+            
+            //println("<i>"+token.data+"</i>");
+            
+            switch (token.type) {
+
+                case 'semicolon':
+                case 'comma':
+                    if (this.braceNesting == expressionBraceNesting &&
+                            bracketNesting == 0 &&
+                            parensNesting == 0) {
+                        return;
+                    }
+                    break;
+
+                case 'function':
+                    this.parseFunctionDeclaration();
+                    break;
+
+                case 'lbrace': //Token.LC:
+                    isObjectLitAr.push(false);
+                    
+                    this.braceNesting++;
+                    break;
+
+                case 'rbrace': //Token.RC:
+                    this.braceNesting--;
+                    isObjectLitAr.pop();
+                    
+                   // assert braceNesting >= expressionBraceNesting;
+                    break;
+
+                case 'lbracket': //Token.LB:
+                    bracketNesting++;
+                    break;
+
+                case 'rbracket': //Token.RB:
+                    bracketNesting--;
+                    break;
+
+                case 'lparen': //Token.LP:
+                    parensNesting++;
+                    break;
+
+                case 'rparen': //Token.RP:
+                    parensNesting--;
+                    break;
+                    
+                    
+                   
+                case 'string': // used for object lit detection..
+                    if (this.ts.lookT(-1).isType('lbrace') && this.ts.lookT(1).isType('colon')) {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                    }
+                    
+                    
+                     
+                    var isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    if (isInObjectLitAr &&  this.ts.lookT(1).isType('colon') &&
+                        ( this.ts.lookT(-11).isType('lbrace') ||  this.ts.lookT(-1).isType('comma'))) {
+                        // see if we can replace..
+                        // remove the quotes..
+                        var str = token.data.substring(1,token.data.length-1);
+                        if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
+                            token.outData = str;
+                        }
+                        
+                         
+                        
+                    }
+                    
+                    break;
+                
+                  
+                    
+                /*
+                case Token.SPECIALCOMMENT:
+                    if (mode == BUILDING_SYMBOL_TREE) {
+                        protectScopeFromObfuscation(currentScope);
+                        this.warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression!" : ""), true);
+                    }
+                    break;
+                */
+                case 'identifier':
+                    symbol = token.data;
+                    if (this.ts.lookT(-1).isType('lbrace') && this.ts.lookT(1).isType('colon')) {
+                        // then we are in an object lit.. -> we need to flag the brace as such...
+                        isObjectLitAr.pop();
+                        isObjectLitAr.push(true);
+                        break;
+                    }
+                    var isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
+                    if (isInObjectLitAr && this.ts.lookT(-1).isType('comma') && this.ts.lookT(1).isType('colon')) {
+                        break;
+                    }
+                    
+                    if (this.ts.lookT(-1).isType('dot')) {
+                        //skip '.'
+                        break;
+                    }
+                    
+                    
+                    
+                    //println("<B>EID</B>");
+                    
+                    
+                    if (this.mode == 'BUILDING_SYMBOL_TREE') {
+
+                        if (symbol == "eval") {
+                            if (this.ts.look(-1).isDoc()) {
+                                // look for eval:var:noreplace\n
+                                var _t = this;
+                                this.ts.look(-1).data.replace(/eval:var:([a-z]+)/ig, function(m, a) {
+                                    var hi = _t.getIdentifier(a, currentScope);
+                                   //println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
+                                    if (hi) {
+                                      //  println("PROTECT "+a+" from munge");
+                                        hi.toMunge = false;
+                                    }
+                                    
+                                    
+                                });
+                                
+                            } else {
+                                this.protectScopeFromObfuscation(currentScope);
+                                this.warn("Using 'eval' is not recommended." + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+                            }
+                            
+
+                        }
+                        break;
+                    } 
+                    if (this.mode == 'CHECKING_SYMBOL_TREE') {
+
+                        identifier = this.getIdentifier(symbol, currentScope);
+                        //println("<B>??</B>");
+                        if (identifier == false) {
+
+                            if (symbol.length <= 3 && JSDOC.Scope.builtin.indexOf(symbol) < 0) {
+                                // Here, we found an undeclared and un-namespaced symbol that is
+                                // 3 characters or less in length. Declare it in the global scope.
+                                // We don't need to declare longer symbols since they won't cause
+                                // any conflict with other munged symbols.
+                                this.globalScope.declareIdentifier(symbol, token);
+                                this.warn("Found an undeclared symbol: " + symbol, true);
+                            } else {
+                                //println("undeclared")
+                            }
+                            
+                            
+                        } else {
+                            //println("<B>++</B>");
+                            token.identifier = identifier;
+                            identifier.refcount++;
+                        }
+                        
+                    }
+                    break;
+            }
+            if (!this.ts.nextT()) break;
+        }
+    },
+
+
+    parseCatch : function() {
+
+        var symbol;
+        var token;
+        var currentScope;
+        var identifier;
+
+        //token = getToken(-1);
+        //assert token.getType() == Token.CATCH;
+        token = this.ts.nextT();
+        //assert token.getType() == Token.LP; (
+        token = this.ts.nextT();
+        //assert token.getType() == Token.NAME;
+        
+        symbol = token.data;
+        currentScope = this.scopes[this.scopes.length-1];
+
+        if (this.mode == 'BUILDING_SYMBOL_TREE') {
+            // We must declare the exception identifier in the containing function
+            // scope to avoid errors related to the obfuscation process. No need to
+            // display a warning if the symbol was already declared here...
+            currentScope.declareIdentifier(symbol, token);
+        } else {
+            //?? why inc the refcount?? - that should be set when building the tree???
+            identifier = this.getIdentifier(symbol, currentScope);
+            identifier.refcount++;
+        }
+
+        token = this.ts.nextT();
+        //assert token.getType() == Token.RP; // )
+    },
+    
+    parseFunctionDeclaration : function() 
+    {
+
+        var symbol;
+        var  token;
+        var currentScope  = false; 
+        var fnScope = false;
+        var identifier;
+        //this.logR("<B>PARSING FUNCTION</B>");
+        currentScope = this.scopes[this.scopes.length-1];
+
+        token = this.ts.nextT();
+        if (token.isType('identifier')) {
+            if (this.mode == 'BUILDING_SYMBOL_TREE') {
+                // Get the name of the function and declare it in the current scope.
+                symbol = token.data;
+                if (currentScope.getIdentifier(symbol) != false) {
+                    this.warn("The function " + symbol + " has already been declared in the same scope...", true);
+                }
+                currentScope.declareIdentifier(symbol,token);
+            }
+            token =  this.ts.nextT();
+        }
+
+        //assert token.getType() == Token.LP;
+        if (this.mode == 'BUILDING_SYMBOL_TREE') {
+            fnScope = new JSDOC.Scope(this.braceNesting, currentScope, token.n, '');
+            
+            //println("STORING SCOPE" + this.ts.cursor);
+            
+            this.indexedScopes[this.ts.cursor] = fnScope;
+            
+        } else {
+            //println("FETCHING SCOPE" + this.ts.cursor);
+            fnScope = this.indexedScopes[this.ts.cursor];
+        }
+        
+        // Parse function arguments.
+        var argpos = 0;
+        while (!this.ts.lookT().isType('rparen')) { //(token = consumeToken()).getType() != Token.RP) {
+            token = this.ts.nextT();
+           
+            //assert token.getType() == Token.NAME ||
+            //        token.getType() == Token.COMMA;
+            if (token.isType('identifier') && this.mode == 'BUILDING_SYMBOL_TREE') {
+                symbol = token.data;
+                identifier = fnScope.declareIdentifier(symbol,token);
+                if (symbol == "$super" && argpos == 0) {
+                    // Exception for Prototype 1.6...
+                    identifier.preventMunging();
+                }
+                argpos++;
+            }
+        }
+
+        token = this.ts.nextT();
+        // assert token.getType() == Token.LC;
+        this.braceNesting++;
+
+        token = this.ts.nextT();
+        if (token.isType('string') && this.ts.lookT(1).isType('semicolon')) {
+            /*
+            
+            NOT SUPPORTED YET!?!!?!
+            
+            // This is a hint. Hints are empty statements that look like
+            // "localvar1:nomunge, localvar2:nomunge"; They allow developers
+            // to prevent specific symbols from getting obfuscated (some heretic
+            // implementations, such as Prototype 1.6, require specific variable
+            // names, such as $super for example, in order to work appropriately.
+            // Note: right now, only "nomunge" is supported in the right hand side
+            // of a hint. However, in the future, the right hand side may contain
+            // other values.
+            consumeToken();
+            String hints = token.getValue();
+            // Remove the leading and trailing quotes...
+            hints = hints.substring(1, hints.length() - 1).trim();
+            StringTokenizer st1 = new StringTokenizer(hints, ",");
+            while (st1.hasMoreTokens()) {
+                String hint = st1.nextToken();
+                int idx = hint.indexOf(':');
+                if (idx <= 0 || idx >= hint.length() - 1) {
+                    if (mode == BUILDING_SYMBOL_TREE) {
+                        // No need to report the error twice, hence the test...
+                        this.warn("Invalid hint syntax: " + hint, true);
+                    }
+                    break;
+                }
+                String variableName = hint.substring(0, idx).trim();
+                String variableType = hint.substring(idx + 1).trim();
+                if (mode == BUILDING_SYMBOL_TREE) {
+                    fnScope.addHint(variableName, variableType);
+                } else if (mode == CHECKING_SYMBOL_TREE) {
+                    identifier = fnScope.getIdentifier(variableName);
+                    if (identifier != null) {
+                        if (variableType.equals("nomunge")) {
+                            identifier.preventMunging();
+                        } else {
+                            this.warn("Unsupported hint value: " + hint, true);
+                        }
+                    } else {
+                        this.warn("Hint refers to an unknown identifier: " + hint, true);
+                    }
+                }
+            }
+            */
+        }
+
+        this.parseScope(fnScope);
+        // now pop it off the stack!!!
+       
+        
+        
+    },
+    
+    protectScopeFromObfuscation : function(scope) {
+            //assert scope != null;
+        
+        if (scope == this.globalScope) {
+            // The global scope does not get obfuscated,
+            // so we don't need to worry about it...
+            return;
+        }
+
+        // Find the highest local scope containing the specified scope.
+        while (scope && scope.parent != this.globalScope) {
+            scope = scope.parent;
+        }
+
+        //assert scope.getParentScope() == globalScope;
+        scope.preventMunging();
+    },
+    
+    getIdentifier: function(symbol, scope) {
+        var identifier;
+        while (scope != false) {
+            identifier = scope.getIdentifier(symbol);
+            //println("ScopeParser.getIdentgetUsedSymbols("+symbol+")=" + scope.getUsedSymbols().join(','));
+            if (identifier) {
+                return identifier;
+            }
+            scope = scope.parent;
+        }
+        return false;
+    }
+});
\ No newline at end of file