function collapseToStringLiteral(node, filepath) {
  switch (node.type) {
    case 'Literal': {
      // If your literal wasn't a string, sorry, you're getting a string.
      return String(node.value);
    }

    case 'Identifier': {
      if (node.name === '__dirname' || node.name === 'moduleDir') {
        return path.dirname(filepath);
      } else if (node.name === '__filename') {
        return filepath;
      } else if (node.name === 'LH_ROOT') {
        // Note: hardcoded for LH. Could be be set via inline-fs options instead.
        return LH_ROOT;
      }
      throw new AstError(`unsupported identifier '${node.name}'`, node);
    }

    case 'CallExpression': {
      return evaluateCallExpression(node, filepath);
    }

    case 'BinaryExpression': {
      if (node.operator !== '+') throw new AstError('only string `+` operators supported', node);
      const left = collapseToStringLiteral(node.left, filepath);
      const right = collapseToStringLiteral(node.right, filepath);
      return left + right;
    }

    case 'TemplateLiteral': {
      assert.equal(node.expressions.length + 1, node.quasis.length);
      // Parts alternate between quasis and expressions, starting and ending with a quasi.
      const parts = [];
      for (let i = 0; i < node.expressions.length; i++) {
        parts.push(collapseToStringLiteral(node.quasis[i], filepath));
        parts.push(collapseToStringLiteral(node.expressions[i], filepath));
      }
      parts.push(collapseToStringLiteral(node.quasis[node.quasis.length - 1], filepath));
      return parts.join('');
    }

    case 'TemplateElement': {
      // Keep tsc happy: AST generation should have already errored if invalid escape sequence in template literal.
      if (typeof node.value.cooked !== 'string') {
        throw new AstError('template string syntax error', node);
      }
      return node.value.cooked;
    }
  }

  throw new AstError(`unsupported node: ${node.type}`, node);
}
Recursively walk tree at `node`, collapsing nodes using supported transforms until
only a single string literal remains (or an error is thrown for unsupported nodes).
@param {Node} node ESTree node.
@param {string} filepath The path of the file containing this node.
@return {string}