Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions .mise/tasks/lint/bats-python-one-liner
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
#MISE description="Flag unreadable Python assertion one-liners in BATS tests"
#USAGE arg "<targets>..." help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:bats-python-one-liner ." header="Flag inline Python assertions in BATS tests"

set -euo pipefail

# shellcheck source=../../../lib/shell-files.sh
source "$MISE_CONFIG_ROOT/lib/shell-files.sh"

# Rationale: BATS tests that embed Python assertions as inline
# `python3 -c "...assert..."` one-liners are hard to review, hard to
# diff, and easy to normalize across repos. The preferred alternative
# is a heredoc Python script:
#
# json_file="$BATS_TEST_TMPDIR/parse.json"
# printf '%s\n' "$output" > "$json_file"
# python3 - "$json_file" <<'PY'
# import json, sys
# data = json.load(open(sys.argv[1]))
# assert data["frontmatter"] == {}
# PY
#
# See fold/notes/bats-tool-testing.md for the broader BATS principle.
#
# Detection: flag any line containing `python3 -c` or `python -c` where
# the inline code contains the word `assert`. This catches the primary
# readability concern while avoiding false positives from short harmless
# calls like `python3 -c 'print("hello")'`.

IFS=' ' read -ra TARGETS <<< "${usage_targets}"

if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "ERROR: at least one target is required" >&2
exit 1
fi

# Resolve relative paths against CALLER_PWD (see lib/shell-files.sh).
for i in "${!TARGETS[@]}"; do
TARGETS[$i]=$(resolve_target "${TARGETS[$i]}")
done

# discover_bats_files <target>
#
# Emit paths of .bats files under <target>/test, excluding '*/fixtures/*'.
discover_bats_files() {
local target="$1"
if [[ -d "$target/test" ]]; then
fd -t f -e bats --exclude fixtures . "$target/test"
fi
}

# is_heredoc_context <file> <lineno>
#
# Check if the given line is inside a heredoc block by scanning backward
# for a heredoc delimiter. Returns 0 if inside a heredoc, 1 otherwise.
is_heredoc_context() {
local file="$1" lineno="$2"
# Scan lines before the current one for heredoc start markers
sed -n "1,$((lineno - 1))p" "$file" | tac | grep -qE '<<[-]?["'"'"']?(PY|EOF|END|PYTHON|JSON)'
}

