import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, test } from "node:test"; import { checkFile } from "./check.mjs"; const dirs = []; const cli = fileURLToPath(new URL("./check.mjs", import.meta.url)); const result = (stdout = "", status = 0, stderr = "") => ({ stdout, stderr, status }); const diagnostic = (severity) => ({ type: "diagnostic", severity, message: "fixture diagnostic", span: { start: 0, end: 1 }, }); function fixture(source = "1 + 2\n") { const dir = mkdtempSync(join(tmpdir(), "nu-check-")); dirs.push(dir); const file = join(dir, "script with spaces.nu"); writeFileSync(file, source); return file; } function fakeNu(response, calls = []) { return (command, args, options) => { calls.push({ command, args, options }); return args.includes("--version") ? result("0.115.1\n") : response; }; } afterEach(() => { for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); }); test("uses bounded config-free Nu diagnostics, never shell interpolation", () => { const file = fixture(); const calls = []; const report = checkFile(file, fakeNu(result(), calls)); assert.equal(report.ok, true); assert.equal(report.version, "0.115.1"); assert.equal(calls.length, 2); assert.deepEqual(calls[1].args, ["--no-config-file", "--ide-check", "100", file]); assert.equal(calls[1].command, "nu"); assert.equal(calls[1].options.cwd, dirname(file)); assert.equal(calls[1].options.shell, false); assert.equal(calls[1].options.timeout, 30_000); assert.equal(calls[1].options.maxBuffer, 1_048_576); }); test("Error diagnostics fail even when Nu exits zero", () => { const error = diagnostic("Error"); const report = checkFile(fixture(), fakeNu(result(JSON.stringify(error)))); assert.equal(report.ok, false); assert.deepEqual(report.diagnostics, [error]); }); test("warnings are reported but are not errors", () => { const warning = diagnostic("Warning"); const report = checkFile(fixture(), fakeNu(result(`${JSON.stringify(warning)}\r\n`))); assert.equal(report.ok, true); assert.deepEqual(report.diagnostics, [warning]); }); for (const output of ["not json", "{}", '{"type":"diagnostic"}', JSON.stringify(diagnostic("Unknown"))]) { test(`fails closed on unsupported diagnostic output: ${output}`, () => { assert.equal(checkFile(fixture(), fakeNu(result(output))).ok, false); }); } for (const response of [ result("", 2, "PRIVATE-STDERR"), result("", 0, "PRIVATE-STDERR"), { error: { code: "ENOENT", message: "PRIVATE-ERROR" }, status: null }, { error: { code: "ETIMEDOUT" }, signal: "SIGTERM", status: null }, { error: { code: "ENOBUFS" }, status: null }, ]) { test(`fails closed on process failure or stderr: ${JSON.stringify(response)}`, () => { const report = checkFile(fixture(), fakeNu(response)); assert.equal(report.ok, false); assert.ok(report.error); assert.doesNotMatch(JSON.stringify(report), /PRIVATE-/); }); } test("version probe failure prevents checking", () => { let calls = 0; const report = checkFile(fixture(), () => { calls++; return result("bad-version"); }); assert.equal(report.ok, false); assert.equal(calls, 1); }); test("missing target fails before invoking Nu", () => { const report = checkFile(join(dirname(fixture()), "missing.nu"), () => assert.fail("spawned Nu")); assert.equal(report.ok, false); }); test("real Nu: valid script passes without executing its body", () => { const file = fixture('"ran" | save sentinel.txt\n1 + 2\n'); const report = checkFile(file); assert.equal(report.ok, true, JSON.stringify(report)); assert.match(report.version, /^\d+\.\d+\.\d+/); assert.equal(existsSync(join(dirname(file), "sentinel.txt")), false); }); test("real Nu: invalid script fails although raw IDE mode exits zero", () => { const file = fixture('1 + "wrong"\n'); const raw = spawnSync("nu", ["--no-config-file", "--ide-check", "100", file], { encoding: "utf8" }); assert.equal(raw.status, 0); const report = checkFile(file); assert.equal(report.ok, false); assert.ok(report.diagnostics.some((entry) => entry.severity === "Error")); }); test("real Nu: passing IDE check does not prove deprecation-free code", () => { const file = fixture('"a" | str upcase\n'); const report = checkFile(file); assert.equal(report.ok, true, JSON.stringify(report)); const runtime = spawnSync("nu", ["--no-config-file", file], { encoding: "utf8" }); assert.equal(runtime.status, 0); assert.match(runtime.stderr, /deprecated/); }); test("CLI emits JSON and returns failure for invalid source and usage", () => { for (const args of [[fixture('1 + "wrong"\n')], []]) { const proc = spawnSync(process.execPath, [cli, ...args], { encoding: "utf8" }); assert.notEqual(proc.status, 0); assert.equal(JSON.parse(proc.stdout).ok, false); } });