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;
}
|