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 };
    }
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
Babel plugin