whole_func_string: function() {
        var t = this;
        
        function getKeyName(key) {
          var keyName = key.type == "StringLiteral" ? key.value : key.name;
          return keyName;
        }
        
        function enterFunction(path, isClassMember) {
          var node = path.node;
          if (node.id) {
            t.addDeclaration(node.id.name);
          }
          t.pushScope(node.id ? node.id.name : null, node, isClassMember);
          for (var i = 0; i < node.params.length; i++) {
            t.addDeclaration(node.params[i].name);
          }
        }
        
        function exitFunction(path) {
          var node = path.node;
          t.popScope(node);
        }
  
        var FUNCTION_DECL_OR_EXPR = {
          enter: enterFunction,
          exit: exitFunction
        };
  
        function getJsDoc(comment) {
          if (!comment) {
            return null;
          }
          if (!qx.lang.Type.isArray(comment)) {
            comment = [comment];
          }
          var result = {};
          comment.forEach(comment => {
            var tmp = qx.tool.compiler.jsdoc.Parser.parseComment(comment.value);
            for (var key in tmp) {
              var value = tmp[key];
              if (!result[key]) {
                result[key] = value; 
              } else {
                qx.lang.Array.append(result[key], value);
              }
            }
          });
          
          return result;
        }
        
        function makeMeta(sectionName, functionName, node) {
          var meta;
          if (functionName) {
            var section = t.__classMeta[sectionName];
            if (section === undefined) {
              section = t.__classMeta[sectionName] = {};
            }
            meta = section[functionName];
            if (meta === undefined) {
              meta = section[functionName] = {};
            }
          } else {
            meta = t.__classMeta[sectionName];
            if (meta === undefined) {
              meta = t.__classMeta[sectionName] = {};
            }
          }
          meta.location = node.loc;
          
          if (node.leadingComments) {
            var jsdoc = getJsDoc(node.leadingComments);
            if (jsdoc) {
              if (jsdoc["@ignore"]) {
                jsdoc["@ignore"].forEach(function(elem) {
                  t.addIgnore(elem.body);
                });
              }
              meta.jsdoc = jsdoc;
            }
          }
          
          if (sectionName === "members" || sectionName === "statics") {
            if (node.type == "ObjectMethod" || node.value.type === "FunctionExpression") {
              meta.type = "function"; 
            } else {
              meta.type = "variable"; 
            }
            if (functionName.startsWith("__")) {
              meta.access = "private"; 
            } else if (functionName.startsWith("_")) {
              meta.access = "protected";
            } else {
              meta.access = "public"; 
            }
          } 
          return meta;
        }
  
        var needsQxCoreEnvironment = false;
        var COLLECT_CLASS_NAMES_VISITOR = {
          MemberExpression(path) {
            var self = this;
            var str = collapseMemberExpression(path.node);
            t._requireClass(str, { location: path.node.loc });
            var info = t.__analyser.getSymbolType(str);
            if (info && info.symbolType == "class") {
              self.collectedClasses.push(str);
            }
          }
        };
  
        const CODE_ELIMINATION_VISITOR = {
          CallExpression(path) {
            const name = collapseMemberExpression(path.node.callee);
  
            if ((name === "qx.core.Environment.select" || name === "qx.core.Environment.get") &&
                types.isLiteral(path.node.arguments[0])) {
              const arg = path.node.arguments[0];
              const env = t.__analyser.getEnvironment();
              const envValue = env[arg.value];
  
              if (envValue !== undefined) {
                if (name === "qx.core.Environment.get") {
                  path.skip();
                  path.replaceWithSourceString(formatValueAsCode(envValue));
                  return;
                } else if (name === "qx.core.Environment.select") {
                  const subPath = path.get("arguments.1");
                  let option = subPath.node.properties.find(prop => prop.key.value === envValue.toString());
  
                  if (!option) { // try to find default value
                    option = subPath.node.properties.find(prop => prop.key.value === "default");
                  }
  
                  if (option) {
                    // path.skip();
                    path.replaceWith(option.value);
                    return;
                  }
                }
              }
              needsQxCoreEnvironment = path.node.loc;
            }
          },
  
          IfStatement: {
            exit(path) {
              var node = path.node;
              // If it's a literal value, we can eliminate code because we can resolve it now.  This
              //  is really important for anything wrapped in `if (qx.core.Environment.get("qx.debug")) ...`
              //  because the `qx.core.Environment.get` is replaced with a literal value and we need to
              //  use this to remove the unwanted code.
              if (types.isLiteral(node.test)) {
                if (node.test.value) {
                  path.replaceWith(node.consequent);
                } else if (node.alternate) {
                  path.replaceWith(node.alternate);
                } else {
                  path.remove(); 
                }
              }
            }
          },
  
          BinaryExpression: {
            exit(path) {
              var node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right) && "+-*/".indexOf(node.operator) > -1) {
                var result;
                switch (node.operator) {
                  case "+": 
                    result = node.left.value + node.right.value; 
                    break;
                  case "-": 
                    result = node.left.value - node.right.value; 
                    break;
                  case "/": 
                    result = node.left.value / node.right.value; 
                    break;
                  case "*": 
                    result = node.left.value * node.right.value; 
                    break;
                }
                path.skip();
                path.replaceWithSourceString(formatValueAsCode(result));
              }
            }
          }
        };
  
        function collectJson(node) {
          var result;
          if (node.type == "ObjectExpression") {
            result = {};
            node.properties.forEach(function(prop) {
              var key = prop.key.name;
              var value = collectJson(prop.value);
              result[key] = value;
            });
          } else if (node.type == "Literal" || 
              node.type == "StringLiteral" || 
              node.type == "BooleanLiteral" || 
              node.type == "NumericLiteral" || 
              node.type == "NullLiteral") {
            result = node.value;
          } else if (node.type == "ArrayExpression") {
            result = [];
            node.elements.forEach(function(elem) {
              result.push(collectJson(elem));
            });
          } else if (node.type == "Identifier") {
            result = node.name;
          } else if (node.type == "CallExpression" || node.type == "FunctionExpression") {
            result = new Function("[[ Function ]]");
          } else if (node.type == "MemberExpression") {
            result = collapseMemberExpression(node);
          } else if (node.type == "UnaryExpression") {
            if (node.operator == "-") {
              let tmp = collectJson(node.argument);
              if (typeof tmp == "number") {
                return tmp * -1; 
              }
            } else if (node.operator == "!") {
              let tmp = collectJson(node.argument);
              if (typeof tmp == "boolean") {
                return !tmp; 
              }
            }
            result = "[[ UnaryExpression ]]";
          } else if (node.type == "NewExpression") {
            result = "[[ NewExpression ]]";
          } else {
            t.warn("Cannot interpret AST " + node.type + " at " + t.__className + " [" + node.loc.start.line + "," + node.loc.start.column + "]");
            result = null;
          }
          return result;
        }
        
        const ALLOWED_KEYS = {
            "class": {
              "static": {
                "@"           : "object",
                "type"        : "string",    // String
                "statics"     : "object",    // Map
                "environment" : "object",    // Map
                "defer"       : "function"   // Function
              },
              "normal": {
                "@"          : "object",
                "@construct" : "object",
                "@destruct"  : "object",
                "type"       : "string",    // String
                "extend"     : "function",  // Function
                "implement"  : "object",    // Interface[]
                "include"    : "object",    // Mixin[]
                "construct"  : "function",  // Function
                "statics"    : "object",    // Map
                "properties" : "object",    // Map
                "members"    : "object",    // Map
                "environment"   : "object", // Map
                "events"     : "object",    // Map
                "defer"      : "function",  // Function
                "destruct"   : "function"   // Function
              }
            },
            "interface": {
              "extend"     : "object", // Interface | Interface[]
              "statics"    : "object", // Map
              "members"    : "object", // Map
              "properties" : "object", // Map
              "events"     : "object"  // Map
            },
            "mixin": {
              "include"    : "object",   // Mixin | Mixin[]
              "statics"    : "object",   // Map
              "members"    : "object",   // Map
              "properties" : "object",   // Map
              "events"     : "object",   // Map
              "destruct"   : "function", // Function
              "construct"  : "function"  // Function
            },
            "theme": {
              "title"       : "string", // String
              "aliases"     : "object", // Map
              "type"        : "string", // String
              "extend"      : "object", // Theme
              "colors"      : "object", // Map
              "borders"     : "object", // Map
              "decorations" : "object", // Map
              "fonts"       : "object", // Map
              "icons"       : "object", // Map
              "widgets"     : "object", // Map
              "appearances" : "object", // Map
              "meta"        : "object", // Map
              "include"     : "object", // Array
              "patch"       : "object", // Array
              "boot"        : "function" // Function
            }
        };
        
        function isValidExtendClause(prop) {
          if (prop.value.type == "MemberExpression" || prop.value.type == "Identifier") {
            return true;
          }
          if (t.__classMeta.type === "class") {
            return false;
          }
          if (prop.value.type == "ArrayExpression") {
            return prop.value.elements.every(elem => elem.type == "MemberExpression" || elem.type == "Identifier");
          }
          return false;
        }
  
        var CLASS_DEF_VISITOR = {
          Property(path) {
            if (path.parentPath.parentPath != this.classDefPath) {
              path.skip();
              path.traverse(VISITOR);
              return;
            }
            var prop = path.node;
            var keyName = getKeyName(prop.key);
            let allowedKeys = ALLOWED_KEYS[t.__classMeta.type];
            if (t.__classMeta.type === "class")
              allowedKeys = allowedKeys[ t.__classMeta.isStatic ? "static" : "normal" ];
            
            if (allowedKeys[keyName] === undefined)
              t.addMarker("compiler.invalidClassDefinitionEntry", prop.value.loc, t.__classMeta.type, keyName);
  
            if (keyName == "extend") {
              if (!isValidExtendClause(prop)) {
                t.addMarker("compiler.invalidExtendClause", prop.value.loc);
                t.__fatalCompileError = true;
              } else {
                t.__classMeta.superClass = collapseMemberExpression(prop.value);
                t._requireClass(t.__classMeta.superClass, { location: path.node.loc });
              }
            } else if (keyName == "type") {
              var type = collapseMemberExpression(prop.value);
              t.__classMeta.isAbstract = type === "abstract";
              t.__classMeta.isStatic = type === "static";
              t.__classMeta.isSingleton = type === "singleton";
            } else if (keyName == "implement") {
              path.skip();
              path.traverse(COLLECT_CLASS_NAMES_VISITOR, { collectedClasses: t.__classMeta.interfaces });
            } else if (keyName == "include") {
              path.skip();
              path.traverse(COLLECT_CLASS_NAMES_VISITOR, { collectedClasses: t.__classMeta.mixins });
            } else if (keyName == "defer") {
              t.__hasDefer = true;
              t.__inDefer = true;
              makeMeta("defer", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__inDefer = false;
            } else if (keyName == "construct") {
              t.__classMeta.functionName = "$$constructor";
              makeMeta("construct", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__classMeta.functionName = null;
            } else if (keyName == "destruct") {
              t.__classMeta.functionName = "$$destructor";
              makeMeta("destruct", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__classMeta.functionName = null;
            } else if (keyName == "statics") {
              if (this.__className == "qx.core.Environment") {
                var checks = this.__findProperty(prop.value.properties, "_checksMap");
                var props = checks.value.properties;
                for (var j = 0; j < props.length; j++) {
                  this.__analyser.setEnvironmentCheck(props[j].key.value, props[j].value.value);
                }
              }
              var staticsPath = path;
              path.skip();
              path.traverse({
                Property(path) {
                  path.skip();
                  if (path.parentPath.parentPath != staticsPath) {
                    path.traverse(VISITOR);
                    return; 
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("statics", t.__classMeta.functionName, path.node);
                  path.traverse(VISITOR);
                  t.__classMeta.functionName = null;
                },
                FunctionDeclaration: FUNCTION_DECL_OR_EXPR,
                FunctionExpression: FUNCTION_DECL_OR_EXPR,
                ArrowFunctionExpression: FUNCTION_DECL_OR_EXPR
              });
            } else if (keyName == "members") {
              var membersPath = path;
              path.skip();
              path.traverse({
                Property(path) {
                  path.skip();
                  if (path.parentPath.parentPath != membersPath) {
                    path.traverse(VISITOR);
                    return;
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("members", t.__classMeta.functionName, path.node);
                  path.traverse(VISITOR);
                  t.__classMeta.functionName = null;
                },
                ObjectMethod(path) {
                  path.skip();
                  if (path.parentPath.parentPath != membersPath) {
                    path.traverse(VISITOR);
                    return;
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("members", t.__classMeta.functionName, path.node);
                  enterFunction(path, true);
                  path.traverse(VISITOR);
                  exitFunction(path);
                  t.__classMeta.functionName = null;
                },
                FunctionDeclaration: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                },
                FunctionExpression: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                },
                ArrowFunctionExpression: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                }
              });
            } else if (keyName == "properties") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidProperties", prop.loc||null);
              } else {
                prop.value.properties.forEach(function(pdNode) {
                  var propName = getKeyName(pdNode.key);
                  var meta = makeMeta("properties", propName, pdNode);
                  var data = collectJson(pdNode.value);
                  meta.name = propName;
                  meta.propertyType = "new";
                  [ "refine", "themeable", "event", "inheritable", "apply", "async", "group", "nullable", "init" ]
                    .forEach(name => meta[name] = data[name]);
                  if (data.nullable !== undefined) {
                    meta.allowNull = data.nullable;
                  }
                  if (data.check !== undefined) {
                    if (qx.lang.Type.isArray(data.check)) {
                      meta.possibleValues = data.check; 
                    } else {
                      meta.check = data.check;
                    }
                  }
                  if (data.init !== undefined) {
                    meta.defaultValue = data.init; 
                  }
                });
              }
              path.traverse(VISITOR);
            } else if (keyName == "events") {
              path.skip();
              if (prop.value.properties) {
                prop.value.properties.forEach(function(eventNode) {
                  var eventName = getKeyName(eventNode.key);
                  var meta = makeMeta("events", eventName, eventNode);
                  meta.name = eventName;
                  meta.type = collectJson(eventNode.value);
                });
              }
              path.traverse(VISITOR);
            } else if (keyName == "aliases") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidAliases", prop.loc||null);
              } else {
                var meta = makeMeta("aliases", null, prop);
                meta.aliasMap = {};
                prop.value.properties.forEach(function(aliasNode) {
                  var aliasName = getKeyName(aliasNode.key);
                  var aliasValue = getKeyName(aliasNode.value);
                  meta.aliasMap[aliasName] = aliasValue;
                });
              }
            } else if (keyName == "meta") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidThemeMeta", prop.loc||null);
              } else {
                let meta = makeMeta("themeMeta", null, prop);
                meta.themeMetaMap = {};
                prop.value.properties.forEach(function(node) {
                  var key = getKeyName(node.key);
                  var value = collapseMemberExpression(node.value);
                  meta.themeMetaMap[key] = value;
                });
              }
              path.traverse(VISITOR);
            } else {
              path.skip();
              path.traverse(VISITOR);
            }
          }
        };
  
        const TYPE = {
            "qx.Class.define": "class",
            "qx.Mixin.define": "mixin",
            "qx.Theme.define": "theme",
            "qx.Interface.define": "interface",
            "qx.Bootstrap.define": "class"
        };
        
        var VISITOR = {
          NewExpression: {
            enter(path) {
              var str = collapseMemberExpression(path.node.callee);
              t._requireClass(str, { usage: "dynamic", location: path.node.loc });
            },
            exit(path) {
              if (t.__analyser.isAddCreatedAt()) {
                var fn = types.memberExpression(types.identifier("qx"), types.identifier("$$createdAt"));
                var tmp = types.callExpression(fn, [ 
                  path.node,
                  types.stringLiteral(t.__className.replace(/\./g, "/") + ".js"),
                  types.numericLiteral(path.node.loc.start.line),
                  types.numericLiteral(path.node.loc.start.column) 
                  ]);
                path.replaceWith(tmp);
                path.skip();
              }
            }
          },
          
          Program: {
            exit(path) {
              let dbClassInfo = t._compileDbClassInfo();
              let copyInfo = {};
              let hasLoadDeps = false;
              if (dbClassInfo.dependsOn) {
                copyInfo.dependsOn = {};
                Object.keys(dbClassInfo.dependsOn).forEach(key => {
                  let tmp = copyInfo.dependsOn[key] = Object.assign({}, dbClassInfo.dependsOn[key]);
                  if (tmp.load) {
                    delete tmp.load;
                    tmp.require = true;
                    hasLoadDeps = true;
                  }
                });
              }
              if (dbClassInfo.environment) {
                copyInfo.environment = dbClassInfo.environment;
                let required = dbClassInfo.environment.required;
                if (required) {
                  for (let key in required) {
                    if (required[key].load) {
                      hasLoadDeps = true;
                      break;
                    }
                  }
                }
              }
              let tmp = types.variableDeclaration("var", [ types.variableDeclarator(types.identifier("$$dbClassInfo"), literalValueToExpression(copyInfo)) ]);
              let inject = [tmp];
              if (hasLoadDeps) {
                tmp = babylon.parse("qx.Bootstrap.executePendingDefers($$dbClassInfo);").program.body;
                inject.push(tmp[0]);
              }
              path.node.body.forEach(node => inject.push(node));
              tmp = babylon.parse(t.__className + ".$$dbClassInfo = $$dbClassInfo;").program.body;
              inject.push(tmp[0]);
              let block = types.blockStatement(inject);
              let rootFn = types.expressionStatement(types.callExpression(types.functionExpression(null, [], block), []));
  
              path.node.body.splice(0, path.node.body.length, rootFn);
            }
          },
  
          CallExpression: {
            enter: function(path) {
              function getStringArg(index) {
                if (index >= path.node.arguments.length) {
                  return null; 
                }
                let arg = path.node.arguments[index];
                if (arg.type == "StringLiteral") {
                  return arg.value; 
                }
                return null;
              }
  
              function addTranslation(entry) {
                let lineNo = path.node.loc.start.line;
                let cur = t.__translations[entry.msgid];
                if (cur) {
                  if (!qx.lang.Type.isArray(cur.lineNo)) {
                    cur.lineNo = [cur.lineNo];
                  }
                  cur.lineNo.push(lineNo);
                } else {
                  entry.lineNo = lineNo;
                  t.__translations.push(entry);
                }
              }
  
              path.traverse(CODE_ELIMINATION_VISITOR, {classDefPath: path});
  
              if (types.isMemberExpression(path.node.callee)) {
                let name = collapseMemberExpression(path.node.callee);
                let thisAlias = null;
                
                // Try and translate aliases for "this"; this is a really simplistic implementation
                //  because static code path analysis is way out of scope for the compiler, so we're
                //  only trying to handle code like:
                //    ```
                //    var that = this, args = arguments;
                //    (function() { that.base(args); })();
                //    ```
                if (path.node.callee.object.type == "Identifier") {
                  let originalAlias = path.node.callee.object.name;
                  let alias = originalAlias;
                  let aliasIsThis = false;
                  for (let scope = t.__scope; scope; scope = scope.parent) {
                    while (!aliasIsThis) {
                      let tmp = scope.vars[alias];
                      if (tmp === "this") {
                        aliasIsThis = true;
                        break;
                      }
                      if (typeof tmp == "string") {
                        alias = tmp;
                      } else {
                        break;
                      }
                    }
                    if (aliasIsThis || scope.isClassMember)
                      break;
                  }
                  if (aliasIsThis) {
                    name = "this" + name.substring(originalAlias.length);
                    thisAlias = originalAlias;
                  }
                }
  
                if (TYPE[name]) {
                  t.__definingType = name.match(/\.([a-zA-Z]+)\./)[1];
                  let node = path.node;
                  let className = node.arguments[0].value;
                  let classDef = node.arguments[1];
    
                  // We can only parse objects (eg qx.data.marshal.Json.__toClass creates objects on the fly that we cannot scan)
                  if (classDef.type != "ObjectExpression") {
                    return;
                  }
                  
                  // Create new meta
                  t.__pushMeta(className);
                  if (needsQxCoreEnvironment) {
                    t._requireClass("qx.core.Environment", { location: needsQxCoreEnvironment, defer: true }); 
                  }
    
                  let meta = makeMeta("clazz", null, path.parent);
                  t.__classMeta.type = TYPE[name];
                  if (t.__definingType == "Class" || t.__definingType == "Bootstrap") {
                    let typeProp = classDef.properties.find(prop => prop.key.type == "Identifier" && prop.key.value == "type");
                    if (typeProp)
                      t.__classMeta.isStatic == typeProp.value.type == "Literal" && typeProp.value.value === "static";
                  }
                  let jsdoc = meta.jsdoc;
                  if (jsdoc) {
                    if (jsdoc["@use"]) {
                      jsdoc["@use"].forEach(function(elem) {
                        t._requireClass(elem.body, { where: "use", load: false, location: path.parent.loc });
                      });
                    }
                    if (jsdoc["@require"]) {
                      jsdoc["@require"].forEach(function(elem) {
                        t._requireClass(elem.body, { where: "require", load: false, location: path.parent.loc });
                      });
                    }
                    if (jsdoc["@optional"]) {
                      jsdoc["@optional"].forEach(function(elem) {
                        t.addIgnore(elem.body);
                      });
                    }
                    if (jsdoc["@ignore"]) {
                      jsdoc["@ignore"].forEach(function(elem) {
                        t.addIgnore(elem.body);
                      });
                    }
                    if (jsdoc["@asset"]) {
                      jsdoc["@asset"].forEach(function(elem) {
                        t._requireAsset(elem.body);
                      });
                    }
                  }
    
                  t._requireClass(name, { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(CLASS_DEF_VISITOR, {classDefPath: path});
                  t.__popMeta(className);
                  
                  // Must be a conventional define 
                  if (path.node.arguments.length != 2 || 
                      path.node.arguments[0].type != "StringLiteral" ||
                      path.node.arguments[1].type != "ObjectExpression") {
                    return;
                  }
                } else if (name == "qx.core.Environment.add") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.add: " + arg.value); 
                    } else {
                      t.__environmentChecks.provided[arg.value] = true;
                    }
                  }
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                } else if (name == "qx.core.Environment.get") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.get: " + arg.value); 
                    } else {
                      t.addEnvCheck(arg.value, path.node.loc);
                    }
                  }
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(VISITOR);
                } else if (name == "qx.core.Environment.select") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.select: " + arg.value);
                    } else {
                      t.addEnvCheck(arg.value, path.node.loc);
                    }
                  }
  
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(VISITOR);
                } else if (name == "this.base") {
                  let expr;
                  
                  // For mixins, there is never a valid time to call this.base() in the constructor; but it is
                  //  OK in methods - but we have to refer to superclass.methodName directly.  For ordinary
                  //  classes, we need to use constructor.methodName.base.
                  if (t.__definingType == "Mixin") {
                    expr = expandMemberExpression(t.__classMeta.className + ".$$members." + t.__classMeta.functionName + ".base.call");
                    // expr = expandMemberExpression("this.constructor.superclass.prototype." + t.__classMeta.functionName + ".call");
                  } else if (t.__classMeta.functionName == "$$constructor") {
                    expr = expandMemberExpression(t.__classMeta.superClass + ".constructor.call");
                  } else {
                    expr = expandMemberExpression(t.__classMeta.className + ".prototype." + t.__classMeta.functionName + ".base.call"); 
                  }
                  if (thisAlias) {
                    path.node.arguments[0] = types.identifier(thisAlias);
                  } else {
                    path.node.arguments[0] = types.thisExpression();
                  }
                  let callExpr = types.callExpression(expr, path.node.arguments);
                  path.replaceWith(callExpr);
                } else if (name == "this.base.apply" || name == "this.base.call") {
                  let methodName = name == "this.base.apply" ? "apply" : "call";
  
                  let expr;
                  if (t.__definingType == "Mixin") {
                    expr = expandMemberExpression("this.constructor.superclass.prototype." + t.__classMeta.functionName + ".call");
                  } else if (t.__classMeta.functionName == "$$constructor") {
                    expr = expandMemberExpression(t.__classMeta.superClass + ".constructor." + methodName); 
                  } else {
                    expr = expandMemberExpression(t.__className + ".prototype." + t.__classMeta.functionName + ".base." + methodName); 
                  }
                  
                  // Original call to this.base.apply would have included arguments in the first element of the array
                  //  here we remove that because it's not needed, specifically changing 
                  //    `this.base.apply(this, args)` to
                  //    `this.base.apply(this, args.slice().shift())`
                  let exprSplice = types.callExpression(types.memberExpression(path.node.arguments[1], types.identifier("slice")), []);
                  let exprUnshift = types.callExpression(types.memberExpression(exprSplice, types.identifier("shift")), []);
                  let callExpr = types.callExpression(expr, [ path.node.arguments[0], exprUnshift ]);
                  path.replaceWith(callExpr);
                } else if (name == "this.self") {
                  let expr = expandMemberExpression(t.__className);
                  path.replaceWith(expr);
                } else if (name == "this.tr" || name == "this.marktr" || name == "qx.locale.Manager.tr" || name == "qx.locale.Manager.marktr") {
                  let arg0 = getStringArg(0);
                  if (!arg0) {
                    t.addMarker("translate.invalidMessageId", path.node.loc, arg0);
                  } else {
                    addTranslation({ msgid: arg0 });
                  }
                } else if (name == "this.trn" || name == "qx.locale.Manager.trn") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  if (!arg0 || !arg1) {
                    t.addMarker("translate.invalidMessageIds", path.node.loc, arg0, arg1);
                  } else {
                    addTranslation({ msgid: arg0, msgid_plural: arg1 });
                  }
                } else if (name == "this.trc" || name == "qx.locale.Manager.trc") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  if (!arg0 || !arg1) {
                    t.addMarker("translate.invalidMessageIds", path.node.loc, arg0, arg1);
                  } else {
                    addTranslation({ msgid: arg1, comment: arg0 });
                  }
                } else if (name == "this.trnc" || name == "qx.locale.Manager.trnc") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  let arg2 = getStringArg(2);
                  if (!arg0 || !arg1 || !arg2) {
                    t.addMarker("translate.invalidMessageIds3", path.node.loc, arg0, arg1, arg2); 
                  } else {
                    addTranslation({ msgid: arg1, msgid_plural: arg2, comment: arg0 }); 
                  }
                } else {
                  let pos = name.lastIndexOf(".");
                  // name can be ".concat" when used with "[].concat"
                  if (pos > 0) {
                    let str = name.substring(0, pos);
                    if (!t.hasDeclaration(str)) {
                      t._requireClass(str, { usage: "dynamic", location: path.node.loc }); 
                    }
                  }
                }
              }
            }
          },
  
          MemberExpression(path) {
            // regular expression or string property (eg "aa".charCodeAt())
            if (path.node.object.type == "Literal") {
              return;
            }
  
            // Handle `[ 123 ].blah()` by visiting
            if (path.node.object.type == "ArrayExpression") {
              return;
            }
            
            // Handle `[ 123 ].blah()` by visiting
            if (path.node.object.type == "MemberExpression" && path.node.object.object.type == "ArrayExpression") {
              return;
            }
            
            let name = collapseMemberExpression(path.node);
            if (name.startsWith("(")) {
              return;
            }
            let members = name.split(".");
  
            // Ignore 'this' references
            if (members[0] === "this") {
              return; 
            }
  
            // Global variable or a local variable?
            if (qx.tool.compiler.ClassFile.GLOBAL_SYMBOLS[members[0]] || t.hasDeclaration(members[0])) {
              return; 
            }
  
            let info = t._requireClass(name, { location: path.node.loc });
            if (!info || !info.className) {
              // The code `abc.def.ghi()` will produce a member expression for both `abc.def` (two Identifier's) 
              //  and another for `abc.def` and `.ghi` (MemberExpression + Identifier).  Our logic for detecting
              //  references and unresolved symbols expects the full `abc.def.ghi` so by excluding MemberExpression's
              //  where the container is also a MemberExpression means that we skip the incomplete `abc.def` 
              if (path.container.type == "MemberExpression") {
                return;
              }
              t.addReference(members, path.node.loc);
            }
          },
  
          FunctionDeclaration: FUNCTION_DECL_OR_EXPR,
          FunctionExpression: FUNCTION_DECL_OR_EXPR,
          ArrowFunctionExpression: FUNCTION_DECL_OR_EXPR,
  
          VariableDeclaration(path) {
            path.node.declarations.forEach(decl => {
              // Simple `var x` form
              if (decl.id.type == "Identifier") {
                let value = null;
                if (decl.init) {
                  if (decl.init.type == "Identifier") {
                    value = decl.init.name;
                  } else if (decl.init.type == "ThisExpression") {
                    value = "this";
                  }
                }
                t.addDeclaration(decl.id.name, value);
                
              // Object destructuring `var {a,b} = {...}`
              } else if (decl.id.type == "ObjectPattern") {
                decl.id.properties.forEach(prop => t.addDeclaration(prop.value.name));
                
              // Array destructuring `var [a,b] = [...]`
              } else if (decl.id.type == "ArrayPattern") {
                decl.id.elements.forEach(prop => t.addDeclaration(prop.name));
              }
            });
          },
  
          CatchClause: {
            enter(path) {
              t.pushScope(null, path.node);
              t.addDeclaration(path.node.param.name);
            },
            exit(path) {
              t.popScope(path.node);
            }
          },
  
          IfStatement: {
            exit(path) {
              let node = path.node;
  
              function apply(value) {
                if (!value) {
                  if (!node.alternate) {
                    path.remove(); 
                  } else {
                    path.replaceWithMultiple(node.alternate.body);
                  }
                } else if (value) {
                  path.replaceWithMultiple(node.consequent.body);
                }
              }
  
              if (types.isLiteral(node.test)) {
                apply(node.test.value);
              } else if (types.isUnaryExpression(node.test, { operator: "!" }) && types.isLiteral(node.test.argument)) {
                let value = node.test.argument.value;
                apply(!value);
              }
            }
          },
  
          LogicalExpression: {
            exit(path) {
              let node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right)) {
                let result = (node.operator == "&&" && node.left.value && node.right.value) ||
                    (node.operator == "||" && (node.left.value || node.right.value));
                path.replaceWith(literalValueToExpression(result));
              }
            }
          },
  
          BinaryExpression: {
            exit(path) {
              let node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right)) {
                let result;
                switch (node.operator) {
                  case "==":
                    result = node.left.value == node.right.value;
                    break;
                  case "===":
                    result = node.left.value === node.right.value;
                    break;
                  case "!=":
                    result = node.left.value != node.right.value;
                    break;
                  case "!==":
                    result = node.left.value !== node.right.value;
                    break;
                }
                if (result !== undefined) {
                  path.replaceWith(types.booleanLiteral(Boolean(result)));
                }
              }
            }
          },
  
          UnaryExpression: {
            exit(path) {
              if (path.node.operator === "!" && types.isLiteral(path.node.argument)) {
                path.replaceWith(types.booleanLiteral(!path.node.argument.value));
              }
            }
          }
        };
  
        return { visitor: VISITOR };
      }
