#!/usr/bin/env bats SCAN="$(cd "$(dirname "${BATS_TEST_FILENAME}")/../.." && pwd -P)/devscripts/comment-scan" scan() { local f="$BATS_TEST_TMPDIR/x.go" cat > "$f" run "$SCAN" "$f" } scan_ts() { local f="$BATS_TEST_TMPDIR/x.tsx" cat > "$f" run "$SCAN" "$f" } scan_as() { local f="$BATS_TEST_TMPDIR/$1" mkdir -p "$(dirname "$f")" cat > "$f" run "$SCAN" "$f" } assert_kept() { [ "$status" -eq 0 ] || { echo "scanner failed: $output" >&2; return 1; } [ -z "$output" ] || { echo "expected nothing rejected, got:" >&2; echo "$output" >&2; return 1; } } assert_rejected() { [[ "$output" == *"$1"* ]] || { echo "expected '$1' to be rejected, got:" >&2 echo "${output:-}" >&2 return 1 } } @test "a doc comment on an exported declaration is kept" { scan <<'EOF' package pkg // Work runs the job and returns ErrBusy when another holds the lock. func Work() error { return nil } EOF assert_kept } @test "a doc comment on an unexported declaration is rejected" { scan <<'EOF' package pkg // work is the unexported half. func work() error { return nil } EOF assert_rejected "the unexported half" } @test "narration above a statement is rejected" { scan <<'EOF' package pkg func Work() { // fetch the user and check get() } EOF assert_rejected "fetch the user and check" } @test "a comment on an exported const group is kept" { scan <<'EOF' package pkg // The sort directions a query may ask for. const ( OrderAsc = "asc" OrderDesc = "desc" ) EOF assert_kept } @test "a doc comment on an exported member is kept" { scan <<'EOF' package pkg type Config struct { // Timeout bounds the whole dial, not each attempt. Timeout int } EOF assert_kept } @test "a block comment is read whole, not just its opener" { scan <<'EOF' package utmp /* At session start a record is written to UtmpxFile. If a record with the same ID exists it is overwritten; otherwise it is appended. */ type Utmpx struct { Pid int32 } EOF assert_kept } @test "a block comment inside a function is rejected in full" { scan <<'EOF' package pkg func Work() { /* the retry loop backs off because the peer rate limits */ retry() } EOF assert_rejected "the retry loop backs off" assert_rejected "because the peer rate limits" } @test "a licence header is kept" { scan <<'EOF' // Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pkg EOF assert_kept } @test "a build directive is kept" { scan <<'EOF' //go:build docker package pkg EOF assert_kept } @test "a suppression with a reason is kept" { scan <<'EOF' package pkg func Work() { //nolint:errcheck // the caller closes conn on this path conn.Close() } EOF assert_kept } @test "the rows of a raw string literal are not comments" { scan <<'EOF' package pkg const banner = ` ****************************** * Welcome to ShellHub * ****************************** ` EOF assert_kept } @test "a doc comment detached by a blank line is still the declaration's" { scan <<'EOF' package pkg // Work runs the job. func Work() error { return nil } EOF assert_kept } @test "an eslint suppression is kept in TypeScript" { scan_ts <<'EOF' export function useThing() { // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => {}, []); } EOF assert_kept } @test "a JSDoc block on an export is kept whole" { scan_ts <<'EOF' /** * Formats the device label, falling back to the hostname. */ export function label(device: Device): string { return device.name; } EOF assert_kept } @test "a local const is not exported, so its doc goes" { scan <<'EOF' package pkg func Work() { // MaxWait bounds one retry, not the whole loop. const MaxWait = time.Hour } EOF assert_rejected "MaxWait bounds one retry" scan_ts <<'EOF' export function Row() { // build the label and render const label = make(); return label; } EOF assert_rejected "build the label and render" } @test "an empty file and a file with no comments are both clean" { scan <<'EOF' package pkg func Work() error { return nil } EOF assert_kept scan <<'EOF' EOF assert_kept } @test "a lone asterisk in JSX is a required marker, not a JSDoc continuation" { scan_ts <<'EOF' export function Field() { return ( ); } EOF assert_kept } @test "a JSDoc continuation is still rejected with the block that opens it" { scan_ts <<'EOF' /** * Formats a duration. */ function format(ms: number) { return ms; } EOF assert_rejected "Formats a duration" assert_rejected "/**" } @test "a triple-slash reference is a compiler directive, not a comment" { scan_ts <<'EOF' /// export const mode = import.meta.env.MODE; EOF assert_kept } @test "a vitest environment pragma is a directive" { scan_ts <<'EOF' // @vitest-environment node import { readFileSync } from "node:fs"; EOF assert_kept } @test "a doc comment on a method of an exported class is what the linter asks for" { scan_ts <<'EOF' export class LocalVaultBackend { /** * Reads the vault header, or null when no vault exists here. */ loadMeta(): Promise { return Promise.resolve(null); } } EOF assert_kept } @test "a doc comment on a declaration the export list names is kept" { scan_ts <<'EOF' /** * Edits an API key's name and role. */ function EditKeyDrawer() { return null; } export default EditKeyDrawer; EOF assert_kept } @test "a doc comment on a declaration nothing exports is still rejected" { scan_ts <<'EOF' /** * Builds the label. */ function buildLabel() { return ""; } EOF assert_rejected "Builds the label" } @test "narration inside an exported class is still rejected" { scan_ts <<'EOF' export class Recorder { start(): void { // reset the clock before writing the header this.startMs = 0; } } EOF assert_rejected "reset the clock" } @test "narration inside a method of an exported class is rejected" { scan_ts <<'EOF' export class Recorder { finish(): void { // write the trailer before closing so a truncated file still replays this.write(""); } } EOF assert_rejected "write the trailer" } @test "a JSX comment in a component is rejected" { scan_ts <<'EOF' export function Header() { return (
{/* the title sits left of the actions */}

