Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AGENTS.md
57 changes: 39 additions & 18 deletions .mise/tasks/lint/_default
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ if [[ ! -f "$TOML" ]]; then
exit 1
fi

# Check if the original config uses groups, for preamble display.
ORIG_LINT="$(mise config get -f "$TOML" _.codebase.lint 2>/dev/null || true)"
HAS_GROUPS=false
if [[ "$ORIG_LINT" == *"@"* ]]; then
HAS_GROUPS=true
fi

RULES=()
while IFS= read -r rule; do
[[ -n "$rule" ]] && RULES+=("$rule")
Expand All @@ -29,31 +36,45 @@ if [[ ${#RULES[@]} -eq 0 ]]; then
echo "Add to mise.toml:" >&2
echo ' [_.codebase]' >&2
echo ' lint = ["mise-settings", "gum-table"]' >&2
echo " # Or use a preset group:" >&2
echo ' # lint = ["@maintained-tool"]' >&2
echo " # See 'mise run lint:groups' for available groups" >&2
exit 1
fi

if $HAS_GROUPS; then
echo "codebase: expanded ${#RULES[@]} rule(s) from lint groups"
fi

failures=0
FAILED=()

for rule in "${RULES[@]}"; do
rule_target=$(codebase_target_for_rule "$REPO_ROOT" "$rule")
echo "codebase: lint:$rule $rule_target"

output=""
if output=$(mise run -q "lint:$rule" "$rule_target" 2>&1); then
status=0
else
status=$?
fi

if [[ -n "$output" ]]; then
printf '%s\n' "$output"
fi

if [[ "$status" -ne 0 ]]; then
failures=$((failures + 1))
FAILED+=("lint:$rule ($rule_target) exited $status")
fi
# Collect all targets for this rule (supports multi-path scopes)
TARGETS=()
while IFS= read -r t; do
[[ -n "$t" ]] && TARGETS+=("$t")
done < <(codebase_targets_for_rule "$REPO_ROOT" "$rule")

for rule_target in "${TARGETS[@]}"; do
echo "codebase: lint:$rule $rule_target"

output=""
if output=$(mise run -q "lint:$rule" "$rule_target" 2>&1); then
status=0
else
status=$?
fi

if [[ -n "$output" ]]; then
printf '%s\n' "$output"
fi

if [[ "$status" -ne 0 ]]; then
failures=$((failures + 1))
FAILED+=("lint:$rule ($rule_target) exited $status")
fi
done
done

if [[ "$failures" -gt 0 ]]; then
Expand Down
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"
Loading