All files / istanbul-lib-instrument/src read-coverage.ts

73.62% Statements 67/91
70.65% Branches 65/92
100% Functions 9/9
73.62% Lines 67/91

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211                                        6x     203x   2x     201x         201x         15423x     15423x       8801x     8801x   165x 165x                                             425x 796x     796x     6032x 6032x 13612x     13612x   13612x         13612x   6032x             1015x 1015x                   46313x                 203x   203x 46313x           203x 203x 203x     46110x 46110x 204x   46110x 261940x 261940x 223985x   37955x 261940x 46110x 1218x       44892x 1x   44892x     203x         203x   203x         203x 2031x 609x   1422x 1422x 1422x       203x       203x   203x 203x       203x 203x   203x 812x     812x 812x     812x     203x 203x   203x    
import type { ESTree } from "rolldown/utils";
 
import { MAGIC_KEY, MAGIC_VALUE } from "./constants";
import { parse } from "./parse";
import type { SourceCoverageData } from "./source-coverage";
 
type Node = ESTree.Node;
 
/** the initial coverage state extracted from already-instrumented code */
export interface InitialCoverage {
  /** the file path the coverage is tracked against */
  path: string;
  /** hash of the coverage data */
  hash: string;
  /** the global coverage variable name */
  gcv: string;
  /** the (empty) coverage data embedded in the instrumented file */
  coverageData: SourceCoverageData;
}
 
const NOT_CONFIDENT = Symbol("not confident");
 
function getAst(code: string | ESTree.Program): ESTree.Program {
  if (typeof code === "object" && typeof code.type === "string") {
    // Assume code is already an ESTree AST.
    return code;
  }
 
  Iif (typeof code !== "string") {
    throw new Error("Code must be a string");
  }
 
  // Parse as leniently as possible
  return parse(code, "instrumented.js", { esModules: true, autoWrap: true, lenient: true }).program;
}
 
// statically evaluates literal expressions
function evaluate(node: Node | null | undefined): unknown {
  Iif (!node) {
    throw NOT_CONFIDENT;
  }
  switch (node.type) {
    case "ParenthesizedExpression":
      return evaluate(node.expression);
    case "Literal":
      Iif ("regex" in node && node.regex) {
        throw NOT_CONFIDENT;
      }
      return node.value;
    case "Identifier":
      Eif (node.name === "undefined") {
        return undefined;
      }
      throw NOT_CONFIDENT;
    case "TemplateLiteral":
      if (node.expressions.length > 0) {
        throw NOT_CONFIDENT;
      }
      return node.quasis[0].value.cooked;
    case "UnaryExpression": {
      const argument = evaluate(node.argument) as any;
      switch (node.operator) {
        case "-":
          return -argument;
        case "+":
          return +argument;
        case "!":
          return !argument;
        case "void":
          return undefined;
      }
      throw NOT_CONFIDENT;
    }
    case "ArrayExpression":
      return node.elements.map((element) => {
        Iif (!element || element.type === "SpreadElement") {
          throw NOT_CONFIDENT;
        }
        return evaluate(element);
      });
    case "ObjectExpression": {
      const result: Record<string, unknown> = {};
      for (const property of node.properties) {
        Iif (property.type !== "Property" || property.computed || property.kind !== "init") {
          throw NOT_CONFIDENT;
        }
        const { key } = property;
        const name =
          key.type === "Identifier"
            ? key.name
            : key.type === "Literal"
              ? String(key.value)
              : evaluate(key as Node);
        result[name as string] = evaluate(property.value);
      }
      return result;
    }
  }
  throw NOT_CONFIDENT;
}
 
function tryEvaluate(node: Node | null | undefined): { confident: boolean; value?: unknown } {
  try {
    return { confident: true, value: evaluate(node) };
  } catch (error) {
    if (error === NOT_CONFIDENT) {
      return { confident: false };
    }
    throw error;
  }
}
 
function isFunction(node: Node): node is ESTree.Function | ESTree.ArrowFunctionExpression {
  return (
    node.type === "FunctionDeclaration" ||
    node.type === "FunctionExpression" ||
    node.type === "ArrowFunctionExpression"
  );
}
 
// finds the function (or program) that contains the coverage schema magic value
function findCoverageScope(ast: ESTree.Program): Node | null {
  const stack: Node[] = [ast];
 
  const walk = (node: Node): boolean => {
    if (
      node.type === "Property" &&
      !node.computed &&
      node.key.type === "Identifier" &&
      node.key.name === MAGIC_KEY
    ) {
      const magicValue = tryEvaluate(node.value);
      Eif (magicValue.confident && magicValue.value === MAGIC_VALUE) {
        return true;
      }
    }
    const isScope = isFunction(node);
    if (isScope) {
      stack.push(node);
    }
    for (const key in node) {
      const child = (node as any)[key];
      if (key === "parent" || !child || typeof child !== "object") {
        continue;
      }
      const children = Array.isArray(child) ? child : [child];
      for (const item of children) {
        if (item && typeof item.type === "string" && walk(item)) {
          return true;
        }
      }
    }
    if (isScope) {
      stack.pop();
    }
    return false;
  };
 
  return walk(ast) ? stack[stack.length - 1] : null;
}
 
// the initializers of variables declared directly in the scope
function getDeclarations(scope: Node): Map<string, Node | null> {
  const declarations = new Map<string, Node | null>();
  const body =
    scope.type === "Program"
      ? scope.body
      : isFunction(scope) && scope.body && scope.body.type === "BlockStatement"
        ? scope.body.body
        : [];
  for (const statement of body) {
    if (statement.type !== "VariableDeclaration") {
      continue;
    }
    for (const declarator of statement.declarations) {
      Eif (declarator.id.type === "Identifier" && !declarations.has(declarator.id.name)) {
        declarations.set(declarator.id.name, declarator.init);
      }
    }
  }
  return declarations;
}
 
export default function readInitialCoverage(code: string | ESTree.Program): InitialCoverage | null {
  const ast = getAst(code);
 
  const covScope = findCoverageScope(ast);
  Iif (!covScope) {
    return null;
  }
 
  const declarations = getDeclarations(covScope);
  const result: Record<string, any> = {};
 
  for (const key of ["path", "hash", "gcv", "coverageData"]) {
    Iif (!declarations.has(key)) {
      return null;
    }
    const value = tryEvaluate(declarations.get(key));
    Iif (!value.confident) {
      return null;
    }
    result[key] = value.value;
  }
 
  delete result.coverageData[MAGIC_KEY];
  delete result.coverageData.hash;
 
  return result as InitialCoverage;
}