Devices

); } EOF assert_rejected "the title sits left" } @test "a comment line in a workflow is rejected" { scan_as ci.yml <<'EOF' jobs: test: # run the suite on every push runs-on: ubuntu-24.04 EOF assert_rejected "run the suite on every push" } @test "the version after a pinned action is kept" { scan_as ci.yml <<'EOF' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 EOF assert_kept } @test "a comment inside a run script is rejected" { scan_as ci.yml <<'EOF' steps: - name: Build run: | # build twice so the cache warms make build EOF assert_rejected "build twice so the cache warms" } @test "a markdown heading inside a text block scalar is kept" { scan_as template.yml <<'EOF' body: - type: markdown attributes: value: | # Bug report Describe what happened. EOF assert_kept } @test "a heredoc body inside a run script is kept" { scan_as ci.yml <<'EOF' steps: - run: | cat > summary.md <<'MD' # Results MD echo done EOF assert_kept } @test "a comment after a block scalar ends is rejected" { scan_as compose.yml <<'EOF' services: api: description: > # not a comment # the gateway fronts every service gateway: image: caddy EOF assert_rejected "the gateway fronts every service" [[ "$output" != *"not a comment"* ]] } @test "yaml tool directives are kept" { scan_as ci.yml <<'EOF' # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json on: pull_request_target: # zizmor: ignore[dangerous-triggers] EOF assert_kept } @test "a shell script keeps its shebang and suppressions and loses its narration" { scan_as run.sh <<'EOF' #!/usr/bin/env bash # shellcheck disable=SC2086 set -eu # wait for the api before seeding seed EOF assert_rejected "wait for the api before seeding" [[ "$output" != *"shellcheck"* && "$output" != *"#!/"* ]] } @test "a heredoc body in a shell script is kept" { scan_as run.sh <<'EOF' cat > notes.md <<-NOTES # Release notes NOTES EOF assert_kept } @test "a herestring does not open a heredoc" { scan_as run.sh <<'EOF' read -r a b <<< "$pair" # split the pair echo "$a" EOF assert_rejected "split the pair" } @test "a shell script without an extension is read by its shebang" { scan_as bin/tool <<'EOF' #!/bin/sh # forward everything to compose exec docker compose "$@" EOF assert_rejected "forward everything to compose" } @test "a file without an extension or a shell shebang is not scanned" { scan_as bin/data <<'EOF' # not a script EOF assert_kept } @test "a licence header in a shell script is kept" { scan_as run.sh <<'EOF' #!/bin/sh # Copyright 2020 O.S. Systems # SPDX-License-Identifier: Apache-2.0 echo ok EOF assert_kept } @test "a Dockerfile keeps its parser directive and RUN heredoc and loses its narration" { scan_as Dockerfile <<'EOF' # syntax=docker/dockerfile:1 FROM alpine # install tools first so the layer caches RUN <

{Astro.props.title}

EOF assert_rejected "keep the hero above the fold" assert_rejected "