lib/qx/tool/compiler/ClassFile.js
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 };
    }
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 };
    }
Babel plugin