scan_file() {
local file="$1"
local lineno=0
local line

while IFS= read -r line || [[ -n "$line" ]]; do
lineno=$((lineno + 1))

# Skip @test description lines (they contain literal strings, not code).
if [[ "$line" =~ ^[[:space:]]*@test[[:space:]]+ ]]; then
continue
fi

# Skip full-line comments.
if [[ "$line" =~ ^[[:space:]]*# ]]; then
continue
fi

# Inline opt-out.
if [[ "$line" == *"codebase:ignore"* ]]; then
continue
fi

# Skip lines inside heredoc context.
if is_heredoc_context "$file" "$lineno"; then
continue
fi

# Flag python3 -c / python -c lines containing 'assert'.
if [[ "$line" =~ python3?[[:space:]]+-c[[:space:]]+ ]] && [[ "$line" == *"assert"* ]]; then
local trimmed="${line#"${line%%[![:space:]]*}"}"
echo "$lineno: $trimmed"
echo " hint: Use a heredoc Python script instead of inline -c with assert"
fi
done < "$file"
}

failures=0

for target in "${TARGETS[@]}"; do
if [[ ! -e "$target" ]]; then
echo "ERROR: target does not exist: $target" >&2
exit 1
fi

name=$(basename "$target")

# File-level ignore via mise.toml
toml="$target/mise.toml"
if [[ -f "$toml" ]] && rg -q 'codebase:ignore bats-python-one-liner' "$toml"; then
echo "SKIP $name (codebase:ignore)"
continue
fi

files=()
while IFS= read -r f; do
[[ -n "$f" ]] && files+=("$f")
done < <(discover_bats_files "$target")

if [[ ${#files[@]} -eq 0 ]]; then
echo "OK $name (no test/ .bats files found)"
continue
fi

hit_count=0
target_output=""
for file in "${files[@]}"; do
rel="${file#"$target"/}"
while IFS= read -r hit; do
[[ -z "$hit" ]] && continue
target_output+=" $rel:$hit"$'\n'
# Only count lines starting with a line number as findings.
# Hint lines (indented) are informational, not separate findings.
if [[ "$hit" =~ ^[0-9]+: ]]; then
hit_count=$((hit_count + 1))
fi
done < <(scan_file "$file")
done

if [[ "$hit_count" -gt 0 ]]; then
echo "FAIL $name: $hit_count unreadable Python one-liner(s)"
printf '%s' "$target_output"
echo " hint: Use a heredoc Python script instead of inline -c with assert —"
echo " see fold/notes/bats-tool-testing.md"
failures=$((failures + 1))
else
echo "OK $name (${#files[@]} file(s) clean)"
fi
done

exit "$failures"
2 changes: 0 additions & 2 deletions test/lib/shell-files.bats
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ setup() {
source "$REPO_DIR/lib/shell-files.sh"
}

# ============================================================================
# resolve_target
# ============================================================================

@test "resolve_target: absolute path passes through unchanged" {
result=$(resolve_target "/some/absolute/path")
Expand Down
113 changes: 113 additions & 0 deletions test/lint/bats-python-one-liner/bats-python-one-liner.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env bats
# Tests for lint:bats-python-one-liner rule

load ../../test_helper

setup() {
FIXTURES="$BATS_TEST_DIRNAME/fixtures"
}

# Pass paths

@test "bats-python-one-liner: passes on clean codebase with heredoc Python" {
run codebase lint:bats-python-one-liner "$FIXTURES/clean"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"*"clean"* ]]
}

@test "bats-python-one-liner: passes when target has no test/ dir" {
run codebase lint:bats-python-one-liner "$FIXTURES/empty"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"*"empty"* ]]
[[ "$output" == *"no test/ .bats files found"* ]]
}

@test "bats-python-one-liner: does not flag short harmless python3 -c print" {
run codebase lint:bats-python-one-liner "$FIXTURES/clean"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
}

# Detection

@test "bats-python-one-liner: flags python3 -c with assert" {
run codebase lint:bats-python-one-liner "$FIXTURES/dirty-assert"
[ "$status" -ne 0 ]
[[ "$output" == *"FAIL"*"dirty-assert"* ]]
[[ "$output" == *"python3 -c"* ]]
[[ "$output" == *"assert"* ]]
}

@test "bats-python-one-liner: flags python -c with assert" {
run codebase lint:bats-python-one-liner "$FIXTURES/dirty-python"
[ "$status" -ne 0 ]
[[ "$output" == *"FAIL"*"dirty-python"* ]]
[[ "$output" == *"python -c"* ]]
[[ "$output" == *"assert"* ]]
}

@test "bats-python-one-liner: flags long python3 -c with assert" {
run codebase lint:bats-python-one-liner "$FIXTURES/dirty-long"
[ "$status" -ne 0 ]
[[ "$output" == *"FAIL"*"dirty-long"* ]]
[[ "$output" == *"assert"* ]]
}

# False positives — these should NOT be flagged

@test "bats-python-one-liner: does NOT flag heredoc Python" {
run codebase lint:bats-python-one-liner "$FIXTURES/heredoc"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
}

# Ignore directives

@test "bats-python-one-liner: inline '# codebase:ignore' suppresses a single line" {
run codebase lint:bats-python-one-liner "$FIXTURES/ignored-inline"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
}

@test "bats-python-one-liner: 'codebase:ignore bats-python-one-liner' in mise.toml skips target" {
run codebase lint:bats-python-one-liner "$FIXTURES/ignored-file"
[ "$status" -eq 0 ]
[[ "$output" == *"SKIP"*"ignored-file"* ]]
}

# Output details

@test "bats-python-one-liner: fail output includes file:line citations" {
run codebase lint:bats-python-one-liner "$FIXTURES/dirty-assert"
[ "$status" -ne 0 ]
[[ "$output" == *"test/bad.bats:"*":"* ]]
}

@test "bats-python-one-liner: fail output includes remediation hint" {
run codebase lint:bats-python-one-liner "$FIXTURES/dirty-assert"
[ "$status" -ne 0 ]
[[ "$output" == *"bats-tool-testing.md"* ]]
[[ "$output" == *"heredoc"* ]]
}

# Error handling

@test "bats-python-one-liner: fails when no targets given" {
run codebase lint:bats-python-one-liner
[ "$status" -ne 0 ]
}

@test "bats-python-one-liner: fails when target does not exist" {
run codebase lint:bats-python-one-liner "/nonexistent/path/xyz"
[ "$status" -ne 0 ]
[[ "$output" == *"does not exist"* ]]
}

# Multi-target

@test "bats-python-one-liner: accepts multiple targets and reports each" {
run codebase lint:bats-python-one-liner "$FIXTURES/clean" "$FIXTURES/dirty-assert"
[ "$status" -ne 0 ]
[[ "$output" == *"OK"*"clean"* ]]
[[ "$output" == *"FAIL"*"dirty-assert"* ]]
}
2 changes: 2 additions & 0 deletions test/lint/bats-python-one-liner/fixtures/clean/mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tools]
bats = "1.13.0"
14 changes: 14 additions & 0 deletions test/lint/bats-python-one-liner/fixtures/clean/test/example.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bats
load test_helper

@test "parses frontmatter with heredoc" {
run mytool parse
json_file="$BATS_TEST_TMPDIR/parse.json"
printf '%s\n' "$output" > "$json_file"
python3 - "$json_file" <<'PY'
import json, sys
data = json.load(open(sys.argv[1]))
assert data["frontmatter"] == {}
PY
[ "$status" -eq 0 ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bats
load test_helper

@test "short harmless print" {
run mytool version
result="$(python3 -c 'print("hello")')"
[ "$result" = "hello" ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bats

@test "parses frontmatter" {
run mytool parse
echo "$output" | python3 -c "import sys, json; data = json.load(sys.stdin); assert data['frontmatter'] == {}"
[ "$status" -eq 0 ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bats

@test "complex transformation" {
run mytool transform
result="$(python3 -c "import sys; data = [l.strip().split(',') for l in sys.stdin]; assert all(len(d) > 2 for d in data)")"
[ -n "$result" ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bats

@test "parses frontmatter with python" {
run mytool parse
echo "$output" | python -c "import sys, json; data = json.load(sys.stdin); assert data['frontmatter'] == {}"
[ "$status" -eq 0 ]
}
17 changes: 17 additions & 0 deletions test/lint/bats-python-one-liner/fixtures/heredoc/test/good.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bats

@test "parses frontmatter with heredoc" {
run mytool parse
json_file="$BATS_TEST_TMPDIR/parse.json"
printf '%s\n' "$output" > "$json_file"
python3 - "$json_file" <<'PY'
import json
import sys

data = json.load(open(sys.argv[1]))
assert data["frontmatter"] == {}
assert data["frontmatter_present"] is False
assert data["diagnostics"] == []
PY
[ "$status" -eq 0 ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[_.codebase]
lint = ["bats-python-one-liner"]
codebase:ignore bats-python-one-liner
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bats

@test "parses frontmatter" {
run mytool parse
echo "$output" | python3 -c "import sys, json; data = json.load(sys.stdin); assert data['frontmatter'] == {}"
[ "$status" -eq 0 ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bats

@test "parses frontmatter with inline ignore" {
run mytool parse
echo "$output" | python3 -c "import sys, json; data = json.load(sys.stdin); assert data['frontmatter'] == {}" # codebase:ignore bats-python-one-liner — intentional inline assertion for a trivial check
[ "$status" -eq 0 ]
}
Loading