Skip to content

Commit

Permalink
feat: add hoisting support for tag var bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanPiercey committed Jul 7, 2021
1 parent e2b0e66 commit 956a80c
Showing 1 changed file with 83 additions and 17 deletions.
100 changes: 83 additions & 17 deletions packages/compiler/src/babel-types/traverse/patch.js
Expand Up @@ -31,30 +31,96 @@ MARKO_ALIAS_TYPES.forEach(aliasName => {
// Adds a one time patch to the scope collector visitors to include
// Marko bindings for params and tag vars.
const originalCrawl = Scope.prototype.crawl;
const patchedVisitors = new WeakSet();

Scope.prototype.crawl = function () {
const path = this.path;
const originalTraverse = path.traverse;
path.traverse = function (visitor) {
Object.assign(
traverse.explode(visitor),
traverse.explode({
MarkoTagBody(body) {
for (const param of body.get("params")) {
body.scope.registerBinding("param", param);
path.traverse = function (visitor, state) {
path.traverse = originalTraverse;

if (!patchedVisitors.has(visitor)) {
patchedVisitors.add(visitor);
Object.assign(
traverse.explode(visitor),
traverse.explode({
MarkoTagBody(body) {
for (const param of body.get("params")) {
body.scope.registerBinding("param", param);
}
},
MarkoTag(tag) {
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)) {
curScope.hoistableTagVars =
curScope.hoistableTagVars ||
(curScope.hoistableTagVars = {});
const existingBinding = curScope.hoistableTagVars[name];

if (existingBinding) {
if (existingBinding !== binding) {
curScope.hoistableTagVars[name] = true;
}
} else {
curScope.hoistableTagVars[name] = binding;
}
}
}
}
}
},
MarkoTag(tag) {
if (tag.has("var")) {
tag.scope.registerBinding("local", tag.get("var"), tag);
})
);
}

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;

while ((curScope = curScope.parent)) {
const hoistableBinding =
curScope.hoistableTagVars && curScope.hoistableTagVars[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.scope) < getScopeDepth(curScope)
) {
movedBindings.set(hoistableBinding, curScope);
}
}
}
})
);
}

path.traverse = originalTraverse;
return originalTraverse.apply(this, arguments);
for (const [binding, scope] of movedBindings) {
binding.scope.moveBindingTo(binding.identifier.name, scope);
}
}
};

Scope.prototype.crawl = originalCrawl;
originalCrawl.apply(this, arguments);
originalCrawl.call(this);
path.traverse = originalTraverse;
};

function getScopeDepth(scope) {
let depth = 0;
let cur = scope;
while ((cur = cur.parent)) depth++;
return depth;
}

0 comments on commit 956a80c

Please sign in to comment.