func_code_string: function() {
        var t = this;
        
        function getKeyName(key) {
          var keyName = key.type == "StringLiteral" ? key.value : key.name;
          return keyName;
        }
        
        function enterFunction(path, isClassMember) {
          var node = path.node;
          if (node.id) {
            t.addDeclaration(node.id.name);
          }
          t.pushScope(node.id ? node.id.name : null, node, isClassMember);
          for (var i = 0; i < node.params.length; i++) {
            t.addDeclaration(node.params[i].name);
          }
        }
        
        function exitFunction(path) {
          var node = path.node;
          t.popScope(node);
        }
  
        var FUNCTION_DECL_OR_EXPR = {
          enter: enterFunction,
          exit: exitFunction
        };
  
        function getJsDoc(comment) {
          if (!comment) {
            return null;
          }
          if (!qx.lang.Type.isArray(comment)) {
            comment = [comment];
          }
          var result = {};
          comment.forEach(comment => {
            var tmp = qx.tool.compiler.jsdoc.Parser.parseComment(comment.value);
            for (var key in tmp) {
              var value = tmp[key];
              if (!result[key]) {
                result[key] = value; 
              } else {
                qx.lang.Array.append(result[key], value);
              }
            }
          });
          
          return result;
        }
        
        function makeMeta(sectionName, functionName, node) {
          var meta;
          if (functionName) {
            var section = t.__classMeta[sectionName];
            if (section === undefined) {
              section = t.__classMeta[sectionName] = {};
            }
            meta = section[functionName];
            if (meta === undefined) {
              meta = section[functionName] = {};
            }
          } else {
            meta = t.__classMeta[sectionName];
            if (meta === undefined) {
              meta = t.__classMeta[sectionName] = {};
            }
          }
          meta.location = node.loc;
          
          if (node.leadingComments) {
            var jsdoc = getJsDoc(node.leadingComments);
            if (jsdoc) {
              if (jsdoc["@ignore"]) {
                jsdoc["@ignore"].forEach(function(elem) {
                  t.addIgnore(elem.body);
                });
              }
              meta.jsdoc = jsdoc;
            }
          }
          
          if (sectionName === "members" || sectionName === "statics") {
            if (node.type == "ObjectMethod" || node.value.type === "FunctionExpression") {
              meta.type = "function"; 
            } else {
              meta.type = "variable"; 
            }
            if (functionName.startsWith("__")) {
              meta.access = "private"; 
            } else if (functionName.startsWith("_")) {
              meta.access = "protected";
            } else {
              meta.access = "public"; 
            }
          } 
          return meta;
        }
  
        var needsQxCoreEnvironment = false;
        var COLLECT_CLASS_NAMES_VISITOR = {
          MemberExpression(path) {
            var self = this;
            var str = collapseMemberExpression(path.node);
            t._requireClass(str, { location: path.node.loc });
            var info = t.__analyser.getSymbolType(str);
            if (info && info.symbolType == "class") {
              self.collectedClasses.push(str);
            }
          }
        };
  
        const CODE_ELIMINATION_VISITOR = {
          CallExpression(path) {
            const name = collapseMemberExpression(path.node.callee);
  
            if ((name === "qx.core.Environment.select" || name === "qx.core.Environment.get") &&
                types.isLiteral(path.node.arguments[0])) {
              const arg = path.node.arguments[0];
              const env = t.__analyser.getEnvironment();
              const envValue = env[arg.value];
  
              if (envValue !== undefined) {
                if (name === "qx.core.Environment.get") {
                  path.skip();
                  path.replaceWithSourceString(formatValueAsCode(envValue));
                  return;
                } else if (name === "qx.core.Environment.select") {
                  const subPath = path.get("arguments.1");
                  let option = subPath.node.properties.find(prop => prop.key.value === envValue.toString());
  
                  if (!option) { // try to find default value
                    option = subPath.node.properties.find(prop => prop.key.value === "default");
                  }
  
                  if (option) {
                    // path.skip();
                    path.replaceWith(option.value);
                    return;
                  }
                }
              }
              needsQxCoreEnvironment = path.node.loc;
            }
          },
  
          IfStatement: {
            exit(path) {
              var node = path.node;
              // If it's a literal value, we can eliminate code because we can resolve it now.  This
              //  is really important for anything wrapped in `if (qx.core.Environment.get("qx.debug")) ...`
              //  because the `qx.core.Environment.get` is replaced with a literal value and we need to
              //  use this to remove the unwanted code.
              if (types.isLiteral(node.test)) {
                if (node.test.value) {
                  path.replaceWith(node.consequent);
                } else if (node.alternate) {
                  path.replaceWith(node.alternate);
                } else {
                  path.remove(); 
                }
              }
            }
          },
  
          BinaryExpression: {
            exit(path) {
              var node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right) && "+-*/".indexOf(node.operator) > -1) {
                var result;
                switch (node.operator) {
                  case "+": 
                    result = node.left.value + node.right.value; 
                    break;
                  case "-": 
                    result = node.left.value - node.right.value; 
                    break;
                  case "/": 
                    result = node.left.value / node.right.value; 
                    break;
                  case "*": 
                    result = node.left.value * node.right.value; 
                    break;
                }
                path.skip();
                path.replaceWithSourceString(formatValueAsCode(result));
              }
            }
          }
        };
  
        function collectJson(node) {
          var result;
          if (node.type == "ObjectExpression") {
            result = {};
            node.properties.forEach(function(prop) {
              var key = prop.key.name;
              var value = collectJson(prop.value);
              result[key] = value;
            });
          } else if (node.type == "Literal" || 
              node.type == "StringLiteral" || 
              node.type == "BooleanLiteral" || 
              node.type == "NumericLiteral" || 
              node.type == "NullLiteral") {
            result = node.value;
          } else if (node.type == "ArrayExpression") {
            result = [];
            node.elements.forEach(function(elem) {
              result.push(collectJson(elem));
            });
          } else if (node.type == "Identifier") {
            result = node.name;
          } else if (node.type == "CallExpression" || node.type == "FunctionExpression") {
            result = new Function("[[ Function ]]");
          } else if (node.type == "MemberExpression") {
            result = collapseMemberExpression(node);
          } else if (node.type == "UnaryExpression") {
            if (node.operator == "-") {
              let tmp = collectJson(node.argument);
              if (typeof tmp == "number") {
                return tmp * -1; 
              }
            } else if (node.operator == "!") {
              let tmp = collectJson(node.argument);
              if (typeof tmp == "boolean") {
                return !tmp; 
              }
            }
            result = "[[ UnaryExpression ]]";
          } else if (node.type == "NewExpression") {
            result = "[[ NewExpression ]]";
          } else {
            t.warn("Cannot interpret AST " + node.type + " at " + t.__className + " [" + node.loc.start.line + "," + node.loc.start.column + "]");
            result = null;
          }
          return result;
        }
        
        const ALLOWED_KEYS = {
            "class": {
              "static": {
                "@"           : "object",
                "type"        : "string",    // String
                "statics"     : "object",    // Map
                "environment" : "object",    // Map
                "defer"       : "function"   // Function
              },
              "normal": {
                "@"          : "object",
                "@construct" : "object",
                "@destruct"  : "object",
                "type"       : "string",    // String
                "extend"     : "function",  // Function
                "implement"  : "object",    // Interface[]
                "include"    : "object",    // Mixin[]
                "construct"  : "function",  // Function
                "statics"    : "object",    // Map
                "properties" : "object",    // Map
                "members"    : "object",    // Map
                "environment"   : "object", // Map
                "events"     : "object",    // Map
                "defer"      : "function",  // Function
                "destruct"   : "function"   // Function
              }
            },
            "interface": {
              "extend"     : "object", // Interface | Interface[]
              "statics"    : "object", // Map
              "members"    : "object", // Map
              "properties" : "object", // Map
              "events"     : "object"  // Map
            },
            "mixin": {
              "include"    : "object",   // Mixin | Mixin[]
              "statics"    : "object",   // Map
              "members"    : "object",   // Map
              "properties" : "object",   // Map
              "events"     : "object",   // Map
              "destruct"   : "function", // Function
              "construct"  : "function"  // Function
            },
            "theme": {
              "title"       : "string", // String
              "aliases"     : "object", // Map
              "type"        : "string", // String
              "extend"      : "object", // Theme
              "colors"      : "object", // Map
              "borders"     : "object", // Map
              "decorations" : "object", // Map
              "fonts"       : "object", // Map
              "icons"       : "object", // Map
              "widgets"     : "object", // Map
              "appearances" : "object", // Map
              "meta"        : "object", // Map
              "include"     : "object", // Array
              "patch"       : "object", // Array
              "boot"        : "function" // Function
            }
        };
        
        function isValidExtendClause(prop) {
          if (prop.value.type == "MemberExpression" || prop.value.type == "Identifier") {
            return true;
          }
          if (t.__classMeta.type === "class") {
            return false;
          }
          if (prop.value.type == "ArrayExpression") {
            return prop.value.elements.every(elem => elem.type == "MemberExpression" || elem.type == "Identifier");
          }
          return false;
        }
  
        var CLASS_DEF_VISITOR = {
          Property(path) {
            if (path.parentPath.parentPath != this.classDefPath) {
              path.skip();
              path.traverse(VISITOR);
              return;
            }
            var prop = path.node;
            var keyName = getKeyName(prop.key);
            let allowedKeys = ALLOWED_KEYS[t.__classMeta.type];
            if (t.__classMeta.type === "class")
              allowedKeys = allowedKeys[ t.__classMeta.isStatic ? "static" : "normal" ];
            
            if (allowedKeys[keyName] === undefined)
              t.addMarker("compiler.invalidClassDefinitionEntry", prop.value.loc, t.__classMeta.type, keyName);
  
            if (keyName == "extend") {
              if (!isValidExtendClause(prop)) {
                t.addMarker("compiler.invalidExtendClause", prop.value.loc);
                t.__fatalCompileError = true;
              } else {
                t.__classMeta.superClass = collapseMemberExpression(prop.value);
                t._requireClass(t.__classMeta.superClass, { location: path.node.loc });
              }
            } else if (keyName == "type") {
              var type = collapseMemberExpression(prop.value);
              t.__classMeta.isAbstract = type === "abstract";
              t.__classMeta.isStatic = type === "static";
              t.__classMeta.isSingleton = type === "singleton";
            } else if (keyName == "implement") {
              path.skip();
              path.traverse(COLLECT_CLASS_NAMES_VISITOR, { collectedClasses: t.__classMeta.interfaces });
            } else if (keyName == "include") {
              path.skip();
              path.traverse(COLLECT_CLASS_NAMES_VISITOR, { collectedClasses: t.__classMeta.mixins });
            } else if (keyName == "defer") {
              t.__hasDefer = true;
              t.__inDefer = true;
              makeMeta("defer", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__inDefer = false;
            } else if (keyName == "construct") {
              t.__classMeta.functionName = "$$constructor";
              makeMeta("construct", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__classMeta.functionName = null;
            } else if (keyName == "destruct") {
              t.__classMeta.functionName = "$$destructor";
              makeMeta("destruct", null, path.node);
              path.skip();
              path.traverse(VISITOR);
              t.__classMeta.functionName = null;
            } else if (keyName == "statics") {
              if (this.__className == "qx.core.Environment") {
                var checks = this.__findProperty(prop.value.properties, "_checksMap");
                var props = checks.value.properties;
                for (var j = 0; j < props.length; j++) {
                  this.__analyser.setEnvironmentCheck(props[j].key.value, props[j].value.value);
                }
              }
              var staticsPath = path;
              path.skip();
              path.traverse({
                Property(path) {
                  path.skip();
                  if (path.parentPath.parentPath != staticsPath) {
                    path.traverse(VISITOR);
                    return; 
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("statics", t.__classMeta.functionName, path.node);
                  path.traverse(VISITOR);
                  t.__classMeta.functionName = null;
                },
                FunctionDeclaration: FUNCTION_DECL_OR_EXPR,
                FunctionExpression: FUNCTION_DECL_OR_EXPR,
                ArrowFunctionExpression: FUNCTION_DECL_OR_EXPR
              });
            } else if (keyName == "members") {
              var membersPath = path;
              path.skip();
              path.traverse({
                Property(path) {
                  path.skip();
                  if (path.parentPath.parentPath != membersPath) {
                    path.traverse(VISITOR);
                    return;
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("members", t.__classMeta.functionName, path.node);
                  path.traverse(VISITOR);
                  t.__classMeta.functionName = null;
                },
                ObjectMethod(path) {
                  path.skip();
                  if (path.parentPath.parentPath != membersPath) {
                    path.traverse(VISITOR);
                    return;
                  }
                  t.__classMeta.functionName = getKeyName(path.node.key);
                  makeMeta("members", t.__classMeta.functionName, path.node);
                  enterFunction(path, true);
                  path.traverse(VISITOR);
                  exitFunction(path);
                  t.__classMeta.functionName = null;
                },
                FunctionDeclaration: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                },
                FunctionExpression: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                },
                ArrowFunctionExpression: {
                  enter: path => enterFunction(path, path.parentPath.parentPath == membersPath),
                  exit: exitFunction
                }
              });
            } else if (keyName == "properties") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidProperties", prop.loc||null);
              } else {
                prop.value.properties.forEach(function(pdNode) {
                  var propName = getKeyName(pdNode.key);
                  var meta = makeMeta("properties", propName, pdNode);
                  var data = collectJson(pdNode.value);
                  meta.name = propName;
                  meta.propertyType = "new";
                  [ "refine", "themeable", "event", "inheritable", "apply", "async", "group", "nullable", "init" ]
                    .forEach(name => meta[name] = data[name]);
                  if (data.nullable !== undefined) {
                    meta.allowNull = data.nullable;
                  }
                  if (data.check !== undefined) {
                    if (qx.lang.Type.isArray(data.check)) {
                      meta.possibleValues = data.check; 
                    } else {
                      meta.check = data.check;
                    }
                  }
                  if (data.init !== undefined) {
                    meta.defaultValue = data.init; 
                  }
                });
              }
              path.traverse(VISITOR);
            } else if (keyName == "events") {
              path.skip();
              if (prop.value.properties) {
                prop.value.properties.forEach(function(eventNode) {
                  var eventName = getKeyName(eventNode.key);
                  var meta = makeMeta("events", eventName, eventNode);
                  meta.name = eventName;
                  meta.type = collectJson(eventNode.value);
                });
              }
              path.traverse(VISITOR);
            } else if (keyName == "aliases") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidAliases", prop.loc||null);
              } else {
                var meta = makeMeta("aliases", null, prop);
                meta.aliasMap = {};
                prop.value.properties.forEach(function(aliasNode) {
                  var aliasName = getKeyName(aliasNode.key);
                  var aliasValue = getKeyName(aliasNode.value);
                  meta.aliasMap[aliasName] = aliasValue;
                });
              }
            } else if (keyName == "meta") {
              path.skip();
              if (!prop.value.properties) {
                t.addMarker("class.invalidThemeMeta", prop.loc||null);
              } else {
                let meta = makeMeta("themeMeta", null, prop);
                meta.themeMetaMap = {};
                prop.value.properties.forEach(function(node) {
                  var key = getKeyName(node.key);
                  var value = collapseMemberExpression(node.value);
                  meta.themeMetaMap[key] = value;
                });
              }
              path.traverse(VISITOR);
            } else {
              path.skip();
              path.traverse(VISITOR);
            }
          }
        };
  
        const TYPE = {
            "qx.Class.define": "class",
            "qx.Mixin.define": "mixin",
            "qx.Theme.define": "theme",
            "qx.Interface.define": "interface",
            "qx.Bootstrap.define": "class"
        };
        
        var VISITOR = {
          NewExpression: {
            enter(path) {
              var str = collapseMemberExpression(path.node.callee);
              t._requireClass(str, { usage: "dynamic", location: path.node.loc });
            },
            exit(path) {
              if (t.__analyser.isAddCreatedAt()) {
                var fn = types.memberExpression(types.identifier("qx"), types.identifier("$$createdAt"));
                var tmp = types.callExpression(fn, [ 
                  path.node,
                  types.stringLiteral(t.__className.replace(/\./g, "/") + ".js"),
                  types.numericLiteral(path.node.loc.start.line),
                  types.numericLiteral(path.node.loc.start.column) 
                  ]);
                path.replaceWith(tmp);
                path.skip();
              }
            }
          },
          
          Program: {
            exit(path) {
              let dbClassInfo = t._compileDbClassInfo();
              let copyInfo = {};
              let hasLoadDeps = false;
              if (dbClassInfo.dependsOn) {
                copyInfo.dependsOn = {};
                Object.keys(dbClassInfo.dependsOn).forEach(key => {
                  let tmp = copyInfo.dependsOn[key] = Object.assign({}, dbClassInfo.dependsOn[key]);
                  if (tmp.load) {
                    delete tmp.load;
                    tmp.require = true;
                    hasLoadDeps = true;
                  }
                });
              }
              if (dbClassInfo.environment) {
                copyInfo.environment = dbClassInfo.environment;
                let required = dbClassInfo.environment.required;
                if (required) {
                  for (let key in required) {
                    if (required[key].load) {
                      hasLoadDeps = true;
                      break;
                    }
                  }
                }
              }
              let tmp = types.variableDeclaration("var", [ types.variableDeclarator(types.identifier("$$dbClassInfo"), literalValueToExpression(copyInfo)) ]);
              let inject = [tmp];
              if (hasLoadDeps) {
                tmp = babylon.parse("qx.Bootstrap.executePendingDefers($$dbClassInfo);").program.body;
                inject.push(tmp[0]);
              }
              path.node.body.forEach(node => inject.push(node));
              tmp = babylon.parse(t.__className + ".$$dbClassInfo = $$dbClassInfo;").program.body;
              inject.push(tmp[0]);
              let block = types.blockStatement(inject);
              let rootFn = types.expressionStatement(types.callExpression(types.functionExpression(null, [], block), []));
  
              path.node.body.splice(0, path.node.body.length, rootFn);
            }
          },
  
          CallExpression: {
            enter: function(path) {
              function getStringArg(index) {
                if (index >= path.node.arguments.length) {
                  return null; 
                }
                let arg = path.node.arguments[index];
                if (arg.type == "StringLiteral") {
                  return arg.value; 
                }
                return null;
              }
  
              function addTranslation(entry) {
                let lineNo = path.node.loc.start.line;
                let cur = t.__translations[entry.msgid];
                if (cur) {
                  if (!qx.lang.Type.isArray(cur.lineNo)) {
                    cur.lineNo = [cur.lineNo];
                  }
                  cur.lineNo.push(lineNo);
                } else {
                  entry.lineNo = lineNo;
                  t.__translations.push(entry);
                }
              }
  
              path.traverse(CODE_ELIMINATION_VISITOR, {classDefPath: path});
  
              if (types.isMemberExpression(path.node.callee)) {
                let name = collapseMemberExpression(path.node.callee);
                let thisAlias = null;
                
                // Try and translate aliases for "this"; this is a really simplistic implementation
                //  because static code path analysis is way out of scope for the compiler, so we're
                //  only trying to handle code like:
                //    ```
                //    var that = this, args = arguments;
                //    (function() { that.base(args); })();
                //    ```
                if (path.node.callee.object.type == "Identifier") {
                  let originalAlias = path.node.callee.object.name;
                  let alias = originalAlias;
                  let aliasIsThis = false;
                  for (let scope = t.__scope; scope; scope = scope.parent) {
                    while (!aliasIsThis) {
                      let tmp = scope.vars[alias];
                      if (tmp === "this") {
                        aliasIsThis = true;
                        break;
                      }
                      if (typeof tmp == "string") {
                        alias = tmp;
                      } else {
                        break;
                      }
                    }
                    if (aliasIsThis || scope.isClassMember)
                      break;
                  }
                  if (aliasIsThis) {
                    name = "this" + name.substring(originalAlias.length);
                    thisAlias = originalAlias;
                  }
                }
  
                if (TYPE[name]) {
                  t.__definingType = name.match(/\.([a-zA-Z]+)\./)[1];
                  let node = path.node;
                  let className = node.arguments[0].value;
                  let classDef = node.arguments[1];
    
                  // We can only parse objects (eg qx.data.marshal.Json.__toClass creates objects on the fly that we cannot scan)
                  if (classDef.type != "ObjectExpression") {
                    return;
                  }
                  
                  // Create new meta
                  t.__pushMeta(className);
                  if (needsQxCoreEnvironment) {
                    t._requireClass("qx.core.Environment", { location: needsQxCoreEnvironment, defer: true }); 
                  }
    
                  let meta = makeMeta("clazz", null, path.parent);
                  t.__classMeta.type = TYPE[name];
                  if (t.__definingType == "Class" || t.__definingType == "Bootstrap") {
                    let typeProp = classDef.properties.find(prop => prop.key.type == "Identifier" && prop.key.value == "type");
                    if (typeProp)
                      t.__classMeta.isStatic == typeProp.value.type == "Literal" && typeProp.value.value === "static";
                  }
                  let jsdoc = meta.jsdoc;
                  if (jsdoc) {
                    if (jsdoc["@use"]) {
                      jsdoc["@use"].forEach(function(elem) {
                        t._requireClass(elem.body, { where: "use", load: false, location: path.parent.loc });
                      });
                    }
                    if (jsdoc["@require"]) {
                      jsdoc["@require"].forEach(function(elem) {
                        t._requireClass(elem.body, { where: "require", load: false, location: path.parent.loc });
                      });
                    }
                    if (jsdoc["@optional"]) {
                      jsdoc["@optional"].forEach(function(elem) {
                        t.addIgnore(elem.body);
                      });
                    }
                    if (jsdoc["@ignore"]) {
                      jsdoc["@ignore"].forEach(function(elem) {
                        t.addIgnore(elem.body);
                      });
                    }
                    if (jsdoc["@asset"]) {
                      jsdoc["@asset"].forEach(function(elem) {
                        t._requireAsset(elem.body);
                      });
                    }
                  }
    
                  t._requireClass(name, { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(CLASS_DEF_VISITOR, {classDefPath: path});
                  t.__popMeta(className);
                  
                  // Must be a conventional define 
                  if (path.node.arguments.length != 2 || 
                      path.node.arguments[0].type != "StringLiteral" ||
                      path.node.arguments[1].type != "ObjectExpression") {
                    return;
                  }
                } else if (name == "qx.core.Environment.add") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.add: " + arg.value); 
                    } else {
                      t.__environmentChecks.provided[arg.value] = true;
                    }
                  }
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                } else if (name == "qx.core.Environment.get") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.get: " + arg.value); 
                    } else {
                      t.addEnvCheck(arg.value, path.node.loc);
                    }
                  }
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(VISITOR);
                } else if (name == "qx.core.Environment.select") {
                  let arg = path.node.arguments[0];
                  if (types.isLiteral(arg)) {
                    if (typeof arg.value != "string") {
                      log.error("Invalid argument to qx.core.Environment.select: " + arg.value);
                    } else {
                      t.addEnvCheck(arg.value, path.node.loc);
                    }
                  }
  
                  t._requireClass("qx.core.Environment", { usage: "dynamic", location: path.node.loc });
                  path.skip();
                  path.traverse(VISITOR);
                } else if (name == "this.base") {
                  let expr;
                  
                  // For mixins, there is never a valid time to call this.base() in the constructor; but it is
                  //  OK in methods - but we have to refer to superclass.methodName directly.  For ordinary
                  //  classes, we need to use constructor.methodName.base.
                  if (t.__definingType == "Mixin") {
                    expr = expandMemberExpression(t.__classMeta.className + ".$$members." + t.__classMeta.functionName + ".base.call");
                    // expr = expandMemberExpression("this.constructor.superclass.prototype." + t.__classMeta.functionName + ".call");
                  } else if (t.__classMeta.functionName == "$$constructor") {
                    expr = expandMemberExpression(t.__classMeta.superClass + ".constructor.call");
                  } else {
                    expr = expandMemberExpression(t.__classMeta.className + ".prototype." + t.__classMeta.functionName + ".base.call"); 
                  }
                  if (thisAlias) {
                    path.node.arguments[0] = types.identifier(thisAlias);
                  } else {
                    path.node.arguments[0] = types.thisExpression();
                  }
                  let callExpr = types.callExpression(expr, path.node.arguments);
                  path.replaceWith(callExpr);
                } else if (name == "this.base.apply" || name == "this.base.call") {
                  let methodName = name == "this.base.apply" ? "apply" : "call";
  
                  let expr;
                  if (t.__definingType == "Mixin") {
                    expr = expandMemberExpression("this.constructor.superclass.prototype." + t.__classMeta.functionName + ".call");
                  } else if (t.__classMeta.functionName == "$$constructor") {
                    expr = expandMemberExpression(t.__classMeta.superClass + ".constructor." + methodName); 
                  } else {
                    expr = expandMemberExpression(t.__className + ".prototype." + t.__classMeta.functionName + ".base." + methodName); 
                  }
                  
                  // Original call to this.base.apply would have included arguments in the first element of the array
                  //  here we remove that because it's not needed, specifically changing 
                  //    `this.base.apply(this, args)` to
                  //    `this.base.apply(this, args.slice().shift())`
                  let exprSplice = types.callExpression(types.memberExpression(path.node.arguments[1], types.identifier("slice")), []);
                  let exprUnshift = types.callExpression(types.memberExpression(exprSplice, types.identifier("shift")), []);
                  let callExpr = types.callExpression(expr, [ path.node.arguments[0], exprUnshift ]);
                  path.replaceWith(callExpr);
                } else if (name == "this.self") {
                  let expr = expandMemberExpression(t.__className);
                  path.replaceWith(expr);
                } else if (name == "this.tr" || name == "this.marktr" || name == "qx.locale.Manager.tr" || name == "qx.locale.Manager.marktr") {
                  let arg0 = getStringArg(0);
                  if (!arg0) {
                    t.addMarker("translate.invalidMessageId", path.node.loc, arg0);
                  } else {
                    addTranslation({ msgid: arg0 });
                  }
                } else if (name == "this.trn" || name == "qx.locale.Manager.trn") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  if (!arg0 || !arg1) {
                    t.addMarker("translate.invalidMessageIds", path.node.loc, arg0, arg1);
                  } else {
                    addTranslation({ msgid: arg0, msgid_plural: arg1 });
                  }
                } else if (name == "this.trc" || name == "qx.locale.Manager.trc") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  if (!arg0 || !arg1) {
                    t.addMarker("translate.invalidMessageIds", path.node.loc, arg0, arg1);
                  } else {
                    addTranslation({ msgid: arg1, comment: arg0 });
                  }
                } else if (name == "this.trnc" || name == "qx.locale.Manager.trnc") {
                  let arg0 = getStringArg(0);
                  let arg1 = getStringArg(1);
                  let arg2 = getStringArg(2);
                  if (!arg0 || !arg1 || !arg2) {
                    t.addMarker("translate.invalidMessageIds3", path.node.loc, arg0, arg1, arg2); 
                  } else {
                    addTranslation({ msgid: arg1, msgid_plural: arg2, comment: arg0 }); 
                  }
                } else {
                  let pos = name.lastIndexOf(".");
                  // name can be ".concat" when used with "[].concat"
                  if (pos > 0) {
                    let str = name.substring(0, pos);
                    if (!t.hasDeclaration(str)) {
                      t._requireClass(str, { usage: "dynamic", location: path.node.loc }); 
                    }
                  }
                }
              }
            }
          },
  
          MemberExpression(path) {
            // regular expression or string property (eg "aa".charCodeAt())
            if (path.node.object.type == "Literal") {
              return;
            }
  
            // Handle `[ 123 ].blah()` by visiting
            if (path.node.object.type == "ArrayExpression") {
              return;
            }
            
            // Handle `[ 123 ].blah()` by visiting
            if (path.node.object.type == "MemberExpression" && path.node.object.object.type == "ArrayExpression") {
              return;
            }
            
            let name = collapseMemberExpression(path.node);
            if (name.startsWith("(")) {
              return;
            }
            let members = name.split(".");
  
            // Ignore 'this' references
            if (members[0] === "this") {
              return; 
            }
  
            // Global variable or a local variable?
            if (qx.tool.compiler.ClassFile.GLOBAL_SYMBOLS[members[0]] || t.hasDeclaration(members[0])) {
              return; 
            }
  
            let info = t._requireClass(name, { location: path.node.loc });
            if (!info || !info.className) {
              // The code `abc.def.ghi()` will produce a member expression for both `abc.def` (two Identifier's) 
              //  and another for `abc.def` and `.ghi` (MemberExpression + Identifier).  Our logic for detecting
              //  references and unresolved symbols expects the full `abc.def.ghi` so by excluding MemberExpression's
              //  where the container is also a MemberExpression means that we skip the incomplete `abc.def` 
              if (path.container.type == "MemberExpression") {
                return;
              }
              t.addReference(members, path.node.loc);
            }
          },
  
          FunctionDeclaration: FUNCTION_DECL_OR_EXPR,
          FunctionExpression: FUNCTION_DECL_OR_EXPR,
          ArrowFunctionExpression: FUNCTION_DECL_OR_EXPR,
  
          VariableDeclaration(path) {
            path.node.declarations.forEach(decl => {
              // Simple `var x` form
              if (decl.id.type == "Identifier") {
                let value = null;
                if (decl.init) {
                  if (decl.init.type == "Identifier") {
                    value = decl.init.name;
                  } else if (decl.init.type == "ThisExpression") {
                    value = "this";
                  }
                }
                t.addDeclaration(decl.id.name, value);
                
              // Object destructuring `var {a,b} = {...}`
              } else if (decl.id.type == "ObjectPattern") {
                decl.id.properties.forEach(prop => t.addDeclaration(prop.value.name));
                
              // Array destructuring `var [a,b] = [...]`
              } else if (decl.id.type == "ArrayPattern") {
                decl.id.elements.forEach(prop => t.addDeclaration(prop.name));
              }
            });
          },
  
          CatchClause: {
            enter(path) {
              t.pushScope(null, path.node);
              t.addDeclaration(path.node.param.name);
            },
            exit(path) {
              t.popScope(path.node);
            }
          },
  
          IfStatement: {
            exit(path) {
              let node = path.node;
  
              function apply(value) {
                if (!value) {
                  if (!node.alternate) {
                    path.remove(); 
                  } else {
                    path.replaceWithMultiple(node.alternate.body);
                  }
                } else if (value) {
                  path.replaceWithMultiple(node.consequent.body);
                }
              }
  
              if (types.isLiteral(node.test)) {
                apply(node.test.value);
              } else if (types.isUnaryExpression(node.test, { operator: "!" }) && types.isLiteral(node.test.argument)) {
                let value = node.test.argument.value;
                apply(!value);
              }
            }
          },
  
          LogicalExpression: {
            exit(path) {
              let node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right)) {
                let result = (node.operator == "&&" && node.left.value && node.right.value) ||
                    (node.operator == "||" && (node.left.value || node.right.value));
                path.replaceWith(literalValueToExpression(result));
              }
            }
          },
  
          BinaryExpression: {
            exit(path) {
              let node = path.node;
              if (types.isLiteral(node.left) && types.isLiteral(node.right)) {
                let result;
                switch (node.operator) {
                  case "==":
                    result = node.left.value == node.right.value;
                    break;
                  case "===":
                    result = node.left.value === node.right.value;
                    break;
                  case "!=":
                    result = node.left.value != node.right.value;
                    break;
                  case "!==":
                    result = node.left.value !== node.right.value;
                    break;
                }
                if (result !== undefined) {
                  path.replaceWith(types.booleanLiteral(Boolean(result)));
                }
              }
            }
          },
  
          UnaryExpression: {
            exit(path) {
              if (path.node.operator === "!" && types.isLiteral(path.node.argument)) {
                path.replaceWith(types.booleanLiteral(!path.node.argument.value));
              }
            }
          }
        };
  
        return { visitor: VISITOR };
      }
func_documentation_string: Babel plugin