Skip to content

Instantly share code, notes, and snippets.

@brigand
Last active April 1, 2018 03:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brigand/ff73a46ce65fe36a1e60bc9a36e0803c to your computer and use it in GitHub Desktop.
Save brigand/ff73a46ce65fe36a1e60bc9a36e0803c to your computer and use it in GitHub Desktop.
Ctx (the invisible argument)

Input Code

Looks like mostly normal JS code. It keeps a stack of names for each time .__sub__ctx__ is called. The output of this program is "value: 10, stack: foo->bar->baz".

You could use this technique to e.g. pass a log function around that's specific to an HTTP request, or a database client, or implement a hierarchy of event emitters.

function makeCtx() {
  'disable ctx';
  const ctx = {
    namePath: [],
    __sub__ctx__(name) {
      return { ...this, namePath: this.namePath.concat([name]) };
    }
  };

  return ctx;
}

const delay = ms => new Promise((resolve) => setTimeout(resolve, ms));

async function foo() {
  Promise.all([
    delay(300).then(() => bar()),
    delay(300).then(() => baz()),
  ]);
  quux();
}

function foo(n) {
  bar(n + 2);
}

function bar(m) {
  baz(c(m + 3))
}

function baz(value) {
  console.log(`value: ${value}, stack: ${ctx.namePath.join('->')}`);
}

const c = (x) => x + 4;

async function run() {
  const ctx = makeCtx();
  
  await foo();
}

run();

Babel Plugin

All the magic happens here:

export default function(babel) {
  const { types: t, template } = babel;

  const LOCAL = `ctx`;
  const IMPL = `__ctx__`;
  const FUNC = `__get${IMPL}`;
  const SUB = `__sub${IMPL}`;

  const ignoreIdent = new RegExp(String.raw`\b(${LOCAL}|${IMPL}|${FUNC}|${SUB})\b`, "g");

  const getGlobal = `window`;

  const access = template(`var ${LOCAL} = ${getGlobal}.${FUNC} && ${getGlobal}.${FUNC}.length && ${getGlobal}.${FUNC}.slice(-1)[0]();`);
  const wrapCall = template(`(${getGlobal}.${FUNC}.push(
	  () => ${LOCAL} && ${LOCAL}.${SUB}(NAME)
    ),
	UID = CALL,
	${getGlobal}.${FUNC}.pop(),
	UID
  )`);

  const initCtx = template(`${getGlobal}.${FUNC} = ${getGlobal}.${FUNC} || []`);

  const SKIP = new WeakMap();
  const OUR_NODES = new WeakMap();

  const OUR_CTX_VAR = new WeakMap();

  function maybeGetCallWrapper(path) {
    const callee = path.get("callee");
    let subName = "(unknown)";

    if (path.node.hasOwnProperty("_fromTemplate")) return null;

    const testIgnore = p => p.isIdentifier() && ignoreIdent.test(p.node.name);

    if (path.parentPath.isSequenceExpression()) return null;

    if (callee.isIdentifier()) {
      const name = callee.node.name;
      subName = name;
      if (!path.scope.getBinding(name)) return null;
      if (testIgnore(callee)) return null;
    }
    if (callee.isMemberExpression()) {
      let nameParts = [];
      let root = callee;

      if (testIgnore(root.get("property"))) {
        return null;
      }

      while (true) {
        const next = root.get("object");

        if (testIgnore(root.get("property"))) {
          return null;
        }
        if (next && next.node) {
          root = next;
        } else {
          break;
        }
      }

      const prop = callee.get("property");
      for (const value of [prop, root]) {
        if (value.isIdentifier()) {
          if (ignoreIdent.test(value.node.name)) return;
          subName = prop.node.name;
        }
      }

      if (root && root.isIdentifier()) {
        if (!path.scope.getBinding(root.node.name)) {
          return null;
        }
      }
    }

    const created = wrapCall({ NAME: t.stringLiteral(subName), CALL: path.node, UID: path.scope.generateDeclaredUidIdentifier(`ret`) });
    console.log(created._fromTemplate);
    const node = created.expression;
    OUR_CTX_VAR.set(node, true);
    return node;
  }

  function shouldDisableHere(_path) {
    let path = _path;
    do {
      const dirs = path.get("directives");
      if (!dirs || !dirs.length) continue;

      const match = dirs.some(x => {
        const v = x.get("value");
        if (!v || !v.node) return false;
        if (v.node.value === "disable ctx") return true;
      });
      if (match) return true;
    } while ((path = path.parentPath));

    return false;
  }

  return {
    name: "ast-transform", // not required
    visitor: {
      Program(path) {
        path.node.body.unshift(initCtx());
      },
      Function(path) {
        if (shouldDisableHere(path)) return;

        if (SKIP.has(path.node)) return;

        if (path.node.params.some(x => t.isIdentifier(x) && x.name === "ctx")) {
          return;
        }
        let skip = false;
        path.traverse({
          enter(path2) {
            if (path2.isFunction()) return path2.skip();
            if (path2.isVariableDeclarator()) {
              const name = path2.get("id").node.name;
              if (name === LOCAL) {
                skip = true;
                return path2.stop();
              }
            }
          }
        });

        SKIP.set(path.node, true);

        if (skip) return;

        let body = path.get("body");

        if (!body.isBlock()) {
          body.replaceWith(t.blockStatement([t.returnStatement(body.node)]));
          return;
        }

        const f = access();
        path.node.body.body.unshift(f);
      },
      CallExpression(path) {
        if (shouldDisableHere(path)) return;

        if (SKIP.has(path.node)) return;
        SKIP.set(path.node, true);

        const localCtx = path.scope.getBinding(LOCAL);
        if (localCtx && localCtx.path.node.type === "VariableDeclarator") {
          const ourRoot = localCtx.path.findParent(p => OUR_CTX_VAR.has(p.node));
          if (!ourRoot || !ourRoot.node) {
            //  console.log(`Skipping`, path.node);
            //  return;
          }
        }

        if (!localCtx && !path.findParent(p => p.isFunction())) {
          return;
        }

        const node = maybeGetCallWrapper(path);
        if (!node) return;

        SKIP.set(node, true);

        path.replaceWith(node);
      }
    }
  };
}

