Ctrl K
ComunidadGuías…

scripts/cpd.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env -S deno run --allow-all

/**
 * jscpd runner that prints actionable guidance when duplication is found.
 *
 * jscpd's own console report tells you *where* the duplication is, but not what
 * to do about it. This wrapper runs jscpd unchanged and forwards every arg, then
 * — only on a non-zero exit — appends a loud reminder of the project's
 * no-new-duplication policy: extract a helper, or curry, and reserve
 * `jscpd:ignore` for unavoidable boilerplate. The configs skip imports.
 *
 * After every scan it also names the worst duplication in the report: the
 * code that appears in the most places, then the one with the most tokens.
 *
 * Usage: deno run -A scripts/cpd.ts <jscpd args...>
 */

import { join } from "node:path";
import * as v from "valibot";
import { projectRoot } from "#scripts/project-root.ts";
import { ensureJscpd } from "./jscpd/install.ts";
import {
  describeWorstFamily,
  rankCloneFamilies,
  readClonePairs,
} from "./jscpd/worst.ts";
import { bold, red, yellow } from "./precommit/colors.ts";

// `--update` records the current clones into the committed baseline instead
// of checking for new ones, so translate it for jscpd and drop the check.
const args = Deno.args.includes("--update")
  ? [
      ...Deno.args.filter(
        (arg) => arg !== "--update" && arg !== "--fail-on-new-clones",
      ),
      "--update-baseline",
    ]
  : Deno.args;

/** The report that this scan writes, read from the config that `--config`
 * names. Every cpd task passes `--config`. */
const reportPath = async (): Promise<{ config: string; report: string }> => {
  const config = args[args.indexOf("--config") + 1];
  if (!args.includes("--config") || config === undefined) {
    throw new Error("scripts/cpd.ts needs --config <file>");
  }
  const { output } = v.parse(
    v.object({ output: v.string() }),
    JSON.parse(await Deno.readTextFile(config)),
  );
  return { config, report: join(projectRoot, output, "jscpd-report.json") };
};

const { config, report } = await reportPath();
// A crash before jscpd writes its report must not show the last run's report.
await Deno.remove(report).catch((error) => {
  if (!(error instanceof Deno.errors.NotFound)) throw error;
});

const { code } = await new Deno.Command(await ensureJscpd(), {
  args,
}).spawn().status;

const families = rankCloneFamilies(
  readClonePairs(JSON.parse(await Deno.readTextFile(report))),
);
console.error(
  yellow(describeWorstFamily(config, families, projectRoot).join("\n")),
);

if (code !== 0) {
  console.error(`
${bold(red("━━━ jscpd: duplicated code found — no new duplication is allowed ━━━"))}

Do NOT reach for ${bold("/* jscpd:ignore */")} to silence this. Fix the duplication:

  ${bold("1. Write a helper.")} This is the answer in ~99.999% of cases. If an
     obvious shared function jumps out, extract it and call it from both sites.

  ${bold("2. No obvious helper? Curry.")} Lift the parts that differ into
     arguments of a function that returns the specialised version, then call it
     at each site. ${yellow("Then review your work before committing")} — zoom out
     one step further. The first small curry you reach for is often not the
     best one; a larger, more holistic curry across the call sites is very
     frequently far better.

  ${bold("3. jscpd:ignore is the LAST resort.")} The configs already skip import
     and re-export statements, so imports never need a marker. Keep a marker
     for the rare unavoidable scrap of boilerplate/infrastructure we have no
     control over. Anywhere else an ignore tag is a code smell, not a fix.

  ${yellow("Never restructure the code just to break the token match")} (a
     config object, a rename, reordering) while two parallel implementations
     stay standing. That hides the signal and keeps the duplication. The
     question is "how do I make these two things one thing", not "how do I
     make jscpd stop flagging this".
`);
}

Deno.exit(code);