The flattener's last-resort branch rewrote ASCII ' to U+2019, justified as the one combination no YAML scalar can carry verbatim. That claim was false: a |- literal block with a single indented content line carries ', ", \ and ": " verbatim and keeps text.frontmatter.description matching — as the wrapper's own docstring already said of literal blocks. The rewrite fired on 12 of 54 in-scope files, silently disabling every rule whose token contains an apostrophe. Case 20 pinned only that the scope stayed alive, so it passed either way. The emission site now splits the emitted scalar on its first newline so a carried-over trailing comment stays on the "description: |-" header line rather than becoming part of the value, and pads by span_lines - 1 - newlines. The pad stays non-negative because the branch is only reachable when the original span is at least two lines. Verified across all 73 in-scope files: no line-count changes, and exactly the 12 expected files take the new branch. One reported position moves: an alert on a description that is itself flagged shifts from the key line to the block's content line, both inside the original span. YAML cannot put a literal block's content on the key's own line, so this is unavoidable; no line at or after the end of any description span moves. Also: --output no longer absolutises the built-in style names line, JSON and CLI, which a same-named file or directory in cwd turned into a template path (exit 2, E100 Runtime error). And case 19's empty-baseline guard no longer lets five dependent comparisons print vacuous passes — while fixing it the guard turned out to be unreachable, since under pipefail an alert-free report aborted the script at the assignment. Refs: #85 ADR: 0014
848 lines
39 KiB
Bash
Executable File
848 lines
39 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Regression test for scripts/vale-wrap.sh: Vale's `text.frontmatter.description`
|
|
# NLP scope silently stops matching when the description value is a YAML block
|
|
# scalar spanning 2+ physical lines. vale-wrap.sh flattens it to one line before
|
|
# handing off to the real vale binary — this asserts that actually happens.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
# skill-audit's copy is used here (not agent-audit's) because every fixture below is a
|
|
# SKILL.md — only skill-audit's .vale.ini has the [**/SKILL.md] glob section. vale-wrap.sh
|
|
# itself is an identical copy in both skills, so which one SCRIPT points at doesn't matter.
|
|
SKILL_AUDIT="$REPO_ROOT/plugins/kyberforge/skills/skill-audit"
|
|
SCRIPT="$SKILL_AUDIT/scripts/vale-wrap.sh"
|
|
VALE_CONFIG="$SKILL_AUDIT/assets/vale/.vale.ini"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
if ! command -v vale &>/dev/null; then
|
|
echo "SKIP: vale is not installed — skipping (matches skill-audit/agent-audit's own fallback behavior)"
|
|
exit 77
|
|
fi
|
|
|
|
make_fixture() {
|
|
local dir desc_lines file
|
|
dir="$(mktemp -d)"
|
|
(cd "$dir" && git init -q)
|
|
mkdir -p "$dir/plugins/testplugin/skills/zzzskill"
|
|
desc_lines="$1"
|
|
file="$dir/plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
{
|
|
echo "---"
|
|
echo "name: zzzskill"
|
|
echo "description: >"
|
|
for ((i = 1; i <= desc_lines; i++)); do
|
|
echo " Line $i mentions helps with and utilize, plus a colon: like this."
|
|
done
|
|
echo "---"
|
|
echo ""
|
|
echo "Body."
|
|
} > "$file"
|
|
echo "$dir"
|
|
}
|
|
|
|
# Every Kyberforge rule is `level: error`, so vale exits non-zero whenever a
|
|
# fixture trips one — which is the expected outcome for nearly every case here.
|
|
# run_wrap therefore captures output and swallows the exit status; assertions
|
|
# are made on the report text. Cases that genuinely care about the exit code
|
|
# capture it explicitly instead.
|
|
run_wrap() {
|
|
local dir="$1"
|
|
shift
|
|
(cd "$dir" && bash "$SCRIPT" "$@" 2>&1) || true
|
|
}
|
|
|
|
# --- 1. A known-bad single-line description is caught (sanity check on Vale itself) ---
|
|
echo ""
|
|
echo "--- catches vague wording in a single-line description ---"
|
|
FIXTURE1="$(make_fixture 1)"
|
|
trap 'rm -rf "$FIXTURE1"' EXIT
|
|
if run_wrap "$FIXTURE1" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
|
|
pass "flags vague wording when description is a single physical line"
|
|
else
|
|
fail "did not flag known-bad single-line description"
|
|
fi
|
|
|
|
# --- 2. The same known-bad wording across 2+ physical lines is still caught ---
|
|
echo ""
|
|
echo "--- catches vague wording in a multi-line folded description ---"
|
|
FIXTURE2="$(make_fixture 2)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2"' EXIT
|
|
if run_wrap "$FIXTURE2" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
|
|
pass "flags vague wording when description spans 2+ physical lines"
|
|
else
|
|
fail "silently missed known-bad wording in a multi-line description — the bug this test guards against"
|
|
fi
|
|
|
|
# --- 3. Line count is preserved so unrelated report line numbers don't shift ---
|
|
echo ""
|
|
echo "--- preserves total line count when flattening ---"
|
|
FIXTURE3="$(make_fixture 3)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3"' EXIT
|
|
ORIG_LINES=$(wc -l < "$FIXTURE3/plugins/testplugin/skills/zzzskill/SKILL.md")
|
|
OUT=$(run_wrap "$FIXTURE3" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md)
|
|
MAX_LINE=$(echo "$OUT" | grep -oE '^[[:space:]]*[0-9]+:[0-9]+' | tr -d '[:space:]' | cut -d: -f1 | sort -n | tail -1)
|
|
if [[ -n "$MAX_LINE" ]] && (( MAX_LINE <= ORIG_LINES )); then
|
|
pass "reported line numbers stay within the original file's line count"
|
|
else
|
|
fail "reported line number ($MAX_LINE) exceeds original file line count ($ORIG_LINES)"
|
|
fi
|
|
|
|
# make_raw_fixture writes stdin verbatim to a fresh fixture's SKILL.md, for
|
|
# cases where the exact description body needs to be hand-crafted rather than
|
|
# generated from the desc_lines loop above.
|
|
make_raw_fixture() {
|
|
local dir
|
|
dir="$(mktemp -d)"
|
|
(cd "$dir" && git init -q)
|
|
mkdir -p "$dir/plugins/testplugin/skills/zzzskill"
|
|
cat > "$dir/plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
echo "$dir"
|
|
}
|
|
|
|
# --- 4. A folded description containing a double quote is still caught ---
|
|
# This is the exact case that silently passed (zero alerts) before switching
|
|
# from json.dumps (double-quoted, backslash-escaped) to a single-quoted scalar.
|
|
echo ""
|
|
echo "--- catches vague wording when the folded description contains a double quote ---"
|
|
FIXTURE4="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Use when the user says "audit this skill" and helps with and utilize things.
|
|
Second line continues the same folded scalar for flattening.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4"' EXIT
|
|
if run_wrap "$FIXTURE4" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
|
|
pass "flags vague wording when the description contains a double quote"
|
|
else
|
|
fail "silently missed vague wording in a description containing a double quote — the bug this test guards against"
|
|
fi
|
|
|
|
# --- 5. A folded description containing an apostrophe fires and stays valid YAML ---
|
|
echo ""
|
|
echo "--- catches vague wording when the folded description contains an apostrophe ---"
|
|
FIXTURE5="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Use when the user's task helps with and utilize things across two lines.
|
|
Second continuation line for the fold.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5"' EXIT
|
|
OUT5=$(run_wrap "$FIXTURE5" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md)
|
|
if echo "$OUT5" | grep -q "VagueWording"; then
|
|
pass "flags vague wording when the description contains an apostrophe"
|
|
else
|
|
fail "silently missed vague wording in a description containing an apostrophe"
|
|
fi
|
|
if echo "$OUT5" | grep -qi "yaml:"; then
|
|
fail "flattened copy with an apostrophe produced a YAML parse error"
|
|
else
|
|
pass "flattened copy with an apostrophe is valid YAML (no parse error)"
|
|
fi
|
|
|
|
# --- 6. A folded description with a backslash and a non-ASCII character ---
|
|
echo ""
|
|
echo "--- catches vague wording when the folded description has a backslash and non-ASCII text ---"
|
|
FIXTURE6="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Use when the café résumé naïve thing helps with and utilize things here.
|
|
Path is C:\Users\test and this is the second continuation line.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6"' EXIT
|
|
if run_wrap "$FIXTURE6" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md | grep -q "VagueWording"; then
|
|
pass "flags vague wording when the description has a backslash and non-ASCII text"
|
|
else
|
|
fail "silently missed vague wording in a description with a backslash and non-ASCII text"
|
|
fi
|
|
|
|
# --- 7. A folded description with a blank line between two paragraphs ---
|
|
echo ""
|
|
echo "--- handles a blank line inside a folded description without crashing ---"
|
|
FIXTURE7="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Use when the user needs a general helper.
|
|
|
|
Do not use when this helps with and utilize things instead.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7"' EXIT
|
|
OUT7=$(run_wrap "$FIXTURE7" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md)
|
|
if echo "$OUT7" | grep -q "Traceback"; then
|
|
fail "crashed while flattening a description with a blank line between paragraphs"
|
|
elif echo "$OUT7" | grep -q "VagueWording"; then
|
|
pass "still flags vague wording in the second paragraph after a blank line"
|
|
else
|
|
fail "silently missed vague wording in the second paragraph after a blank line — the bug this test guards against"
|
|
fi
|
|
|
|
# --- 8. Relative paths resolve against the caller's cwd, exactly as bare vale
|
|
# resolves them. Every path below is deliberately relative to $SUBDIR8, not to
|
|
# the fixture's repo root: an earlier version of the wrapper resolved relative
|
|
# paths against the git toplevel instead, which (a) hard-errored on a
|
|
# `--config ../../..` that bare vale accepts and (b) silently dropped file
|
|
# arguments that didn't resolve from the repo root, skipping the flattening the
|
|
# wrapper exists to perform. The old tests only ever passed repo-root-relative
|
|
# paths from a subdirectory, so neither failure mode was caught.
|
|
echo ""
|
|
echo "--- resolves a cwd-relative --config from a subdirectory (equals and two-argv forms) ---"
|
|
FIXTURE8="$(mktemp -d)"
|
|
(cd "$FIXTURE8" && git init -q)
|
|
cp "$VALE_CONFIG" "$FIXTURE8/.vale.ini"
|
|
cp -r "$SKILL_AUDIT/assets/vale/styles" "$FIXTURE8/styles"
|
|
mkdir -p "$FIXTURE8/plugins/testplugin/skills/zzzskill"
|
|
{
|
|
echo "---"
|
|
echo "name: zzzskill"
|
|
echo "description: >"
|
|
echo " Line one mentions helps with and utilize, plus a colon: like this."
|
|
echo " Line two continues the same folded scalar for flattening."
|
|
echo "---"
|
|
echo ""
|
|
echo "Body."
|
|
} > "$FIXTURE8/plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8"' EXIT
|
|
SUBDIR8="$FIXTURE8/plugins/testplugin/skills/zzzskill"
|
|
# Both paths are relative to $SUBDIR8 (four levels below the fixture root).
|
|
REL_CFG8="../../../../.vale.ini"
|
|
REL_FILE8="SKILL.md"
|
|
OUT_EQ=$(run_wrap "$SUBDIR8" "--config=$REL_CFG8" "$REL_FILE8")
|
|
OUT_TWO=$(run_wrap "$SUBDIR8" --config "$REL_CFG8" "$REL_FILE8")
|
|
if echo "$OUT_EQ" | grep -q "VagueWording" && [[ "$OUT_EQ" == "$OUT_TWO" ]]; then
|
|
pass "cwd-relative --config resolves from a subdirectory in both argv forms"
|
|
else
|
|
fail "cwd-relative --config did not resolve from a subdirectory (equals form vs two-argv form)"
|
|
fi
|
|
|
|
# --- 8b. A cwd-relative --config matches the equivalent absolute invocation ---
|
|
# Regression for failure mode (a): resolving --config against the repo root made
|
|
# `--config ../../../../.vale.ini` expand to a path above the toplevel, and vale
|
|
# hard-errored with "does not exist" (exit 2) on args bare vale handles fine.
|
|
echo ""
|
|
echo "--- a cwd-relative --config produces the same result as the absolute-path form ---"
|
|
set +e
|
|
OUT_REL_CFG=$(cd "$SUBDIR8" && bash "$SCRIPT" --config "$REL_CFG8" "$REL_FILE8" 2>&1)
|
|
RC_REL_CFG=$?
|
|
OUT_ABS_CFG=$(cd "$SUBDIR8" && bash "$SCRIPT" --config "$FIXTURE8/.vale.ini" "$REL_FILE8" 2>&1)
|
|
RC_ABS_CFG=$?
|
|
set -e
|
|
if echo "$OUT_REL_CFG" | grep -qi "does not exist"; then
|
|
fail "cwd-relative --config hard-errored ('does not exist') — the bug this test guards against"
|
|
elif [[ "$OUT_REL_CFG" == "$OUT_ABS_CFG" && "$RC_REL_CFG" -eq "$RC_ABS_CFG" ]]; then
|
|
pass "cwd-relative --config matches the absolute-path invocation (output and exit code)"
|
|
else
|
|
fail "cwd-relative --config (rc=$RC_REL_CFG) diverged from the absolute-path form (rc=$RC_ABS_CFG)"
|
|
fi
|
|
|
|
# --- 8c. A cwd-relative FILE argument is still flattened, not silently skipped ---
|
|
# Regression for failure mode (b): a relative file path that didn't resolve from
|
|
# the repo root failed the wrapper's file test, fell through to the vale flag
|
|
# list, and left the file list empty — so the wrapper exec'd bare vale and
|
|
# silently skipped the flattening. Bare vale reports nothing here, so asserting
|
|
# on the alert (not just the exit code) is what makes the silence detectable.
|
|
echo ""
|
|
echo "--- flattens a cwd-relative file argument passed from a subdirectory ---"
|
|
WRAPPED_REL=$(run_wrap "$SUBDIR8" --config "$FIXTURE8/.vale.ini" "$REL_FILE8")
|
|
BARE_REL=$(cd "$SUBDIR8" && vale --config "$FIXTURE8/.vale.ini" "$REL_FILE8" 2>&1 || true)
|
|
if ! echo "$WRAPPED_REL" | grep -q "VagueWording"; then
|
|
fail "cwd-relative file argument produced no alert — flattening was silently skipped, the bug this test guards against"
|
|
elif echo "$BARE_REL" | grep -q "VagueWording"; then
|
|
fail "bare vale already flags this fixture, so the test can't detect a silently-skipped flattening"
|
|
else
|
|
pass "cwd-relative file argument is flattened and flagged where bare vale reports nothing"
|
|
fi
|
|
|
|
# --- 9. Zero file args (or a file list that filters to nothing) exits promptly ---
|
|
echo ""
|
|
echo "--- exits promptly instead of hanging on stdin when no files are passed ---"
|
|
if timeout 5 bash "$SCRIPT" --config "$VALE_CONFIG" < <(sleep 100) >/dev/null 2>&1; then
|
|
pass "exits promptly with zero file args"
|
|
else
|
|
RC=$?
|
|
if [[ $RC -eq 124 ]]; then
|
|
fail "hung waiting on stdin with zero file args — the bug this test guards against"
|
|
else
|
|
pass "exits promptly (nonzero exit) with zero file args"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- exits promptly when a file list filters down to nothing ---"
|
|
if timeout 5 bash "$SCRIPT" --config "$VALE_CONFIG" --no-such-flag < <(sleep 100) >/dev/null 2>&1; then
|
|
pass "exits promptly when no file-shaped args remain"
|
|
else
|
|
RC=$?
|
|
if [[ $RC -eq 124 ]]; then
|
|
fail "hung waiting on stdin when the file list filtered to nothing — the bug this test guards against"
|
|
else
|
|
pass "exits promptly (nonzero exit) when the file list filters to nothing"
|
|
fi
|
|
fi
|
|
|
|
# --- 10. An absolute path to the fixture SKILL.md is still linted, not skipped ---
|
|
echo ""
|
|
echo "--- lints an absolute path to a skill file instead of silently skipping it ---"
|
|
FIXTURE10="$(make_fixture 2)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10"' EXIT
|
|
ABS_FILE10="$FIXTURE10/plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
if run_wrap "$FIXTURE10" --config "$VALE_CONFIG" "$ABS_FILE10" | grep -q "VagueWording"; then
|
|
pass "an absolute path is linted, not silently skipped"
|
|
else
|
|
fail "an absolute path was silently skipped — the bug this test guards against"
|
|
fi
|
|
|
|
# --- 11. A literal (|) block scalar passes through unflattened. Unlike every
|
|
# other multi-line form, `|` is not broken in Vale: its parsed value keeps the
|
|
# same line breaks the source has, so the description scope still matches. The
|
|
# second assertion pins that down — without it, a wrapper that broke `|` and a
|
|
# Vale that never matched `|` would agree on zero alerts and the comparison
|
|
# would pass vacuously.
|
|
echo ""
|
|
echo "--- leaves a literal (|) block scalar untouched (narrowed >-only scope) ---"
|
|
FIXTURE11="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: |
|
|
Line one mentions helps with and utilize things here.
|
|
Line two continues the literal block scalar for this test.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11"' EXIT
|
|
REL11="plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
WRAPPED_OUT=$(run_wrap "$FIXTURE11" --config "$VALE_CONFIG" "$REL11")
|
|
BARE_OUT=$(cd "$FIXTURE11" && vale --config "$VALE_CONFIG" "$REL11" 2>&1 || true)
|
|
if ! echo "$BARE_OUT" | grep -q "VagueWording"; then
|
|
fail "bare vale reports nothing for a literal (|) block scalar — the 'literal blocks are not broken' premise is wrong"
|
|
elif [[ "$WRAPPED_OUT" == "$BARE_OUT" ]]; then
|
|
pass "literal (|) block scalar output matches bare vale exactly — untouched by flattening"
|
|
else
|
|
fail "wrapper altered output for a literal (|) block scalar description — should be left untouched"
|
|
fi
|
|
|
|
# --- 12. With no --config at all, the wrapper falls back to its own sibling
|
|
# assets/vale/.vale.ini. `.pre-commit-hooks.yaml` relies on this: pre-commit
|
|
# prefixes only entry[0] with the hook-repo clone path, so a --config argument
|
|
# there resolves against the consuming repo and hard-errors (E100) for every
|
|
# external consumer.
|
|
echo ""
|
|
echo "--- defaults --config to the wrapper's own sibling assets/vale/.vale.ini ---"
|
|
FIXTURE12="$(make_fixture 2)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12"' EXIT
|
|
OUT12=$(run_wrap "$FIXTURE12" plugins/testplugin/skills/zzzskill/SKILL.md)
|
|
if echo "$OUT12" | grep -q "VagueWording"; then
|
|
pass "a --config-less invocation uses the wrapper's bundled config"
|
|
else
|
|
fail "a --config-less invocation found no config — external pre-commit consumers get E100, the bug this test guards against"
|
|
fi
|
|
|
|
# --- 13. No GNU-only `realpath -m`. macOS ships the BSD realpath, which has no
|
|
# -m (canonicalize-missing) — and every scratch destination is a path that does
|
|
# not exist yet, so a plain `realpath` exits 1 and set -e aborts the hook.
|
|
echo ""
|
|
echo "--- runs with a BSD realpath that has no -m option ---"
|
|
STUB13="$(mktemp -d)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12" "$STUB13"' EXIT
|
|
REAL_REALPATH="$(command -v realpath || echo /bin/false)"
|
|
cat > "$STUB13/realpath" <<EOF
|
|
#!/usr/bin/env bash
|
|
for a in "\$@"; do
|
|
case "\$a" in
|
|
-m|--canonicalize-missing)
|
|
echo "realpath: illegal option -- m" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
exec "$REAL_REALPATH" "\$@"
|
|
EOF
|
|
chmod +x "$STUB13/realpath"
|
|
OUT13=$(cd "$FIXTURE12" && PATH="$STUB13:$PATH" bash "$SCRIPT" --config "$VALE_CONFIG" \
|
|
plugins/testplugin/skills/zzzskill/SKILL.md 2>&1 || true)
|
|
if echo "$OUT13" | grep -q "illegal option"; then
|
|
fail "invoked realpath -m — fails on macOS's BSD realpath, the bug this test guards against"
|
|
elif echo "$OUT13" | grep -q "VagueWording"; then
|
|
pass "flattens and flags with no GNU realpath available"
|
|
else
|
|
fail "produced no alert under a BSD-style realpath: $OUT13"
|
|
fi
|
|
|
|
# --- 14. A directory argument is walked and its files flattened. The classifier
|
|
# used to accept only regular files, so a directory fell through to the vale
|
|
# flag list, left the file list empty, and exec'd bare vale — silently skipping
|
|
# the flattening. `lint`'s vale-run skill documents `vale <path-or-glob>` as
|
|
# normal usage, so this is a reachable path.
|
|
echo ""
|
|
echo "--- flattens files reached through a directory argument ---"
|
|
FIXTURE14="$(make_fixture 2)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12" "$STUB13" "$FIXTURE14"' EXIT
|
|
WRAPPED_DIR=$(run_wrap "$FIXTURE14" --config "$VALE_CONFIG" plugins)
|
|
BARE_DIR=$(cd "$FIXTURE14" && vale --config "$VALE_CONFIG" plugins 2>&1 || true)
|
|
if ! echo "$WRAPPED_DIR" | grep -q "VagueWording"; then
|
|
fail "a directory argument produced no alert — flattening was silently skipped, the bug this test guards against"
|
|
elif echo "$BARE_DIR" | grep -q "VagueWording"; then
|
|
fail "bare vale already flags this fixture, so the test can't detect a silently-skipped flattening"
|
|
else
|
|
pass "a directory argument is walked and its files flattened"
|
|
fi
|
|
|
|
# --- 15. Directory walking must survive paths with spaces ---
|
|
echo ""
|
|
echo "--- walks a directory containing a path with spaces ---"
|
|
SPACED15="$FIXTURE14/plugins/testplugin/skills/zzz skill"
|
|
mkdir -p "$SPACED15"
|
|
cp "$FIXTURE14/plugins/testplugin/skills/zzzskill/SKILL.md" "$SPACED15/SKILL.md"
|
|
rm -rf "$FIXTURE14/plugins/testplugin/skills/zzzskill"
|
|
OUT15=$(run_wrap "$FIXTURE14" --config "$VALE_CONFIG" plugins/testplugin/skills)
|
|
if echo "$OUT15" | grep -q "zzz skill" && echo "$OUT15" | grep -q "VagueWording"; then
|
|
pass "a file under a directory whose name contains a space is walked and flattened"
|
|
else
|
|
fail "a path with a space was dropped from the directory walk"
|
|
fi
|
|
|
|
# --- 16. No unguarded `"${arr[@]}"` expansion survives in any script that runs
|
|
# on macOS. bash before 4.4 — including the 3.2 that macOS still ships as
|
|
# /bin/bash — treats that form on an *empty* array as an unbound variable under
|
|
# `set -u` and aborts. The portable form is `${arr[@]+"${arr[@]}"}`. This is a
|
|
# static check because no bash 5 host can reproduce the abort at runtime: the
|
|
# construct is only fatal on the older shell, so absence of the construct is the
|
|
# property to assert. `${#arr[@]}` is deliberately not flagged — the count form
|
|
# is safe on 3.2. Neither is an array seeded with at least one element where it
|
|
# is declared and never reset to empty: it cannot be empty at any expansion
|
|
# site, so the construct is not a hazard there and demanding the guarded form
|
|
# would be a wrong test. The file list covers every script this repo ships or
|
|
# runs that a macOS user reaches: the wrapper itself, the two pre-commit hook
|
|
# scripts, and the test runner AGENTS.md tells contributors to run by hand.
|
|
# `mapfile` is checked alongside, because it is bash 4.0+ and the expansion scan
|
|
# cannot see it — run-tests.sh carried one until it was replaced with a
|
|
# `while read` loop, and nothing would have caught its return.
|
|
echo ""
|
|
echo "--- no unguarded array expansion remains in the macOS-facing scripts ---"
|
|
unguarded_expansions() {
|
|
local file="$1" hit name
|
|
while IFS= read -r hit; do
|
|
name="$(printf '%s\n' "$hit" \
|
|
| grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\[@\]\}' | head -1 \
|
|
| sed -E 's/^\$\{//; s/\[@\]\}$//')"
|
|
if grep -qE "^[[:space:]]*((local|declare|readonly)[[:space:]]+)?(-a[[:space:]]+)?$name=\([^)]" "$file" \
|
|
&& ! grep -qE "^[[:space:]]*$name=\(\)" "$file"; then
|
|
continue
|
|
fi
|
|
printf '%s:%s\n' "${file##*/}" "$hit"
|
|
done < <(
|
|
# Blank out whole-line comments (keeping line numbers), delete every
|
|
# correctly guarded expansion, then anything still matching is a candidate.
|
|
awk '{ if ($0 ~ /^[[:space:]]*#/) print ""; else print }' "$file" \
|
|
| sed -E 's/\$\{([A-Za-z_][A-Za-z0-9_]*)\[@\]\+"\$\{\1\[@\]\}"\}//g' \
|
|
| grep -nE '\$\{[A-Za-z_][A-Za-z0-9_]*\[@\]\}' || true
|
|
)
|
|
}
|
|
HAZARDS16=""
|
|
for BASH32_SCRIPT in \
|
|
"$SCRIPT" \
|
|
"$REPO_ROOT/scripts/skill-size-check.sh" \
|
|
"$REPO_ROOT/scripts/check-release-needed.sh" \
|
|
"$REPO_ROOT/tests/run-tests.sh"; do
|
|
FOUND16="$(unguarded_expansions "$BASH32_SCRIPT")"
|
|
if [[ -n "$FOUND16" ]]; then
|
|
HAZARDS16+="$FOUND16 "
|
|
fi
|
|
# `mapfile`/`readarray` are bash 4.0+ builtins with no 3.2 fallback. Whole-line
|
|
# comments are blanked first so prose naming the builtin is not a hit.
|
|
FOUND16B="$(awk '{ if ($0 ~ /^[[:space:]]*#/) print ""; else print }' "$BASH32_SCRIPT" \
|
|
| grep -nE '(^|[^[:alnum:]_])(mapfile|readarray)[[:space:]]' || true)"
|
|
if [[ -n "$FOUND16B" ]]; then
|
|
HAZARDS16+="${BASH32_SCRIPT##*/}:$FOUND16B "
|
|
fi
|
|
done
|
|
if [[ -n "$HAZARDS16" ]]; then
|
|
fail "unguarded array expansion(s) abort on bash < 4.4 under set -u: $(echo "$HAZARDS16" | tr '\n' ' ')"
|
|
else
|
|
pass "every array expansion uses the bash-3.2-safe \${arr[@]+\"\${arr[@]}\"} form"
|
|
fi
|
|
|
|
# --- 17. The invocations whose arrays are closest to empty actually run. Under
|
|
# a bash older than 4.4 this is genuine macOS-shell coverage; on a modern bash it
|
|
# degrades to a smoke test, so the pass message names the shell that really ran.
|
|
# Point VALE_WRAP_TEST_BASH at a 3.2 build to get the real thing in CI.
|
|
echo ""
|
|
echo "--- degenerate invocations survive on the oldest available bash ---"
|
|
OLD_BASH="bash"
|
|
OLD_BASH_VER="$(bash -c 'echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"')"
|
|
for CAND in "${VALE_WRAP_TEST_BASH:-}" bash-3.2 bash3 /bin/bash /usr/local/bin/bash; do
|
|
[[ -n "$CAND" ]] && command -v "$CAND" >/dev/null 2>&1 || continue
|
|
CAND_VER="$("$CAND" -c 'echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"' 2>/dev/null)" || continue
|
|
[[ -n "$CAND_VER" ]] || continue
|
|
if (( ${CAND_VER%.*} * 100 + ${CAND_VER#*.} < ${OLD_BASH_VER%.*} * 100 + ${OLD_BASH_VER#*.} )); then
|
|
OLD_BASH="$CAND"
|
|
OLD_BASH_VER="$CAND_VER"
|
|
fi
|
|
done
|
|
FIXTURE17="$(make_fixture 2)"
|
|
mkdir -p "$FIXTURE17/emptydir"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12" "$STUB13" "$FIXTURE14" "$FIXTURE17"' EXIT
|
|
# Zero args, flags with no path, and a directory that walks to nothing are the
|
|
# three shapes that leave vale_args/path_args/argv_paths at their emptiest.
|
|
OUT17=""
|
|
for ARGS17 in "" "--config $VALE_CONFIG" "--config $VALE_CONFIG emptydir"; do
|
|
# shellcheck disable=SC2086 # deliberate word splitting of the argv fixture
|
|
OUT17+="$( (cd "$FIXTURE17" && "$OLD_BASH" "$SCRIPT" $ARGS17 </dev/null 2>&1) || true)"
|
|
done
|
|
if echo "$OUT17" | grep -q "unbound variable"; then
|
|
fail "aborted with 'unbound variable' on bash $OLD_BASH_VER — the bug this test guards against"
|
|
else
|
|
pass "degenerate invocations run clean under bash $OLD_BASH_VER ($OLD_BASH)"
|
|
fi
|
|
|
|
# --- 18. The guarded expansion must keep argv word boundaries intact. Dropping
|
|
# the quotes (`${arr[@]}`) also silences the unbound-variable abort, so it is the
|
|
# tempting wrong fix — and it splits any path containing a space into two bogus
|
|
# arguments. Case 15 covers spaces found by the directory walk; this covers a
|
|
# space in the path argument itself, which is what argv_paths expands.
|
|
echo ""
|
|
echo "--- a path argument containing a space survives the guarded expansion ---"
|
|
FIXTURE18="$(make_fixture 2)"
|
|
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" "$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12" "$STUB13" "$FIXTURE14" "$FIXTURE17" "$FIXTURE18"' EXIT
|
|
SPACED18="$FIXTURE18/plugins/testplugin/skills/zzz skill dir"
|
|
mkdir -p "$SPACED18"
|
|
mv "$FIXTURE18/plugins/testplugin/skills/zzzskill/SKILL.md" "$SPACED18/SKILL.md"
|
|
OUT18=$(run_wrap "$FIXTURE18" --config "$VALE_CONFIG" "plugins/testplugin/skills/zzz skill dir/SKILL.md")
|
|
if echo "$OUT18" | grep -q "zzz skill dir/SKILL.md" && echo "$OUT18" | grep -q "VagueWording"; then
|
|
pass "a path argument with a space is passed to vale as one word"
|
|
else
|
|
fail "a path argument with a space was split by the array expansion: $OUT18"
|
|
fi
|
|
|
|
# The cases below share one cleanup list. The per-case trap rebuilding above
|
|
# does not scale past the fixture count it already carries, and this trap is
|
|
# installed last, so it is the one that runs.
|
|
EXTRA_FIXTURES=()
|
|
new_fixture() { EXTRA_FIXTURES+=("$1"); }
|
|
cleanup_all() {
|
|
rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5" "$FIXTURE6" \
|
|
"$FIXTURE7" "$FIXTURE8" "$FIXTURE10" "$FIXTURE11" "$FIXTURE12" "$STUB13" \
|
|
"$FIXTURE14" "$FIXTURE17" "$FIXTURE18" \
|
|
${EXTRA_FIXTURES[@]+"${EXTRA_FIXTURES[@]}"}
|
|
}
|
|
trap cleanup_all EXIT
|
|
|
|
# --- 19. Every YAML form whose parsed value is joined back out of 2+ physical
|
|
# lines breaks the `text.frontmatter.description` scope identically, not just
|
|
# the `>` folded block the flattener originally handled: a plain scalar wrapped
|
|
# onto continuation lines, a double-quoted one, a single-quoted one, and a bare
|
|
# `description:` whose value starts on the next line all report zero alerts
|
|
# under bare vale. Each must come back with the same alerts as the single-line
|
|
# spelling of the same sentence. Line and column numbers legitimately move (the
|
|
# value lands on one physical line), so the comparison drops the `line:col`
|
|
# prefix and compares the alert text — message, matched token, and rule name.
|
|
REL_SKILL19="plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
DESC19_A="Use when the caller helps with a specific job"
|
|
DESC19_B="and the second physical line will utilize the wrap"
|
|
|
|
# make_form_fixture spells the same two-clause description in one YAML scalar
|
|
# form: single, folded, plain, dquote, squote, or keyonly.
|
|
make_form_fixture() {
|
|
local form="$1" dir
|
|
dir="$(mktemp -d)"
|
|
new_fixture "$dir"
|
|
(cd "$dir" && git init -q)
|
|
mkdir -p "$dir/plugins/testplugin/skills/zzzskill"
|
|
{
|
|
echo "---"
|
|
echo "name: zzzskill"
|
|
case "$form" in
|
|
single) echo "description: $DESC19_A $DESC19_B" ;;
|
|
folded) echo "description: >"; echo " $DESC19_A"; echo " $DESC19_B" ;;
|
|
plain) echo "description: $DESC19_A"; echo " $DESC19_B" ;;
|
|
dquote) echo "description: \"$DESC19_A"; echo " $DESC19_B\"" ;;
|
|
squote) echo "description: '$DESC19_A"; echo " $DESC19_B'" ;;
|
|
keyonly) echo "description:"; echo " $DESC19_A"; echo " $DESC19_B" ;;
|
|
*) echo "make_form_fixture: unknown form '$form'" >&2; exit 1 ;;
|
|
esac
|
|
echo "---"
|
|
echo ""
|
|
echo "Body."
|
|
} > "$dir/plugins/testplugin/skills/zzzskill/SKILL.md"
|
|
echo "$dir"
|
|
}
|
|
|
|
# Alert text with the `line:col` prefix and ANSI colouring stripped, sorted.
|
|
# The `|| true` matters under this file's `set -o pipefail`: a report with no
|
|
# alerts at all makes grep exit 1, which would abort the whole run inside the
|
|
# command substitutions below — silently, before the empty-baseline guard could
|
|
# print anything. Returning empty output instead is what makes that guard
|
|
# reachable.
|
|
alert_text() {
|
|
echo "$1" \
|
|
| sed -E 's/\x1b\[[0-9;]*m//g' \
|
|
| { grep -oE '(error|warning|suggestion)[[:space:]]+.*' || true; } \
|
|
| sed -E 's/[[:space:]]+/ /g' \
|
|
| sort
|
|
}
|
|
|
|
echo ""
|
|
echo "--- every multi-line description form reports what its single-line form reports ---"
|
|
FIXTURE19_SINGLE="$(make_form_fixture single)"
|
|
BASELINE19="$(alert_text "$(run_wrap "$FIXTURE19_SINGLE" --config "$VALE_CONFIG" "$REL_SKILL19")")"
|
|
if [[ -z "$BASELINE19" ]]; then
|
|
# The loop below has to be skipped, not merely reported on: an empty baseline
|
|
# compares equal to five empty results, so it would print five vacuous PASSes
|
|
# alongside this one FAIL. The FAIL alone still fails the run at the end.
|
|
fail "the single-line baseline reported nothing — the comparisons below would be vacuous, so they are skipped"
|
|
else
|
|
for FORM19 in folded plain dquote squote keyonly; do
|
|
DIR19="$(make_form_fixture "$FORM19")"
|
|
BARE19="$(cd "$DIR19" && vale --config "$VALE_CONFIG" "$REL_SKILL19" 2>&1 || true)"
|
|
GOT19="$(alert_text "$(run_wrap "$DIR19" --config "$VALE_CONFIG" "$REL_SKILL19")")"
|
|
if echo "$BARE19" | grep -q "VagueWording"; then
|
|
fail "bare vale already flags the $FORM19 form, so this case can't detect a silently-skipped flattening"
|
|
elif [[ "$GOT19" == "$BASELINE19" ]]; then
|
|
pass "a $FORM19 multi-line description reports the same alerts as its single-line form"
|
|
else
|
|
fail "a $FORM19 multi-line description diverged from its single-line form: got [$GOT19]"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# --- 20. A style token containing an ASCII apostrophe matches inside a
|
|
# flattened description. The flattener used to substitute U+2019 for every `'`
|
|
# before writing the scratch copy, so no rule whose token carried an apostrophe
|
|
# could ever fire on a flattened description — a silent, rule-shaped blind spot.
|
|
# All three branches that can hold an apostrophe are exercised: a value that is
|
|
# safe unquoted; one that must be quoted (it contains `: `) and so lands in a
|
|
# double-quoted scalar, since a single-quoted one would need the `''` escape
|
|
# that kills the scope outright; and one that also holds a double quote, which
|
|
# no inline scalar can spell verbatim and which therefore lands in a `|-`
|
|
# literal block.
|
|
echo ""
|
|
echo "--- a style token containing an apostrophe matches in a flattened description ---"
|
|
APOS_STYLE="$(mktemp -d)"
|
|
new_fixture "$APOS_STYLE"
|
|
mkdir -p "$APOS_STYLE/styles/Apostrophe"
|
|
cat > "$APOS_STYLE/styles/Apostrophe/Token.yml" <<'EOF'
|
|
extends: existence
|
|
message: "apostrophe token: '%s'"
|
|
level: error
|
|
scope: text.frontmatter.description
|
|
ignorecase: true
|
|
tokens:
|
|
- "user's task"
|
|
EOF
|
|
# A body-scoped companion rule, used by case 20b to read back the line number of
|
|
# a line *after* the frontmatter — the only way to catch the blank-line pad
|
|
# being off in either direction.
|
|
cat > "$APOS_STYLE/styles/Apostrophe/Body.yml" <<'EOF'
|
|
extends: existence
|
|
message: "body token: '%s'"
|
|
level: error
|
|
scope: text
|
|
tokens:
|
|
- flattening marker phrase
|
|
EOF
|
|
cat > "$APOS_STYLE/.vale.ini" <<'EOF'
|
|
StylesPath = styles
|
|
|
|
[**/SKILL.md]
|
|
BasedOnStyles = Apostrophe
|
|
EOF
|
|
FIXTURE20_PLAIN="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Use when the user's task needs handling, and a second physical
|
|
line continues the folded scalar.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
new_fixture "$FIXTURE20_PLAIN"
|
|
FIXTURE20_QUOTED="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Triggers on: the user's task needing handling, and a second
|
|
physical line continues the folded scalar.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
)"
|
|
new_fixture "$FIXTURE20_QUOTED"
|
|
# Needs quoting (`: `), holds an apostrophe AND a double quote — the one
|
|
# combination no inline scalar can carry, so this is the `|-` literal-block
|
|
# branch. The VagueWording tokens are there for case 20b, which reuses it.
|
|
FIXTURE20_BLOCK="$(make_raw_fixture <<'EOF'
|
|
---
|
|
name: zzzskill
|
|
description: >
|
|
Triggers on: the user's task and "audit this" phrasing, which helps
|
|
with and utilize things across a second physical line.
|
|
---
|
|
|
|
Body carrying a flattening marker phrase for the line-number check.
|
|
EOF
|
|
)"
|
|
new_fixture "$FIXTURE20_BLOCK"
|
|
for CASE20 in "unquoted:$FIXTURE20_PLAIN" "double-quoted:$FIXTURE20_QUOTED" \
|
|
"literal-block:$FIXTURE20_BLOCK"; do
|
|
if run_wrap "${CASE20#*:}" --config "$APOS_STYLE/.vale.ini" "$REL_SKILL19" \
|
|
| grep -q "Apostrophe.Token"; then
|
|
pass "an apostrophe-bearing token matches in a flattened ${CASE20%%:*} description"
|
|
else
|
|
fail "an apostrophe-bearing token was rewritten out of a flattened ${CASE20%%:*} description"
|
|
fi
|
|
done
|
|
|
|
# --- 20b. The `|-` literal-block branch that case 20 just proved lossless must
|
|
# also keep the rest of the scope working and keep the line accounting right.
|
|
# The block is 2 physical lines where every inline form is 1, so the blank-line
|
|
# pad that preserves later line numbers has to drop by one. The second
|
|
# assertion pins that arithmetic against the body line's true number: case 3's
|
|
# `<= original line count` bound would not, since a pad that is one line short
|
|
# shifts every later line *up*, staying inside the bound while still lying.
|
|
echo ""
|
|
echo "--- the |- literal-block fallback lints normally and preserves line numbers ---"
|
|
OUT20B=$(run_wrap "$FIXTURE20_BLOCK" --config "$VALE_CONFIG" "$REL_SKILL19")
|
|
if echo "$OUT20B" | grep -q "VagueWording"; then
|
|
pass "a description needing quotes with both an apostrophe and a double quote is still linted"
|
|
else
|
|
fail "a description needing quotes with both an apostrophe and a double quote produced no alerts"
|
|
fi
|
|
WANT20B_LINE="$(grep -n 'flattening marker phrase' "$FIXTURE20_BLOCK/$REL_SKILL19" | cut -d: -f1)"
|
|
# `--output line` prints `file:line:col:Rule:message`, so the line number reads
|
|
# back without any wrapping or colour to strip.
|
|
GOT20B_LINE="$(run_wrap "$FIXTURE20_BLOCK" --config "$APOS_STYLE/.vale.ini" --output line "$REL_SKILL19" \
|
|
| grep 'Apostrophe.Body' | head -1 | cut -d: -f2)"
|
|
if [[ "$GOT20B_LINE" == "$WANT20B_LINE" ]]; then
|
|
pass "a body line after a |- flattened description keeps its original line number ($WANT20B_LINE)"
|
|
else
|
|
fail "the |- block's blank-line pad shifted the body: vale reported line $GOT20B_LINE, the file has it at $WANT20B_LINE"
|
|
fi
|
|
|
|
# --- 21. A symlinked file inside a directory argument is mirrored and linted.
|
|
# Vale follows symlinks (both a symlinked file and a file under a symlinked
|
|
# directory), so a `-type f` walk of the tree reported "0 files" where bare vale
|
|
# reports one — and the audit skills read a "0 files" report as NOT RUN.
|
|
echo ""
|
|
echo "--- mirrors a symlinked file reached through a directory argument ---"
|
|
FIXTURE21="$(make_fixture 2)"
|
|
new_fixture "$FIXTURE21"
|
|
mkdir -p "$FIXTURE21/real"
|
|
mv "$FIXTURE21/$REL_SKILL19" "$FIXTURE21/real/SKILL.md"
|
|
ln -s ../../../../real/SKILL.md "$FIXTURE21/$REL_SKILL19"
|
|
BARE21="$(cd "$FIXTURE21" && vale --config "$VALE_CONFIG" plugins 2>&1 || true)"
|
|
WRAPPED21="$(run_wrap "$FIXTURE21" --config "$VALE_CONFIG" plugins)"
|
|
BARE21_FILES="$(echo "$BARE21" | sed -E 's/\x1b\[[0-9;]*m//g' | grep -oE 'in [0-9]+ files?' | tail -1)"
|
|
WRAPPED21_FILES="$(echo "$WRAPPED21" | sed -E 's/\x1b\[[0-9;]*m//g' | grep -oE 'in [0-9]+ files?' | tail -1)"
|
|
if [[ "$BARE21_FILES" != "in 1 file" ]]; then
|
|
fail "bare vale did not lint the symlinked file ($BARE21_FILES), so this case can't detect the walk dropping it"
|
|
elif [[ "$WRAPPED21_FILES" != "$BARE21_FILES" ]]; then
|
|
fail "the directory walk dropped a symlinked file: wrapper saw '$WRAPPED21_FILES', bare vale '$BARE21_FILES'"
|
|
elif echo "$WRAPPED21" | grep -q "VagueWording"; then
|
|
pass "a symlinked file under a directory argument is mirrored, flattened and flagged"
|
|
else
|
|
fail "a symlinked file was mirrored but not flattened — no alert came back"
|
|
fi
|
|
|
|
# --- 22. The value of a separated two-argv flag is never treated as a lint
|
|
# target, however file-like it looks. `--output tmpl.tmpl` names a real
|
|
# template file: classifying it as input both linted the template and reordered
|
|
# argv, so vale received `--output --no-wrap` and died on `open :`.
|
|
echo ""
|
|
echo "--- a separated flag value that names a real file is not linted as a target ---"
|
|
FIXTURE22="$(make_fixture 1)"
|
|
new_fixture "$FIXTURE22"
|
|
printf 'TMPL{{range .Files}} {{.Path}}{{end}}\n' > "$FIXTURE22/tmpl.tmpl"
|
|
WRAPPED22="$(run_wrap "$FIXTURE22" --config "$VALE_CONFIG" --output tmpl.tmpl --no-wrap "$REL_SKILL19")"
|
|
BARE22="$(cd "$FIXTURE22" && vale --config "$VALE_CONFIG" --output tmpl.tmpl --no-wrap "$REL_SKILL19" 2>&1 || true)"
|
|
# The fixture's description is a single physical line, so flattening is a no-op
|
|
# and the two invocations must agree byte for byte.
|
|
if [[ "$WRAPPED22" == "$BARE22" ]]; then
|
|
pass "a separated --output value is passed through to vale, not linted"
|
|
else
|
|
fail "a separated --output value was misrouted: wrapper gave [$WRAPPED22], bare vale [$BARE22]"
|
|
fi
|
|
|
|
# --- 23. A path argument that does not exist is a hard error. Bare vale drops
|
|
# it, falls back to stdin and prints `0 errors ... in stdin` with exit 0, so a
|
|
# typo'd target is indistinguishable from a clean run — and the audit skills'
|
|
# NOT RUN guard string-matches `0 files`, which `in stdin` never produces. This
|
|
# is a deliberate divergence from bare vale, documented in the wrapper header.
|
|
echo ""
|
|
echo "--- a nonexistent path argument fails loudly instead of falling back to stdin ---"
|
|
set +e
|
|
OUT23="$(cd "$FIXTURE22" && bash "$SCRIPT" --config "$VALE_CONFIG" plugins/testplugin/skills/zzzskill/SKILLL.md 2>&1)"
|
|
RC23=$?
|
|
set -e
|
|
if [[ $RC23 -eq 0 ]]; then
|
|
fail "a typo'd path exited 0 — indistinguishable from a clean run, the bug this test guards against"
|
|
elif echo "$OUT23" | grep -q "in stdin"; then
|
|
fail "a typo'd path fell back to reading stdin and reported 'in stdin' instead of erroring"
|
|
elif echo "$OUT23" | grep -q "SKILLL.md"; then
|
|
pass "a typo'd path exits nonzero with a message naming the path"
|
|
else
|
|
fail "a typo'd path exited $RC23 but the message does not name it: $OUT23"
|
|
fi
|
|
|
|
# --- 24. `--output`'s built-in style names must not be path-absolutized. The
|
|
# wrapper rewrites path-valued flag values to absolute form so they still
|
|
# resolve after the `cd` into the scratch mirror, deciding with an `-e`
|
|
# existence test — but `line`, `JSON` and `CLI` are style names, not paths. With
|
|
# a file or directory of that name sitting in the caller's cwd the test hit, the
|
|
# built-in became `$cwd/line`, and vale flipped into template mode and died with
|
|
# `E100 [template] Runtime error` where bare vale prints a normal report.
|
|
echo ""
|
|
echo "--- a built-in --output style name survives a same-named entry in the cwd ---"
|
|
FIXTURE24="$(make_fixture 2)"
|
|
new_fixture "$FIXTURE24"
|
|
mkdir -p "$FIXTURE24/line"
|
|
: > "$FIXTURE24/JSON"
|
|
for FORM24 in "--output line" "--output=line" "--output JSON" "--output=JSON"; do
|
|
# shellcheck disable=SC2086 # deliberate word splitting of the argv fixture
|
|
OUT24="$(run_wrap "$FIXTURE24" --config "$VALE_CONFIG" $FORM24 "$REL_SKILL19")"
|
|
if echo "$OUT24" | grep -q "E100"; then
|
|
fail "'$FORM24' was rewritten to a cwd path and vale flipped into template mode — the bug this test guards against"
|
|
elif echo "$OUT24" | grep -q "VagueWording"; then
|
|
pass "'$FORM24' is passed through as a built-in style name"
|
|
else
|
|
fail "'$FORM24' produced no alert: $OUT24"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[[ $FAIL -eq 0 ]]
|