Ctrl K
ComunidadGuías…

scripts/tap-diagnostics.ts

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

Archivo original ↗Preguntar a KiBot
/** The TAP diagnostic block one failing test emits: pretty JSON or YAML,
 *  parsed into the message and location the compact summary shows. */
import { identity } from "#fp";
import { NO_TAP_DIAGNOSTIC } from "./tap-summary.ts";

export type TapDiagnostic = {
  message?: string;
  severity?: string;
  at?: {
    file?: string;
    line?: number;
    column?: number;
  };
};

const STEP_FAILURE_RE = /^\d+\s+test\s+steps?\s+failed\.$/;

const leadingWhitespaceLength = (line: string): number =>
  line.match(/^\s*/)?.[0].length ?? 0;

const stripCommonIndent = (lines: string[]): string => {
  const indents = lines
    .filter((line) => line.trim().length > 0)
    .map(leadingWhitespaceLength);
  const indent = indents.length > 0 ? Math.min(...indents) : 0;
  return lines.map((line) => line.slice(indent)).join("\n");
};

const parseYamlScalar = (value: string): string => {
  const trimmed = value.trim();
  if (!trimmed) return "";
  if (
    (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
    (trimmed.startsWith("'") && trimmed.endsWith("'"))
  ) {
    try {
      return JSON.parse(trimmed);
    } catch {
      return trimmed.slice(1, -1);
    }
  }
  return trimmed;
};

const readYamlBlockScalar = (
  lines: string[],
  startIndex: number,
  baseIndent: number,
): string => {
  const block: string[] = [];
  for (const line of lines.slice(startIndex + 1)) {
    const indent = leadingWhitespaceLength(line);
    if (line.trim() && indent <= baseIndent && /^\w[\w-]*:/.test(line.trim())) {
      break;
    }
    block.push(line);
  }
  return stripCommonIndent(block).trimEnd();
};

const parseYamlMessage = (lines: string[]): string | undefined => {
  for (let index = 0; index < lines.length; index++) {
    const match = lines[index]?.match(/^(\s*)message:\s*(.*)$/);
    if (!match) continue;
    const value = match[2]?.trim() ?? "";
    const baseIndent = (match[1] ?? "").length;
    return value.startsWith("|") || value.startsWith(">")
      ? readYamlBlockScalar(lines, index, baseIndent)
      : parseYamlScalar(value);
  }
  return;
};

type AtFieldAssign = (
  at: NonNullable<TapDiagnostic["at"]>,
  value: string,
) => void;

/** One `at:` field's setter: read its YAML text through `parse` into the field. */
const atField =
  <K extends keyof NonNullable<TapDiagnostic["at"]>>(
    key: K,
    parse: (raw: string) => NonNullable<TapDiagnostic["at"]>[K],
  ): AtFieldAssign =>
  (at, value) => {
    at[key] = parse(value);
  };

const AT_FIELD_ASSIGNERS: Record<string, AtFieldAssign | undefined> = {
  column: atField("column", Number),
  file: atField("file", identity),
  line: atField("line", Number),
};

const assignAtField = (
  at: NonNullable<TapDiagnostic["at"]>,
  key: string,
  value: string,
): void => {
  AT_FIELD_ASSIGNERS[key]?.(at, value);
};

const parseYamlAt = (lines: string[]): TapDiagnostic["at"] | undefined => {
  const atIndex = lines.findIndex((line) => /^(\s*)at:\s*$/.test(line));
  if (atIndex === -1) return;

  const atIndent = leadingWhitespaceLength(lines[atIndex] ?? "");
  const at: NonNullable<TapDiagnostic["at"]> = {};
  for (const line of lines.slice(atIndex + 1)) {
    if (line.trim() && leadingWhitespaceLength(line) <= atIndent) break;
    const match = line.match(/^\s*(file|line|column):\s*(.*)$/);
    if (!match) continue;
    assignAtField(at, match[1] ?? "", parseYamlScalar(match[2] ?? ""));
  }
  return at;
};

const parseYamlTapDiagnostic = (text: string): TapDiagnostic | undefined => {
  const lines = text.split(/\r?\n/);
  const diagnostic: TapDiagnostic = {};

  const message = parseYamlMessage(lines);
  if (message !== undefined) diagnostic.message = message;

  const at = parseYamlAt(lines);
  if (at !== undefined) diagnostic.at = at;

  return diagnostic.message || diagnostic.at ? diagnostic : undefined;
};

export const parseTapDiagnosticBlock = (lines: string[]): TapDiagnostic => {
  const text = stripCommonIndent(lines).trimEnd();
  const trimmed = text.trim();
  if (!trimmed) {
    return { message: NO_TAP_DIAGNOSTIC };
  }

  try {
    return JSON.parse(trimmed) as TapDiagnostic;
  } catch {
    return parseYamlTapDiagnostic(text) ?? { message: text };
  }
};

export const isStepFailureDiagnostic = (diagnostic: TapDiagnostic): boolean =>
  STEP_FAILURE_RE.test((diagnostic.message ?? "").trim());