Output Code

The combination of the above produces this:

window.__get__ctx__ = window.__get__ctx__ || [];
const delay = ms => {
  return new Promise(resolve => {
    return setTimeout(resolve, ms);
  });
};

async function foo(ctx) {
  var _ret, _ret2, _ret4, _ret5;
  Promise.all([
    (window.__get__ctx__.push(() => {
      return ctx && ctx.__sub__ctx__("then");
    }), _ret = (window.__get__ctx__.push(() => {
      return ctx && ctx.__sub__ctx__("delay");
    }), _ret2 = delay(300), window.__get__ctx__.pop(), _ret2).then(() => {
      var _ret3;

      return window.__get__ctx__.push(() => {
        return ctx && ctx.__sub__ctx__("bar");
      }), _ret3 = bar(), window.__get__ctx__.pop(), _ret3;
    }), window.__get__ctx__.pop(), _ret),
    (window.__get__ctx__.push(() => {
      return ctx && ctx.__sub__ctx__("then");
    }), _ret4 = (window.__get__ctx__.push(() => {
      return ctx && ctx.__sub__ctx__("delay");
    }), _ret5 = delay(300), window.__get__ctx__.pop(), _ret5).then(() => {
      var _ret6;

      return window.__get__ctx__.push(() => {
        return ctx && ctx.__sub__ctx__("baz");
      }), _ret6 = baz(), window.__get__ctx__.pop(), _ret6;
    }), window.__get__ctx__.pop(), _ret4),
  ]);
  quux();
}

function foo(n) {
  var _ret7;
  var ctx = window.__get__ctx__ && window.__get__ctx__.length && window.__get__ctx__.slice(-1)[0]();
  window.__get__ctx__.push(() => {
    return ctx && ctx.__sub__ctx__("bar");
  }), _ret7 = bar(n + 2), window.__get__ctx__.pop(), _ret7;
}

function bar(m) {
  var _ret8, _ret9;
  var ctx = window.__get__ctx__ && window.__get__ctx__.length && window.__get__ctx__.slice(-1)[0]();
  window.__get__ctx__.push(() => {
    return ctx && ctx.__sub__ctx__("baz");
  }), _ret8 = baz((window.__get__ctx__.push(() => {
    return ctx && ctx.__sub__ctx__("c");
  }), _ret9 = c(m + 3), window.__get__ctx__.pop(), _ret9)), window.__get__ctx__.pop(), _ret8
}

function baz(value) {
  var ctx = window.__get__ctx__ && window.__get__ctx__.length && window.__get__ctx__.slice(-1)[0]();
  console.log(`value: ${value}, stack: ${ctx.namePath.join('->')}`);
}

const c = x => {
  return x + 4;
};

async function run() {
  var _ret10, _ret11;
  const ctx = (window.__get__ctx__.push(() => {
    return ctx && ctx.__sub__ctx__("makeCtx");
  }), _ret10 = makeCtx(), window.__get__ctx__.pop(), _ret10);

  await (window.__get__ctx__.push(() => {
    return ctx && ctx.__sub__ctx__("foo");
  }), _ret11 = foo(1), window.__get__ctx__.pop(), _ret11);
}

function makeCtx() {
  'disable ctx';
  const ctx = {
    namePath: [],
    __sub__ctx__(name) {
      return { ...this, namePath: this.namePath.concat([name]) };
    }
  };

  return ctx;
}


run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment