All files cache.ts

100% Statements 30/30
100% Branches 15/15
100% Functions 7/7
100% Lines 29/29

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                                30x 30x   21x 1x         9x 9x   1x     8x       24x   2x                 23x   23x 27x     23x       23x       8x 1x     7x 7x   7x 6x 2x     4x 4x 1x         3x 3x     4x       8x    
import { readFile } from "node:fs/promises";
 
export type MangleCache = Record<string, string>;
 
interface TerserNameCache {
  props: {
    props: Record<string, string>;
  };
  vars: {
    props: Record<string, string>;
  };
}
 
export async function readMangleCache(cachePath: string): Promise<MangleCache> {
  let source: string;
 
  try {
    source = await readFile(cachePath, "utf8");
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
    throw error;
  }
 
  let value: unknown;
 
  try {
    value = JSON.parse(source);
  } catch (error) {
    throw new Error(`Could not parse cache file ${cachePath}`, { cause: error });
  }
 
  return validateMangleCache(value, cachePath);
}
 
export function toTerserNameCache(cache: MangleCache): TerserNameCache {
  return {
    props: {
      props: Object.fromEntries(Object.entries(cache).map(([name, mangled]) => [`$${name}`, mangled])),
    },
    vars: {
      props: {},
    },
  };
}
 
export function fromTerserNameCache(nameCache: TerserNameCache): MangleCache {
  const cache: MangleCache = {};
 
  for (const [name, mangled] of Object.entries(nameCache.props.props)) {
    cache[name.slice(1)] = mangled;
  }
 
  return cache;
}
 
export function serializeMangleCache(cache: MangleCache): string {
  return `${JSON.stringify(cache, null, 2)}\n`;
}
 
function validateMangleCache(value: unknown, cachePath: string): MangleCache {
  if (!isRecord(value)) {
    throw new TypeError(`Cache file ${cachePath} must contain a JSON object`);
  }
 
  const cache: MangleCache = {};
  const owners = new Map<string, string>();
 
  for (const [name, mangled] of Object.entries(value)) {
    if (typeof mangled !== "string" || !mangled) {
      throw new TypeError(`Cache entry ${JSON.stringify(name)} in ${cachePath} must map to a non-empty string`);
    }
 
    const owner = owners.get(mangled);
    if (owner && owner !== name) {
      throw new Error(
        `Cache entries ${JSON.stringify(owner)} and ${JSON.stringify(name)} in ${cachePath} both map to ${JSON.stringify(mangled)}`,
      );
    }
 
    cache[name] = mangled;
    owners.set(mangled, name);
  }
 
  return cache;
}
 
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}