@embroider/macros
Version:
Standardized build-time macros for ember apps.
425 lines • 16.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Evaluator = void 0;
exports.assertNotArray = assertNotArray;
exports.assertArray = assertArray;
exports.buildLiterals = buildLiterals;
const dependency_satisfies_1 = __importDefault(require("./dependency-satisfies"));
const module_exists_1 = __importDefault(require("./module-exists"));
const get_config_1 = __importDefault(require("./get-config"));
const assert_never_1 = __importDefault(require("assert-never"));
const binops = {
'||': function (a, b) {
return a || b;
},
'&&': function (a, b) {
return a && b;
},
'|': function (a, b) {
return a | b;
},
'^': function (a, b) {
return a ^ b;
},
'&': function (a, b) {
return a & b;
},
'==': function (a, b) {
// eslint-disable-next-line eqeqeq
return a == b;
},
'!=': function (a, b) {
// eslint-disable-next-line eqeqeq
return a != b;
},
'===': function (a, b) {
return a === b;
},
'!==': function (a, b) {
return a !== b;
},
'<': function (a, b) {
return a < b;
},
'>': function (a, b) {
return a > b;
},
'<=': function (a, b) {
return a <= b;
},
'>=': function (a, b) {
return a >= b;
},
'<<': function (a, b) {
return a << b;
},
'>>': function (a, b) {
return a >> b;
},
'>>>': function (a, b) {
return a >>> b;
},
'+': function (a, b) {
return a + b;
},
'-': function (a, b) {
return a - b;
},
'*': function (a, b) {
return a * b;
},
'/': function (a, b) {
return a / b;
},
'%': function (a, b) {
return a % b;
},
'??': function (a, b) {
if (a === null || a === undefined) {
return b;
}
return a;
},
};
const unops = {
'-': function (a) {
return -a;
},
'+': function (a) {
return +a;
},
'~': function (a) {
return ~a;
},
'!': function (a) {
return !a;
},
void: function () {
return undefined;
},
};
class Evaluator {
constructor(env = {}) {
this.knownPaths = env.knownPaths || new Map();
this.locals = env.locals || {};
this.state = env.state;
}
evaluateMember(path, optionalChain) {
let propertyPath = assertNotArray(path.get('property'));
let property;
if (path.node.computed) {
property = this.evaluate(propertyPath);
}
else {
property = this.evaluateKey(propertyPath);
}
if (property.confident) {
let objectPath = path.get('object');
let object = this.evaluate(objectPath);
if (object.confident) {
let confidentObject = object;
let confidentProperty = property;
return {
confident: true,
get value() {
if (optionalChain) {
return confidentObject.value != null
? confidentObject.value[confidentProperty.value]
: confidentObject.value;
}
else {
return confidentObject.value[confidentProperty.value];
}
},
hasRuntimeImplementation: confidentObject.hasRuntimeImplementation || confidentProperty.hasRuntimeImplementation,
};
}
}
return { confident: false };
}
evaluateKey(path) {
let first = this.evaluate(path);
if (first.confident) {
return first;
}
if (path.isIdentifier()) {
return { confident: true, value: path.node.name, hasRuntimeImplementation: false };
}
return { confident: false };
}
evaluate(path) {
let known = this.knownPaths.get(path);
if (known) {
return known;
}
let result = this.realEvaluate(path);
return result;
}
realEvaluate(path) {
if (path.isMemberExpression()) {
return this.evaluateMember(path, false);
}
// Here we are glossing over the lack of a real OptionalMemberExpression type
// in our @babel/traverse typings.
if (path.node.type === 'OptionalMemberExpression') {
return this.evaluateMember(path, true);
}
if (path.isStringLiteral()) {
return { confident: true, value: path.node.value, hasRuntimeImplementation: false };
}
if (path.isNumericLiteral()) {
return { confident: true, value: path.node.value, hasRuntimeImplementation: false };
}
if (path.isBooleanLiteral()) {
return { confident: true, value: path.node.value, hasRuntimeImplementation: false };
}
if (path.isNullLiteral()) {
return { confident: true, value: null, hasRuntimeImplementation: false };
}
if (path.isIdentifier() && path.node.name === 'undefined') {
return { confident: true, value: undefined, hasRuntimeImplementation: false };
}
if (path.isObjectExpression()) {
let props = assertArray(path.get('properties')).map(p => {
if (p.isSpreadElement()) {
return [{ confident: false }, { confident: false }];
}
let key = assertNotArray(p.get('key'));
let keyEvalValue = this.evaluateKey(key);
let value = assertNotArray(p.get('value'));
let valueEvalValue = this.evaluate(value);
return [keyEvalValue, valueEvalValue];
});
for (let [k, v] of props) {
if (!k.confident || !v.confident) {
return { confident: false };
}
}
let confidentProps = props;
return {
confident: true,
get value() {
let result = {};
for (let [k, v] of confidentProps) {
result[k.value] = v.value;
}
return result;
},
hasRuntimeImplementation: confidentProps.some(([key, value]) => key.hasRuntimeImplementation || value.hasRuntimeImplementation),
};
}
if (path.isArrayExpression()) {
let elements = path.get('elements').map(element => {
return this.evaluate(element);
});
if (elements.every(element => element.confident)) {
let confidentElements = elements;
return {
confident: true,
get value() {
return confidentElements.map(element => element.value);
},
hasRuntimeImplementation: confidentElements.some(el => el.hasRuntimeImplementation),
};
}
}
if (path.isAssignmentExpression()) {
let leftPath = path.get('left');
if (leftPath.isIdentifier()) {
let rightPath = path.get('right');
let right = this.evaluate(rightPath);
if (right.confident) {
this.locals[leftPath.node.name] = right.value;
return right;
}
}
}
if (path.isCallExpression()) {
let result = this.maybeEvaluateRuntimeConfig(path);
if (result.confident) {
return result;
}
result = this.evaluateMacroCall(path);
if (result.confident) {
return result;
}
}
if (path.isLogicalExpression() || path.isBinaryExpression()) {
let operator = path.node.operator;
if (binops[operator]) {
let leftOperand = this.evaluate(path.get('left'));
if (leftOperand.confident) {
let rightOperand = this.evaluate(path.get('right'));
if (leftOperand.confident && rightOperand.confident) {
let value = binops[operator](leftOperand.value, rightOperand.value);
return {
confident: true,
value,
hasRuntimeImplementation: leftOperand.hasRuntimeImplementation || rightOperand.hasRuntimeImplementation,
};
}
}
}
return { confident: false };
}
if (path.isConditionalExpression()) {
let test = this.evaluate(path.get('test'));
if (test.confident) {
let result = test.value ? this.evaluate(path.get('consequent')) : this.evaluate(path.get('alternate'));
if (result.confident) {
return {
confident: true,
value: result.value,
hasRuntimeImplementation: test.hasRuntimeImplementation || result.hasRuntimeImplementation,
};
}
}
}
if (path.isUnaryExpression()) {
let operator = path.node.operator;
if (unops[operator]) {
let operand = this.evaluate(path.get('argument'));
if (operand.confident) {
let value = unops[operator](operand.value);
return { confident: true, value, hasRuntimeImplementation: operand.hasRuntimeImplementation };
}
}
return { confident: false };
}
if (path.isIdentifier()) {
if (!this.locals.hasOwnProperty(path.node.name)) {
return { confident: false };
}
return { confident: true, value: this.locals[path.node.name], hasRuntimeImplementation: false };
}
if (path.isSequenceExpression()) {
let expressions = path.get('expressions');
let lastExpression = expressions[expressions.length - 1];
if (lastExpression) {
// The value of a sequence expression is only the value of the last
// expression it contains, all the rest are ignored.
return this.evaluate(lastExpression);
}
}
return { confident: false };
}
// This handles the presence of our runtime-mode getConfig functions. We want
// to designate them as { confident: true }, because it's important that we
// give feedback even in runtime-mode if the developer is trying to pass
// non-static arguments somewhere they're not supposed to. But we don't
// actually want to calculate their value here because that has been deferred
// to runtime. That's why we've made `value` lazy. It lets us check the
// confidence without actually forcing the value.
maybeEvaluateRuntimeConfig(path) {
if (!this.state) {
return { confident: false };
}
let callee = path.get('callee');
if (callee.isIdentifier()) {
// Does the identifier refer to our runtime config?
if (callee.referencesImport(this.state.pathToOurAddon('runtime'), 'config')) {
return {
confident: true,
get value() {
throw new Error(`bug in @embroider/macros: didn't expect to need to evaluate this value`);
},
hasRuntimeImplementation: true,
};
}
}
return { confident: false };
}
evaluateMacroCall(path) {
if (!this.state) {
return { confident: false };
}
let callee = path.get('callee');
if (callee.referencesImport('@embroider/macros', 'dependencySatisfies')) {
return { confident: true, value: (0, dependency_satisfies_1.default)(path, this.state), hasRuntimeImplementation: false };
}
if (callee.referencesImport('@embroider/macros', 'moduleExists')) {
return { confident: true, value: (0, module_exists_1.default)(path, this.state), hasRuntimeImplementation: false };
}
if (callee.referencesImport('@embroider/macros', 'getConfig')) {
return { confident: true, value: (0, get_config_1.default)(path, this.state, 'package'), hasRuntimeImplementation: false };
}
if (callee.referencesImport('@embroider/macros', 'getOwnConfig')) {
return { confident: true, value: (0, get_config_1.default)(path, this.state, 'own'), hasRuntimeImplementation: false };
}
if (callee.referencesImport('@embroider/macros', 'getGlobalConfig')) {
// Check for getGlobalConfig().fastboot.isRunning, which is the only pattern in use where config actually needs to have a runtime implementation.
// For compatibility reasons we will continue to support that. All other cases of macro configs are static now.
let maybeFastbootMemberExpression = path.parentPath;
if (maybeFastbootMemberExpression.isMemberExpression() ||
maybeFastbootMemberExpression.isOptionalMemberExpression()) {
let maybeFastbootProperty = maybeFastbootMemberExpression.isMemberExpression()
? maybeFastbootMemberExpression.get('property')
: maybeFastbootMemberExpression.isOptionalMemberExpression()
? maybeFastbootMemberExpression.get('property')
: (0, assert_never_1.default)(maybeFastbootMemberExpression);
if (maybeFastbootProperty.isIdentifier() && maybeFastbootProperty.node.name === 'fastboot') {
return {
confident: true,
value: (0, get_config_1.default)(path, this.state, 'getGlobalConfig'),
hasRuntimeImplementation: true,
};
}
}
return {
confident: true,
value: (0, get_config_1.default)(path, this.state, 'getGlobalConfig'),
hasRuntimeImplementation: false,
};
}
if (callee.referencesImport('@embroider/macros', 'isDevelopingApp')) {
return {
confident: true,
value: Boolean(this.state.opts.appPackageRoot &&
this.state.opts.isDevelopingPackageRoots.includes(this.state.opts.appPackageRoot)),
hasRuntimeImplementation: false,
};
}
if (callee.referencesImport('@embroider/macros', 'isDevelopingThisPackage')) {
return {
confident: true,
value: this.state.opts.isDevelopingPackageRoots.includes(this.state.originalOwningPackage().root),
hasRuntimeImplementation: false,
};
}
if (callee.referencesImport('@embroider/macros', 'isTesting')) {
let g = (0, get_config_1.default)(path, this.state, 'getGlobalConfig');
let e = g && g['@embroider/macros'];
let value = Boolean(e && e.isTesting);
return { confident: true, value, hasRuntimeImplementation: true };
}
return { confident: false };
}
}
exports.Evaluator = Evaluator;
// these next two functions are here because the type definitions we're using
// don't seem to know exactly which NodePath properties are arrays and which
// aren't.
function assertNotArray(input) {
if (Array.isArray(input)) {
throw new Error(`bug: not supposed to be an array`);
}
return input;
}
function assertArray(input) {
if (!Array.isArray(input)) {
throw new Error(`bug: supposed to be an array`);
}
return input;
}
function buildLiterals(value, babelContext) {
if (typeof value === 'undefined') {
return babelContext.types.identifier('undefined');
}
let statement = babelContext.parse(`a(${JSON.stringify(value)})`, { configFile: false });
let expression = statement.program.body[0].expression;
return expression.arguments[0];
}
//# sourceMappingURL=evaluate-json.js.map