@qooxdoo/framework
Version:
The JS Framework for Coders
1,645 lines (1,514 loc) • 104 kB
JavaScript
/* ************************************************************************
*
* qooxdoo-compiler - node.js based replacement for the Qooxdoo python
* toolchain
*
* https://github.com/qooxdoo/qooxdoo
*
* Copyright:
* 2011-2017 Zenesis Limited, http://www.zenesis.com
*
* License:
* MIT: https://opensource.org/licenses/MIT
*
* This software is provided under the same licensing terms as Qooxdoo,
* please see the LICENSE file in the Qooxdoo project's top-level directory
* for details.
*
* Authors:
* * John Spackman (john.spackman@zenesis.com, @johnspackman)
*
* *********************************************************************** */
/* eslint-disable padded-blocks */
var fs = require("fs");
var babelCore = require("@babel/core");
var types = require("@babel/types");
var babylon = require("@babel/parser");
var async = require("async");
var log = qx.tool.utils.LogManager.createLog("analyser");
/**
* Helper method that collapses the MemberExpression into a string
* @param node
* @returns {string}
*/
function collapseMemberExpression(node) {
var done = false;
function doCollapse(node) {
if (node.type == "ThisExpression") {
return "this";
}
if (node.type == "Super") {
return "super";
}
if (node.type == "Identifier") {
return node.name;
}
if (node.type == "ArrayExpression") {
var result = [];
node.elements.forEach(element => result.push(doCollapse(element)));
return result;
}
if (node.type != "MemberExpression") {
return "(" + node.type + ")";
}
if (types.isIdentifier(node.object)) {
let str = node.object.name;
if (node.property.name) {
str += "." + node.property.name;
} else {
done = true;
}
return str;
}
var str;
if (node.object.type == "ArrayExpression") {
str = "[]";
} else {
str = doCollapse(node.object);
}
if (done) {
return str;
}
// `computed` is set if the expression is a subscript, eg `abc[def]`
if (node.computed) {
done = true;
} else if (node.property.name) {
str += "." + node.property.name;
} else {
done = true;
}
return str;
}
return doCollapse(node);
}
function isCollapsibleLiteral(node) {
let nodeType = node.type;
return (
nodeType === "Literal" ||
nodeType === "StringLiteral" ||
nodeType === "NumericLiteral" ||
nodeType === "BooleanLiteral" ||
nodeType === "BigIntLiteral"
);
}
/**
* Helper method that expands a dotted string into MemberExpression
* @param str
* @returns {*}
*/
function expandMemberExpression(str) {
var segs = str.split(".");
var expr = types.memberExpression(
types.identifier(segs[0]),
types.identifier(segs[1])
);
for (var i = 2; i < segs.length; i++) {
expr = types.memberExpression(expr, types.identifier(segs[i]));
}
return expr;
}
function literalValueToExpression(value) {
if (value === null || value === undefined) {
return types.nullLiteral();
}
if (typeof value == "boolean") {
return types.booleanLiteral(value);
}
if (typeof value == "number") {
return types.numericLiteral(value);
}
if (typeof value == "string") {
return types.stringLiteral(value);
}
if (qx.lang.Type.isRegExp(value)) {
return types.regExpLiteral(value.toString());
}
if (qx.lang.Type.isDate(value)) {
return types.stringLiteral(value.toString());
}
if (qx.lang.Type.isArray(value)) {
var arr = [];
value.forEach(function (item) {
arr.push(literalValueToExpression(item));
});
return types.arrayExpression(arr);
}
if (typeof value != "object") {
log.error("Cannot serialise value " + value + " into AST");
return types.nullLiteral();
}
var properties = [];
for (var key in value) {
var expr = literalValueToExpression(value[key]);
var prop = types.objectProperty(types.stringLiteral(key), expr);
properties.push(prop);
}
return types.objectExpression(properties);
}
function formatValueAsCode(value) {
if (value === undefined) {
return "undefined";
}
if (value === null) {
return "null";
}
if (typeof value === "string") {
return JSON.stringify(value);
}
if (typeof value === "object" && value instanceof Date) {
return "new Date(" + value.getTime() + ")";
}
if (qx.lang.Type.isArray(value)) {
return "[" + value.map(formatValueAsCode).join(", ") + "]";
}
return value.toString();
}
/**
* A class file is parsed and anaysed into an instance of ClassFile; it is
* connected to the Analyser that found the class so that dependencies can be
* identified.
*/
qx.Class.define("qx.tool.compiler.ClassFile", {
extend: qx.core.Object,
/**
* Constructor
*
* @param analyser {Analyser} the Analyser that found the file
* @param className {String} the full name of the class
* @param library {Library} the Library the class belongs to (note that the class name is
* not always enough to identify the library, eg private source files such as qxWeb.js)
*/
construct(analyser, className, library) {
super();
this.__analyser = analyser;
this.__className = className;
this.__metaStack = [];
this.__metaDefinitions = {};
this.__library = library;
this.__sourceFilename = analyser.getClassSourcePath(library, className);
this.__requiredClasses = {};
this.__environmentChecks = {
provided: {},
required: {}
};
this.__requiredAssets = [];
this.__requiredFonts = {};
this.__translations = [];
this.__markers = [];
this.__haveMarkersFor = {};
this.__scope = {
parent: null,
vars: {},
unresolved: {}
};
this.__externals = [];
this.__commonjsModules = {};
this.__taskQueueDrains = [];
this.__taskQueue = async.queue(function (task, cb) {
task(cb);
});
this.__taskQueue.drain = this._onTaskQueueDrain;
this.__taskQueue.error = err => {
qx.tool.compiler.Console.error(err.stack || err);
};
analyser.getIgnores().forEach(s => this.addIgnore(s));
this.__globalSymbols = {};
this.__privates = {};
this.__blockedPrivates = {};
this.__privateMangling = analyser.getManglePrivates();
const CF = qx.tool.compiler.ClassFile;
const addSymbols = arr =>
arr.forEach(s => (this.__globalSymbols[s] = true));
if (analyser.getGlobalSymbols().length) {
addSymbols(analyser.getGlobalSymbols());
} else {
addSymbols(CF.QX_GLOBALS);
addSymbols(CF.COMMON_GLOBALS);
addSymbols(CF.BROWSER_GLOBALS);
}
},
members: {
__analyser: null,
__className: null,
__numClassesDefined: 0,
__library: null,
__requiredClasses: null,
__environmentChecks: null,
__requiredAssets: null,
/** @type{Map<String,Object>} list of fonts indexed by name; the value is an object with `name` and `loc` */
__requiredFonts: null,
__translateMessageIds: null,
__scope: null,
__inDefer: false,
__inConstruct: false,
__taskQueue: null,
__taskQueueDrains: null,
__markers: null,
__haveMarkersFor: null,
__classMeta: null,
__metaStack: null,
__metaDefinitions: null,
__fatalCompileError: false,
__translations: null,
__dbClassInfo: null,
__hasDefer: null,
__definingType: null,
__sourceFilename: null,
__taskQueueDrain: null,
__globalSymbols: null,
__privates: null,
__blockedPrivates: null,
__externals: null,
__commonjsModules: null,
_onTaskQueueDrain() {
var cbs = this.__taskQueueDrain;
this.__taskQueueDrain = [];
cbs.forEach(function (cb) {
cb();
});
},
_waitForTaskQueueDrain(cb) {
if (this.__taskQueue.length() == 0) {
cb();
} else {
this.__taskQueueDrains.push(cb);
}
},
_queueTask(cb) {
this.__taskQueue.push(cb);
},
/**
* Returns the absolute path to the class file
* @returns {string}
*/
getSourcePath() {
return this.__sourceFilename;
},
/**
* Returns the path to the rewritten class file
* @returns {string}
*/
getOutputPath() {
return this.__analyser.getClassOutputPath(this.__className);
},
/**
* Loads the source, transpiles and analyses the code, storing the result in outputPath
*
* @param callback
* {Function} callback for when the load is completed
*/
load(callback) {
var t = this;
var className = this.__className;
t.__fatalCompileError = false;
t.__numClassesDefined = 0;
fs.readFile(
this.getSourcePath(),
{ encoding: "utf-8" },
function (err, src) {
if (err) {
callback(err);
return;
}
var result;
try {
let babelConfig = t.__analyser.getBabelConfig() || {};
let options = qx.lang.Object.clone(babelConfig.options || {}, true);
options.modules = false;
let extraPreset = [
{
plugins: []
}
];
if (babelConfig.plugins) {
for (let key in babelConfig.plugins) {
if (babelConfig.plugins[key] === true) {
extraPreset[0].plugins.push(require.resolve(key));
} else if (babelConfig.plugins[key]) {
extraPreset[0].plugins.push([
require.resolve(key),
babelConfig.plugins[key]
]);
}
}
}
let myPlugins = t._babelClassPlugins();
var config = {
babelrc: false,
sourceFileName: t.getSourcePath(),
filename: t.getSourcePath(),
sourceMaps: true,
presets: [
[
{
plugins: [myPlugins.CodeElimination]
}
],
[
{
plugins: [myPlugins.Compiler]
}
],
[require.resolve("@babel/preset-env"), options],
[require.resolve("@babel/preset-typescript")],
[
require.resolve("@babel/preset-react"),
qx.tool.compiler.ClassFile.JSX_OPTIONS
]
],
generatorOpts: {
compact: false
},
parserOpts: {
allowSuperOutsideMethod: true,
sourceType: "script"
},
passPerPreset: true
};
if (extraPreset[0].plugins.length) {
config.presets.push(extraPreset);
}
if (this.__privateMangling == "unreadable") {
config.blacklist = ["spec.functionName"];
}
result = babelCore.transform(src, config);
} catch (ex) {
qx.tool.compiler.Console.log(ex);
t.addMarker("compiler.syntaxError", ex.loc, ex.message);
t.__fatalCompileError = true;
t._compileDbClassInfo();
callback();
return;
}
if (!t.__numClassesDefined) {
t.addMarker("compiler.missingClassDef");
t.__fatalCompileError = true;
t._compileDbClassInfo();
callback();
return;
}
if (!t.__metaDefinitions[className]) {
t.addMarker(
"compiler.wrongClassName",
null,
className,
Object.keys(t.__metaDefinitions).join(", ")
);
t._compileDbClassInfo();
}
var pos = className.lastIndexOf(".");
var name = pos > -1 ? className.substring(pos + 1) : className;
var outputPath = t.getOutputPath();
qx.tool.utils.Utils.mkParentPath(outputPath, function (err) {
if (err) {
callback(err);
return;
}
let mappingUrl = name + ".js.map";
if (
qx.lang.Array.contains(
t.__analyser.getApplicationTypes(),
"browser"
)
) {
mappingUrl += "?dt=" + new Date().getTime();
}
fs.writeFile(
outputPath,
result.code + "\n\n//# sourceMappingURL=" + mappingUrl,
{ encoding: "utf-8" },
function (err) {
if (err) {
callback(err);
return;
}
fs.writeFile(
outputPath + ".map",
JSON.stringify(result.map, null, 2),
{ encoding: "utf-8" },
function (err) {
if (err) {
callback(err);
return;
}
t._waitForTaskQueueDrain(function () {
callback();
});
}
);
}
);
});
}
);
},
/**
* Writes the data for the database; updates the record, which may have been previously
* used (so needs to be zero'd out)
* @param dbClassInfo {Map}
*/
writeDbInfo(dbClassInfo) {
delete dbClassInfo.unresolved;
delete dbClassInfo.dependsOn;
delete dbClassInfo.assets;
delete dbClassInfo.translations;
delete dbClassInfo.markers;
delete dbClassInfo.fatalCompileError;
delete dbClassInfo.commonjsModules;
for (var key in this.__dbClassInfo) {
dbClassInfo[key] = this.__dbClassInfo[key];
}
},
/**
* Compiles the DbInfo POJO to be stored in the database about this class
* */
_compileDbClassInfo() {
var t = this;
var dbClassInfo = (this.__dbClassInfo = {});
// Collect the dependencies on other classes
var deps = this.getRequiredClasses();
if (t.__usesJsx) {
let JSX = qx.tool.compiler.ClassFile.JSX_OPTIONS;
let classname = JSX.pragma;
let pos = classname.lastIndexOf(".");
classname = classname.substring(0, pos);
if (!deps[classname]) {
deps[classname] = {};
}
}
for (var name in deps) {
var dep = deps[name];
if (!dep.ignore) {
if (!dbClassInfo.dependsOn) {
dbClassInfo.dependsOn = {};
}
dbClassInfo.dependsOn[name] = dep;
}
}
function fixAnnos(section) {
if (!section) {
return;
}
Object.keys(section).forEach(name => {
if (name[0] == "@") {
var value = section[name];
delete section[name];
name = name.substring(1);
var meta = section[name];
if (meta) {
if (!meta.annotations) {
meta.annotations = [];
}
meta.annotations.push(value);
}
}
});
}
var meta = this.getOuterClassMeta();
if (meta) {
fixAnnos(meta.events);
fixAnnos(meta.members);
fixAnnos(meta.statics);
if (meta.properties && meta.members) {
Object.keys(meta.properties).forEach(name => {
let pm = meta.properties[name];
if (pm.apply) {
let fm = meta.members[pm.apply];
if (fm) {
if (!fm.applyFor) {
fm.applyFor = [];
}
fm.applyFor.push(name);
}
}
});
}
// Class heirararchy
dbClassInfo.extends = meta.superClass;
dbClassInfo.include = meta.mixins.slice(0);
dbClassInfo.implement = meta.interfaces.slice(0);
}
// Environment Checks
if (
Object.keys(this.__environmentChecks.provided).length ||
Object.keys(this.__environmentChecks.required).length
) {
dbClassInfo.environment = { provided: [], required: {} };
for (let key in this.__environmentChecks.provided) {
dbClassInfo.environment.provided.push(key);
}
for (let key in this.__environmentChecks.required) {
dbClassInfo.environment.required[key] =
this.__environmentChecks.required[key];
}
}
// Save whether the class has a defer method
dbClassInfo.hasDefer = this.hasDefer();
// Unresolved symbols
dbClassInfo.unresolved = [];
for (let name in this.__scope.unresolved) {
let item = this.__scope.unresolved[name];
// item is undefined if it has already been removed from the list
if (item === undefined) {
continue;
}
// One of multiple classes defined in this file
if (this.__metaDefinitions[name]) {
continue;
}
var info = t.__analyser.getSymbolType(name);
if (info && info.className) {
t._requireClass(info.className, {
load: item.load,
defer: item.defer
});
} else if (info && info.symbolType == "package") {
t.deleteReference(name);
} else {
dbClassInfo.unresolved.push(item);
for (var j = 0; j < item.locations.length; j++) {
t.addMarker(
"symbol.unresolved#" + name,
item.locations[j].start,
name
);
}
}
}
if (!dbClassInfo.unresolved.length) {
delete dbClassInfo.unresolved;
}
// Assets
var assets = this.getAssets();
if (assets.length) {
dbClassInfo.assets = assets;
}
// Fonts
var fontNames = Object.keys(this.__requiredFonts);
if (fontNames.length) {
dbClassInfo.fonts = fontNames;
for (let fontName in this.__requiredFonts) {
if (!this.__analyser.getFont(fontName)) {
t.addMarker(
"fonts.unresolved#" + fontName,
this.__requiredFonts[fontName].loc.start,
fontName
);
}
}
}
if (meta) {
if (meta.aliases) {
dbClassInfo.aliases = {};
for (let name in meta.aliases.aliasMap) {
dbClassInfo.aliases[name] = meta.aliases.aliasMap[name];
}
}
if (meta.themeMeta) {
dbClassInfo.themeMeta = {};
for (let name in meta.themeMeta.themeMetaMap) {
dbClassInfo.themeMeta[name] = meta.themeMeta.themeMetaMap[name];
}
}
}
if (this.__externals.length) {
dbClassInfo.externals = this.__externals;
}
// Translation
if (this.__translations.length) {
dbClassInfo.translations = this.__translations.slice(0);
}
// Markers
if (this.__markers.length) {
dbClassInfo.markers = qx.lang.Array.clone(this.__markers);
}
// Errors
if (this.__fatalCompileError) {
dbClassInfo.fatalCompileError = true;
}
// CommonJS modules
if (Object.keys(this.__commonjsModules).length > 0) {
dbClassInfo.commonjsModules = {};
for (let moduleName in this.__commonjsModules) {
dbClassInfo.commonjsModules[moduleName] = [
...this.__commonjsModules[moduleName]
];
}
}
return dbClassInfo;
},
/**
* Returns the loaded meta data
*/
getOuterClassMeta() {
let src = this.__metaDefinitions[this.__className] || null;
if (!src) {
return src;
}
let dest = {};
Object.keys(src)
.filter(key => key[0] != "_")
.forEach(key => (dest[key] = src[key]));
return dest;
},
/**
* Babel plugin
*/
_babelClassPlugins() {
var t = this;
function getKeyName(key) {
var keyName = key.type == "StringLiteral" ? key.value : key.name;
return keyName;
}
function checkNodeJsDocDirectives(node) {
var jsdoc = getJsDoc(node.leadingComments);
if (jsdoc) {
checkJsDocDirectives(jsdoc, node.loc);
}
return jsdoc;
}
function checkJsDocDirectives(jsdoc, loc) {
if (!jsdoc) {
return jsdoc;
}
if (jsdoc["@use"]) {
jsdoc["@use"].forEach(function (elem) {
t._requireClass(elem.body, {
where: "use",
load: false,
location: loc
});
});
}
if (jsdoc["@require"]) {
jsdoc["@require"].forEach(function (elem) {
t._requireClass(elem.body, {
where: "require",
load: false,
location: 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["@external"]) {
jsdoc["@external"].forEach(function (elem) {
t.addExternal(elem.body);
t._requireAsset(elem.body);
});
}
if (jsdoc["@asset"]) {
jsdoc["@asset"].forEach(function (elem) {
t._requireAsset(elem.body);
});
}
if (jsdoc["@usefont"]) {
jsdoc["@usefont"].forEach(function (elem) {
t._requireFont(elem.body, loc);
});
}
return jsdoc;
}
function enterFunction(path, node, idNode) {
node = node || path.node;
idNode = idNode || node.id || null;
let isClassMember =
t.__classMeta &&
t.__classMeta._topLevel &&
t.__classMeta._topLevel.keyName == "members" &&
path.parentPath.parentPath.parentPath == t.__classMeta._topLevel.path;
if (idNode) {
t.addDeclaration(idNode.name);
}
t.pushScope(idNode ? idNode.name : null, node, isClassMember);
function addDecl(param) {
if (param.type == "AssignmentPattern") {
addDecl(param.left);
} else if (param.type == "RestElement") {
t.addDeclaration(param.argument.name);
} else if (param.type == "Identifier") {
t.addDeclaration(param.name);
} else if (param.type == "ArrayPattern") {
param.elements.forEach(elem => addDecl(elem));
} else if (param.type == "ObjectPattern") {
param.properties.forEach(prop => addDecl(prop.value));
} else {
t.addMarker("testForFunctionParameterType", node.loc, param.type);
}
}
node.params.forEach(param => {
addDecl(param);
});
checkNodeJsDocDirectives(node);
if (
node.key?.name === "_createQxObjectImpl" &&
t.__classMeta.type !== "interface"
) {
if (t.__classMeta.type === "mixin") {
if (t.__classMeta.className === "qx.core.MObjectId") {
return;
}
qx.tool.compiler.Console.print(
"qx.tool.compiler.compiler.mixinQxObjectImpl",
t.__classMeta.className
);
}
const injectCode = `{
let object = qx.core.MObjectId.handleObjects(${
t.__classMeta.className ?? "this.constructor"
}, this, ...arguments);
if (object !== undefined) {
return object;
}
}`;
const injectBlockAst = babylon.parse(injectCode, {
errorRecovery: true
}).program.body[0];
const bodyLines = node.body.body;
node.body.body = [injectBlockAst].concat(bodyLines);
path.skip();
}
}
function exitFunction(path, node) {
node = node || path.node;
t.popScope(node);
}
var FUNCTION_DECL_OR_EXPR = {
enter: path => enterFunction(path),
exit: path => exitFunction(path)
};
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) {
let jsdoc = checkNodeJsDocDirectives(node);
if (jsdoc) {
meta.jsdoc = jsdoc;
}
}
if (sectionName === "members" || sectionName === "statics") {
if (
node.type == "ObjectMethod" ||
node.value.type === "FunctionExpression" ||
node.value.type === "MemberExpression"
) {
meta.type = "function";
} else {
meta.type = "variable";
}
if (node.async) {
meta.async = true;
}
if (functionName.startsWith("__")) {
meta.access = "private";
} else if (functionName.startsWith("_")) {
meta.access = "protected";
} else {
meta.access = "public";
}
}
return meta;
}
var es6ClassDeclarations = 0;
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 = {
ClassBody: {
enter(path) {
es6ClassDeclarations++;
},
exit(path) {
es6ClassDeclarations--;
}
},
CallExpression(path) {
const name = collapseMemberExpression(path.node.callee);
const env = t.__analyser.getEnvironment();
if (
env["qx.environment.allowRuntimeMutations"] !== true &&
(name === "qx.core.Environment.select" ||
name === "qx.core.Environment.get") &&
types.isLiteral(path.node.arguments[0])
) {
const arg = path.node.arguments[0];
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) {
let 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();
}
}
}
},
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 (
isCollapsibleLiteral(node.left) &&
isCollapsibleLiteral(node.right)
) {
if ("+-*/".indexOf(node.operator) > -1) {
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;
}
path.skip();
path.replaceWithSourceString(formatValueAsCode(result));
} else {
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));
}
}
}
};
function collectJson(node, isProperties, jsonPath) {
var result;
if (node.type == "ObjectExpression") {
result = {};
let nextJsonPath = jsonPath ? jsonPath + "." : "";
node.properties.forEach(function (prop) {
var key = prop.key.name;
if (prop.type == "ObjectMethod") {
result[key] = "[[ ObjectMethod Function ]]";
} else {
var value = collectJson(
prop.value,
isProperties,
nextJsonPath + key
);
result[key] = value;
}
});
} else if (
node.type == "Literal" ||
node.type == "StringLiteral" ||
node.type == "BooleanLiteral" ||
node.type == "NumericLiteral" ||
node.type == "NullLiteral"
) {
if (typeof node.value == "string") {
let isIdentifier = false;
if (
isProperties &&
(jsonPath === "apply" ||
jsonPath === "transform" ||
jsonPath === "isEqual")
) {
isIdentifier = true;
}
node.value = t.encodePrivate(node.value, isIdentifier, node.loc);
}
result = node.value;
} else if (node.type == "ArrayExpression") {
result = [];
node.elements.forEach(function (elem) {
result.push(collectJson(elem, isProperties));
});
} else if (node.type == "Identifier") {
node.name = t.encodePrivate(node.name, true, node.loc);
result = node.name;
} else if (
node.type == "CallExpression" ||
node.type == "FunctionExpression" ||
node.type == "ArrowFunctionExpression"
) {
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, isProperties);
if (typeof tmp == "number") {
return tmp * -1;
}
} else if (node.operator == "!") {
let tmp = collectJson(node.argument, isProperties);
if (typeof tmp == "boolean") {
return !tmp;
}
}
result = "[[ UnaryExpression ]]";
} else if (
node.type == "NewExpression" ||
node.type == "BinaryExpression"
) {
result = "[[ " + node.type + " ]]";
} else {
t.warn(
"Cannot interpret AST " +
node.type +
" at " +
t.__className +
(node.loc
? " [" + 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
objects: "object" // Map
}
},
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
objects: "object" // Map
},
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" ||
prop.value.type == "NullLiteral"
) {
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;
}
const FUNCTION_NAMES = {
construct: "$$constructor",
destruct: "$$destructor",
defer: null
};
function ensureCreateQxObjectImpl(membersPropertyPath) {
// if we are in a class with no top-level properties, don't bother creating _createQxObjectImpl
if (
!membersPropertyPath.parent.properties.some(
p => p.key?.name === "objects"
)
) {
return;
}
const membersPropertyNode = membersPropertyPath.node;
const memberNames = membersPropertyNode?.value?.properties?.map(
it => it.key.name
);
if (!memberNames) {
t.addMarker(
"compiler.membersNotAnObject",
membersPropertyPath.loc,
t.__classMeta.type
);
return;
}
if (memberNames.includes("_createQxObjectImpl")) {
return;
}
const functionBody = `{
return super._createQxObjectImpl(...arguments);
}`;
const functionBlock = babylon.parse(functionBody, {
errorRecovery: true
}).program.body[0];
membersPropertyNode.value.properties.push(
types.objectMethod(
"method",
types.identifier("_createQxObjectImpl"),
[],
functionBlock
)
);
}
function checkValidTopLevel(path) {
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.loc,
t.__classMeta.type,
keyName
);
}
}
function handleTopLevelMethods(path, keyName, functionNode) {
if (keyName == "defer") {
t.__hasDefer = true;
t.__inDefer = true;
}
var isSpecialFunctionName =
Object.keys(FUNCTION_NAMES).includes(keyName);
t.__classMeta.functionName = isSpecialFunctionName
? FUNCTION_NAMES[keyName]
: keyName;
if (isSpecialFunctionName) {
makeMeta(keyName, null, functionNode);
}
enterFunction(path, functionNode);
path.traverse(VISITOR);
exitFunction(path, functionNode);
path.skip();
t.__classMeta.functionName = null;
}
var CLASS_DEF_VISITOR = {
ClassBody: {
enter(path) {
es6ClassDeclarations++;
},
exit(path) {
es6ClassDeclarations--;
}
},
ObjectMethod(path) {
let functionNode = path.node;
if (path.parentPath.parentPath != this.classDefPath) {
path.skip();
enterFunction(path, functionNode);
path.traverse(VISITOR);
exitFunction(path, functionNode);
return;
}
var keyName = getKeyName(path.node.key);
checkValidTopLevel(path);
handleTopLevelMethods(path, keyName, functionNode);
},
ObjectProperty(path) {
var prop = path.node;
if (path.parentPath.parentPath != this.classDefPath) {
path.skip();
path.traverse(VISITOR);
return;
}
var keyName = getKeyName(prop.key);
checkValidTopLevel(path);
var isSpecialFunctionName =
Object.keys(FUNCTION_NAMES).includes(keyName);
if (isSpecialFunctionName) {
let val = path.node.value;
val.leadingComments = (path.node.leadingComments || []).concat(
val.leadingComments || []
);
handleTopLevelMethods(path, keyName, val);
return;
}
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 = prop.value.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" || keyName == "patch") {
path.skip();
path.traverse(COLLECT_CLASS_NAMES_VISITOR, {
collectedClasses: t.__classMeta.mixins
});
} else if (
keyName == "members" ||
keyName == "statics" ||
keyName == "objects" ||
keyName == "@"
) {
t.__classMeta._topLevel = {
path,
keyName
};
path.skip();
if (keyName === "members" && t.__classMeta.type === "class") {
ensureCreateQxObjectImpl(path);
}
path.traverse(VISITOR);
t.__classMeta._topLevel = null;
} 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, true);
meta.name = propName;
meta.propertyType = "new";
[
"refine",
"themeable",
"event",
"inheritable",
"apply",
"async",
"group",
"nullable",
"init",
"transform"
].forEach(name => (meta[name] = data[name]));
if (data.nullable !== undefined) {
meta.allowNull = data.nullable;
}
if (data.check !== undefined) {
let checks;
if (qx.lang.Type.isArray(data.check)) {
checks = meta.possibleValues = data.check;
} else {
meta.check = data.check;
checks = [data.check];
}
checks.forEach(check => {
if (!qx.tool.compiler.ClassFile.SYSTEM_CHECKS[check]) {
let symbolData = t.__analyser.getSymbolType(check);
if (symbolData?.symbolType == "class") {
t._requireClass(check, {
load: false,
usage: "dynamic",
location: path.node.loc
});
}
}
});
}
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);
}
}
};
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 ? path.node.loc.start.line : 0
),
types.numericLiteral(
path.node.loc ? path.node.loc.start.column : 0
),
types.booleanLiteral(t.__analyser.isVerboseCreatedAt())
]);
path.replaceWith(tmp);
path.skip();
}
}
},
ExpressionStatement: {
enter: path => {
checkNodeJsDocDirectives(path.node);
},
exit: path => {
checkNodeJsDocDirectives(path.node);
}
},
EmptyStatement: path => {
checkNodeJsDocDirectives(path.node);
},
JSXElement(path) {
t.__usesJsx = true;
},
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)
)
]);