@marko/compiler
Version:
Marko template to JS compiler.
133 lines (114 loc) • 4.72 kB
JavaScript
;require("../types/patch");
var _traverse = _interopRequireWildcard(require("@babel/traverse"));
var t = _interopRequireWildcard(require("@babel/types"));
var _definitions = require("../types/definitions");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);}
_definitions.MARKO_TYPES.forEach((typeName) => {
const checkKey = `is${typeName}`;
const assertKey = `assert${typeName}`;
const checkFn = t[checkKey];
const assertFn = t[assertKey];
_traverse.NodePath.prototype[checkKey] = function (opts) {
return checkFn(this.node, opts);
};
_traverse.NodePath.prototype[assertKey] = function (opts) {
assertFn(this.node, opts);
};
});
_definitions.MARKO_ALIAS_TYPES.forEach((aliasName) => {
const checkKey = `is${aliasName}`;
const originalProtoCheck = _traverse.NodePath.prototype[checkKey];
_traverse.NodePath.prototype[checkKey] = function (opts) {
return (
t.is(aliasName, this.node, opts) ||
originalProtoCheck.call(this, this.node, opts));
};
});
// Adds a one time patch to the scope collector visitors to include
// Marko bindings for params and tag vars.
const originalCrawl = _traverse.Scope.prototype.crawl;
const patchedVisitors = new WeakSet();
_traverse.Scope.prototype.crawl = function () {
const path = this.path;
const originalTraverse = path.traverse;
path.traverse = function (visitor, state) {
state.hoistableTagVarsByScope = new Map();
path.traverse = originalTraverse;
if (!patchedVisitors.has(visitor)) {
patchedVisitors.add(visitor);
Object.assign(
_traverse.default.explode(visitor),
_traverse.default.explode({
MarkoTagBody(body) {
for (const param of body.get("params")) {
body.scope.registerBinding("param", param);
}
},
MarkoTag(tag, state) {
const tagVar = tag.get("var");
if (tagVar.node) {
tag.scope.registerBinding("local", tagVar, tag);
for (const name in tagVar.getBindingIdentifiers()) {
let curScope = tag.scope;
const binding = curScope.getBinding(name);
while (curScope = curScope.parent) {
const hoistableTagVars =
state.hoistableTagVarsByScope.get(curScope);
if (hoistableTagVars) {
hoistableTagVars[name] = hoistableTagVars[name] ?
true :
binding;
} else {
state.hoistableTagVarsByScope.set(curScope, {
[name]: binding
});
}
}
}
}
}
})
);
}
if (this.type === "Program" && this.node.params?.length) {
this.scope.registerBinding("params", this);
}
this.traverse(visitor, state);
if (state.references.length) {
const movedBindings = new Map();
for (const ref of state.references) {
const { name } = ref.node;
let curScope = ref.scope;
if (curScope.hasBinding(name)) continue;
do {
const hoistableBinding =
state.hoistableTagVarsByScope.get(curScope)?.[name];
if (hoistableBinding) {
if (hoistableBinding === true) {
throw ref.buildCodeFrameError(
"Ambiguous reference, variable was defined in multiple places and was not shadowed."
);
}
const movedBinding = movedBindings.get(hoistableBinding);
if (
!movedBinding ||
getScopeDepth(movedBinding) > getScopeDepth(curScope))
{
movedBindings.set(hoistableBinding, curScope);
}
}
} while (curScope = curScope.parent);
}
for (const [binding, scope] of movedBindings) {
binding.scope.moveBindingTo(binding.identifier.name, scope);
}
}
};
originalCrawl.call(this);
path.traverse = originalTraverse;
};
function getScopeDepth(scope) {
let depth = 0;
let cur = scope;
while (cur = cur.parent) depth++;
return depth;
}