import { describe, expect, test } from "bun:test"; import { describeFile, describeFolder, firstSentence } from "../src/describe/describe.ts"; describe("descriptions by kind (§21.5)", () => { test("Zig and Rust take the leading //! lines, not /// or //", () => { const zig = '//! Tree rendering.\n//!\n//! Second paragraph.\n\nconst std = @import("std");\n'; expect(describeFile("src/tree.zig", zig)).toBe("Tree rendering.\n\nSecond paragraph."); const rust = "// Copyright 2026 June\n\n//! Parses things.\nuse std::io;\n"; expect(describeFile("src/lib.rs", rust)).toBe("Parses things."); expect(describeFile("a.zig", "// main.zig — entry point\nconst std = 1;\n")).toBeNull(); expect(describeFile("a.rs", "/// Doc of a function.\nfn main() {}\n")).toBeNull(); }); test("V, Go, C, C++, Swift, Kotlin, Java, JS and TS take the leading // block", () => { for (const ext of "v go c h cpp swift kt java js ts".split(" ")) { expect(describeFile(`x.${ext}`, "// Reads the bucket.\n// Two lines.\n\ncode();\n")).toBe( "Reads the bucket.\nTwo lines.", ); } expect(describeFile("cli.v", "//! Command line parsing.\n\nmodule cli\n")).toBe( "Command line parsing.", ); }); test("the /** */ block counts, with its stars and delimiters removed", () => { const ts = "/**\n * Markup that is `safe`.\n *\n * More.\n */\nexport class Html {}\n"; expect(describeFile("html.ts", ts)).toBe("Markup that is `safe`.\n\nMore."); expect(describeFile("x.java", "/** One line. */\nclass X {}\n")).toBe("One line."); expect(describeFile("x.ts", "/** Describes x. */ export const x = 1;\n")).toBe("Describes x."); expect(describeFile("x.ts", "/**\n * Two lines.\n */ export const x = 1;\n")).toBe( "Two lines.", ); }); test("a comment after the first code does not count", () => { expect(describeFile("x.kt", "val x = 1\n/** Too late. */\nobject X\n")).toBeNull(); expect(describeFile("x.go", "package main\n// Too late.\n")).toBeNull(); expect(describeFile("x.ts", "import x from 'y';\n// Too late.\n")).toBeNull(); }); test("Kotlin, Java and Swift pass over package and import lines first", () => { const kt = "// SPDX-License-Identifier: MIT\npackage com.peach\n\nimport a.B\n\n/**\n * Idle auto-lock timer.\n */\nclass T\n"; expect(describeFile("T.kt", kt)).toBe("Idle auto-lock timer."); expect(describeFile("X.java", "package a;\nimport b.C;\n// Reads C.\nclass X {}\n")).toBe( "Reads C.", ); expect(describeFile("X.swift", "import Foundation\n\n/// A view.\nstruct X {}\n")).toBe( "A view.", ); }); test("Go build constraints are not a description", () => { const go = "//go:build linux\n\n// Package tty talks to terminals.\npackage tty\n"; expect(describeFile("tty.go", go)).toBe("Package tty talks to terminals."); }); test("Python takes the module docstring, after comments and a shebang", () => { const py = '#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n"""Sync tool.\n\n Indented body.\n"""\nimport os\n'; expect(describeFile("tool.py", py)).toBe("Sync tool.\n\nIndented body."); expect(describeFile("one.py", "'''One line.'''\n")).toBe("One line."); expect(describeFile("x.py", 'import os\n"""Not a docstring."""\n')).toBeNull(); expect(describeFile("x.py", "# Just a comment.\nimport os\n")).toBeNull(); }); test("Shell, Nix, TOML and YAML take the leading # lines after any shebang", () => { for (const ext of "sh nix toml yaml".split(" ")) { expect(describeFile(`x.${ext}`, "# Builds the thing.\n# Quickly.\nvalue = 1\n")).toBe( "Builds the thing.\nQuickly.", ); } expect(describeFile("install.sh", "#!/bin/sh\n# Installs kepr.\nset -e\n")).toBe( "Installs kepr.", ); expect(describeFile("bin/run", "#!/usr/bin/env bash\n\n# Runs it.\nexec x\n")).toBe("Runs it."); expect(describeFile("install.sh", "#!/bin/sh\nset -e\n# Too late.\n")).toBeNull(); expect(describeFile("bin/tool", "#!/usr/bin/env python3\n# Not shell.\n")).toBeNull(); }); test("Markdown takes the first paragraph, not a heading", () => { const md = "# Guide\n\nRead **this** first,\nthen the rest.\n\nMore.\n"; expect(describeFile("docs/guide.md", md)).toBe("Read **this** first,\nthen the rest."); expect(describeFile("empty.md", "# Only a heading\n")).toBeNull(); }); test("no comment, an unknown kind or a binary file means no description", () => { expect(describeFile("x.ts", "export const one = 1;\n")).toBeNull(); expect(describeFile("x.ts", "")).toBeNull(); expect(describeFile("logo.png", "// looks like a comment\n")).toBeNull(); expect(describeFile("x.c", "//\n//\nint x;\n")).toBeNull(); }); }); describe("license headers", () => { test("the first block is skipped when it carries SPDX, copyright or licence wording", () => { const headers = [ "// SPDX-License-Identifier: MIT\n", "/*\n * Copyright (c) 2026 June\n */\n", "// Licensed under the Apache License, Version 2.0.\n// See LICENSE.\n", "/* © 2026 Asha. All rights reserved. */\n", ]; for (const header of headers) { expect(describeFile("x.ts", `${header}\n// Real description.\ncode();\n`)).toBe( "Real description.", ); expect(describeFile("x.ts", `${header}\ncode();\n`)).toBeNull(); } expect(describeFile("x.nix", "# SPDX-License-Identifier: MIT\n\n# The flake.\n{ }\n")).toBe( "The flake.", ); }); test("the word copyright alone is not a license header", () => { expect(describeFile("x.ts", "// Renders copyright notices for every package.\ncode();\n")).toBe( "Renders copyright notices for every package.", ); expect(describeFile("x.ts", "// Copyright © Asha.\n\n// Real.\ncode();\n")).toBe("Real."); }); test("only the first block, so a description mentioning a licence survives", () => { expect(describeFile("x.ts", "// Checks each repo's license file.\ncode();\n")).toBe( "Checks each repo's license file.", ); const two = "// Copyright 2026.\n\n// Copyright notices, rendered.\ncode();\n"; expect(describeFile("x.ts", two)).toBe("Copyright notices, rendered."); }); }); describe("real sources, verbatim headers", () => { const lines = (...text: string[]) => `${text.join("\n")}\n`; test("Koh 3, V with //! lines", () => { const v = lines( "//! AWS Signature Version 4, the part S3 needs.", "//!", "//! Signing is a fixed recipe: canonicalise the request, hash it, derive a key", "//! from the secret for that day and region, and sign the string-to-sign. It is", "", "module remote", ); expect(firstSentence(describeFile("src/remote/sigv4.v", v) ?? "")).toBe( "AWS Signature Version 4, the part S3 needs.", ); }); test("Peach, Kotlin with package and imports before the KDoc", () => { const kt = lines( "package com.peachpasswords.vault", "", "import androidx.annotation.VisibleForTesting", "import kotlinx.coroutines.CoroutineScope", "import javax.inject.Singleton", "", "/**", " * Idle auto-lock timer. The vault locks [VaultSettings.autoLockMinutes] after the", ' * last user interaction; a null (or non-positive) setting means "never". [poke]', " */", "@Singleton", ); expect(firstSentence(describeFile("vault/VaultLockTimer.kt", kt) ?? "")).toBe( "Idle auto-lock timer.", ); }); test("nina, Nix and shell", () => { const nix = lines( "# Standalone NixOS module for nina — no flake inputs needed.", "# Generated by the installer into ~/nina.nix. Also importable directly.", "#", "# Usage (in configuration.nix):", "{ config, lib, pkgs, ... }:", ); expect(firstSentence(describeFile("nixos-module.nix", nix) ?? "")).toBe( "Standalone NixOS module for nina — no flake inputs needed.", ); const sh = lines( "#!/usr/bin/env sh", "# G-01/G-02/G-03 generations validation, run inside the VM. Creates distinct", "# system generations by changing the config between applies (a tiny /etc file via", "set -u", ); expect(firstSentence(describeFile("test/vm/tests/gen.sh", sh) ?? "")).toBe( "G-01/G-02/G-03 generations validation, run inside the VM.", ); }); }); describe("folders", () => { const child = (name: string, text: string) => ({ name, description: describeFile(name, text) }); test("a README's leading HTML comment or raw
block is not its first paragraph", () => { const readme = '\n\n
LOGO
\n\n# Title\n\nThe intro.\n'; expect(describeFile("README.md", readme)).toBe("The intro."); }); test("a README's first paragraph wins over the module file", () => { const children = [ child("mod.zig", "//! From the module.\n"), child("README.md", "# Pages\n\nFrom the readme.\n"), ]; expect(describeFolder(children)).toBe("From the readme."); }); test("else the module file's header, else nothing", () => { expect(describeFolder([child("mod.rs", "//! Rust module.\n")])).toBe("Rust module."); expect(describeFolder([child("index.ts", "// TS module.\nx();\n")])).toBe("TS module."); expect(describeFolder([child("__init__.py", '"""Py package."""\n')])).toBe("Py package."); expect( describeFolder([child("README.md", "# Only a title\n"), child("mod.zig", "//! Mod.\n")]), ).toBe("Mod."); expect(describeFolder([child("other.ts", "// Not a module file.\n")])).toBeNull(); }); }); describe("the one-line description", () => { test("joins hard-wrapped lines and stops at the first sentence", () => { expect(firstSentence("Reads the\nbucket. Then more.\n\nNext paragraph.")).toBe( "Reads the bucket.", ); }); test("ignores full stops inside code, versions and common abbreviations", () => { expect(firstSentence("Reads `koh.build`, e.g. v3.0.72 files. More.")).toBe( "Reads `koh.build`, e.g. v3.0.72 files.", ); expect(firstSentence("`a. b` is code! Next.")).toBe("`a. b` is code!"); }); test("a paragraph without a full stop is the whole line", () => { expect(firstSentence("Command line entry: dispatch table")).toBe( "Command line entry: dispatch table", ); }); });