@boyingliu01/xp-gate 0.33.2 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gate-13-compiler-strictness.sh +129 -4
- package/gate-4.sh +8 -1
- package/hooks/gate-13-compiler-strictness.sh +129 -4
- package/hooks/gate-4.sh +8 -1
- package/hooks/lib/assertion-quality.sh +232 -0
- package/hooks/lib/diff-coverage.sh +223 -0
- package/hooks/pre-commit +175 -0
- package/lib/__tests__/foreign-hooks-global.test.js +125 -0
- package/lib/__tests__/uninstall-global-residue.test.js +160 -0
- package/lib/doctor.js +70 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/uninstall.js +225 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-driven-development/SKILL.md +19 -0
- package/plugins/claude-code/skills/test-driven-development/testing-anti-patterns.md +31 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/delphi-review/SKILL.md +30 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-driven-development/SKILL.md +19 -0
- package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +31 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/delphi-review/SKILL.md +30 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-driven-development/SKILL.md +19 -0
- package/plugins/opencode/skills/test-driven-development/testing-anti-patterns.md +31 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/delphi-review/SKILL.md +30 -0
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-driven-development/SKILL.md +19 -0
- package/plugins/qoder/skills/test-driven-development/testing-anti-patterns.md +31 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/principles/rules/solid/dip.ts +94 -14
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/delphi-review/SKILL.md +30 -0
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-driven-development/SKILL.md +19 -0
- package/skills/test-driven-development/testing-anti-patterns.md +31 -0
- package/skills/test-specification-alignment/AGENTS.md +2 -2
|
@@ -501,6 +501,108 @@ gate13_javac_args() {
|
|
|
501
501
|
printf '%s\n' "-Xlint:all,-processing -d"
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
# ---------------------------------------------------------------------------
|
|
505
|
+
# AC-670-34 / AC-670-35 (#659) — counting javac lint findings, locale-proof.
|
|
506
|
+
#
|
|
507
|
+
# The previous detector was an allow-list of English `[category]` names applied to
|
|
508
|
+
# `[WARNING]` lines. Two independent ways it returned 0 on defective code:
|
|
509
|
+
#
|
|
510
|
+
# 1. NOVELTY: a JDK adds a category the list does not name (`future-category`),
|
|
511
|
+
# or the fix relies on the list staying in sync with javac. A fixed allow-list
|
|
512
|
+
# over an open enum is a fail-OPEN design.
|
|
513
|
+
# 2. LOCALISATION: on a non-English build javac's own hint drifts, and the
|
|
514
|
+
# `[WARNING]` prefix may too. Measured on a zh_CN Maven build, the tell is
|
|
515
|
+
# "使用了未经检查或不安全的操作 ... 请使用 -Xlint:unchecked 重新编译" -- a message
|
|
516
|
+
# javac emits precisely when -Xlint was NOT in effect.
|
|
517
|
+
#
|
|
518
|
+
# Fail-closed policy (#499/#570) applied to text matching: count any bracketed
|
|
519
|
+
# lowercase category (whatever it is named), and treat javac's "you should have
|
|
520
|
+
# used -Xlint" hint as a finding too -- the code has an unchecked/unsafe operation
|
|
521
|
+
# whether or not the flag that would name it was on.
|
|
522
|
+
#
|
|
523
|
+
# Prints a single integer. Never fails (always exits 0).
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
gate13_count_lint_findings() {
|
|
526
|
+
local src="${1:-}"
|
|
527
|
+
[ -n "$src" ] || src=$(cat)
|
|
528
|
+
printf '%s\n' "$src" | awk '
|
|
529
|
+
BEGIN { n = 0 }
|
|
530
|
+
# Any bracketed lowercase category: covers the whole javac -Xlint enum and any
|
|
531
|
+
# later addition to it. Restricted to lowercase/digit/hyphen so a Maven
|
|
532
|
+
# `[WARNING]` with no category, or a `[INFO]` banner, is not counted.
|
|
533
|
+
/\[[a-z][a-z0-9-]*\]/ { n++; next }
|
|
534
|
+
# javac localised or English: "warning:" prefix at line start.
|
|
535
|
+
/^[[:space:]]*warning:/ { n++; next }
|
|
536
|
+
# Unchecked/unsafe operations reported from any locale. The English and Chinese
|
|
537
|
+
# wordings are both matched; anything else falls through to the category rule
|
|
538
|
+
# above, which is marker-based and therefore locale-independent.
|
|
539
|
+
/unchecked|unsafe|未经检查|不安全/ { n++; next }
|
|
540
|
+
END { print n + 0 }
|
|
541
|
+
'
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# AC-670-32 (#659) — PROVE that -Xlint takes effect, do not assume it.
|
|
547
|
+
#
|
|
548
|
+
# "Can assemble the flag" is not "the flag works". `compilerArgument` is a single
|
|
549
|
+
# appended argument, so `-Xlint:all,-processing` (a comma list) is not reliably
|
|
550
|
+
# passed through -- measured on Maven 3.9.12 + JDK 21, where the injected run
|
|
551
|
+
# produced no `[category]` markers at all and javac printed its own
|
|
552
|
+
# "recompile with -Xlint:unchecked" hint instead.
|
|
553
|
+
#
|
|
554
|
+
# The only honest test is a probe: compile a source that is KNOWN to warn (a raw
|
|
555
|
+
# type is the canonical one -- measured to emit `[rawtypes]` under -Xlint:all and
|
|
556
|
+
# nothing otherwise) using the exact same command shape, and look for the marker.
|
|
557
|
+
#
|
|
558
|
+
# Returns 0 = injection proven effective, 1 = not proven (caller must degrade).
|
|
559
|
+
# ---------------------------------------------------------------------------
|
|
560
|
+
gate13_maven_lint_effective() {
|
|
561
|
+
local mvn_bin="${1:-mvn}"
|
|
562
|
+
local budget tmpd probe out rc
|
|
563
|
+
budget=$(gate13_timeout_budget)
|
|
564
|
+
tmpd="${TMPDIR:-/tmp}/gate13_probe_$$"
|
|
565
|
+
mkdir -p "$tmpd" || return 1
|
|
566
|
+
|
|
567
|
+
# Raw type on purpose: javac emits NOTHING for this without -Xlint:all, so a
|
|
568
|
+
# silent run proves the flag did not reach the compiler.
|
|
569
|
+
probe="$tmpd/Gate13Probe.java"
|
|
570
|
+
cat > "$probe" <<'PROBE'
|
|
571
|
+
import java.util.ArrayList;
|
|
572
|
+
import java.util.List;
|
|
573
|
+
public class Gate13Probe {
|
|
574
|
+
private List items = new ArrayList();
|
|
575
|
+
public void add(Object o) { items.add(o); }
|
|
576
|
+
}
|
|
577
|
+
PROBE
|
|
578
|
+
|
|
579
|
+
# Same argument shape as gate13_maven_args(), against the probe only. A single
|
|
580
|
+
# -Djavac directly is not what we are testing -- the whole point is whether the
|
|
581
|
+
# MAVEN injection path works.
|
|
582
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
583
|
+
out=$(timeout "$budget" javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
584
|
+
else
|
|
585
|
+
out=$(javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
586
|
+
fi
|
|
587
|
+
rc=$?
|
|
588
|
+
rm -rf "$tmpd"
|
|
589
|
+
|
|
590
|
+
# If the DIRECT javac probe cannot even produce a rawtypes warning, the JDK
|
|
591
|
+
# itself is not behaving as measured, so nothing about the Maven path can be
|
|
592
|
+
# concluded -- report "not proven" rather than "effective".
|
|
593
|
+
if [ "$rc" -ne 0 ]; then
|
|
594
|
+
return 1
|
|
595
|
+
fi
|
|
596
|
+
printf '%s\n' "$out" | grep -q 'rawtypes'
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
# Is `-Xlint` reachable at all on this machine? The Maven probe only makes sense
|
|
600
|
+
# when a raw-type warning is obtainable; when javac cannot produce one, callers
|
|
601
|
+
# stay in degraded mode instead of claiming a clean lint.
|
|
602
|
+
gate13_maven_probe_available() {
|
|
603
|
+
command -v javac >/dev/null 2>&1
|
|
604
|
+
}
|
|
605
|
+
|
|
504
606
|
gate13_java_sources() {
|
|
505
607
|
git diff --cached --name-only --diff-filter=d -- '*.java' 2>/dev/null
|
|
506
608
|
}
|
|
@@ -574,6 +676,23 @@ gate13_maven_injection_reliable() {
|
|
|
574
676
|
return 1
|
|
575
677
|
fi
|
|
576
678
|
|
|
679
|
+
# AC-670-33 (#659): every check above verifies that the arguments can be
|
|
680
|
+
# ASSEMBLED. None of them verifies that they take EFFECT, which is exactly how a
|
|
681
|
+
# pom declaring plugin 3.13.0 was declared "reliable" while Maven ran with no
|
|
682
|
+
# -Xlint at all and Gate 13 reported PASS on a file javac warned about 3 times.
|
|
683
|
+
#
|
|
684
|
+
# So prove it: one probe compile of a known-raw-type source through the same
|
|
685
|
+
# command shape. If the marker does not appear, injection did not happen and the
|
|
686
|
+
# caller must degrade -- "could not verify" is never "verified fine" (#499/#570).
|
|
687
|
+
if ! gate13_maven_probe_available; then
|
|
688
|
+
printf 'javac unavailable; -Xlint injection cannot be probed\n' >&2
|
|
689
|
+
return 1
|
|
690
|
+
fi
|
|
691
|
+
if ! gate13_maven_lint_effective "${MVN_BIN:-mvn}"; then
|
|
692
|
+
printf 'probe compile did not produce a [rawtypes] marker; -Xlint injection is NOT effective\n' >&2
|
|
693
|
+
return 1
|
|
694
|
+
fi
|
|
695
|
+
|
|
577
696
|
return 0
|
|
578
697
|
}
|
|
579
698
|
|
|
@@ -704,14 +823,20 @@ gate13_run_java_analysis() {
|
|
|
704
823
|
fi
|
|
705
824
|
|
|
706
825
|
# Count javac's lint output. Maven prefixes it with "[WARNING] ".
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
826
|
+
#
|
|
827
|
+
# #659: this used to be an English `[WARNING]...[allow-listed-category]`
|
|
828
|
+
# regex. Applied to a localised build -- or to a JDK category the list does
|
|
829
|
+
# not name -- it returned 0 on code javac had warned about, and the gate then
|
|
830
|
+
# printed PASSED. The shared counter below is marker- and locale-proof and
|
|
831
|
+
# fails closed (#499/#570). It lives in one place so the Maven path and the
|
|
832
|
+
# direct-javac path can never disagree about what counts as a finding.
|
|
833
|
+
mvn_findings=$(gate13_count_lint_findings "$mvn_out")
|
|
710
834
|
mvn_findings=${mvn_findings:-0}
|
|
835
|
+
case "$mvn_findings" in ''|*[!0-9]*) mvn_findings=0 ;; esac
|
|
711
836
|
|
|
712
837
|
if [ "$mvn_findings" -gt 0 ]; then
|
|
713
838
|
printf '%s\n' "$mvn_out" \
|
|
714
|
-
| grep -E '\[
|
|
839
|
+
| grep -E '\[[a-z][a-z0-9-]*\]|warning:' | head -10 | sed 's/^/ /'
|
|
715
840
|
echo " ❌ ${mvn_findings} javac -Xlint finding(s)"
|
|
716
841
|
return "$GATE13_FAIL"
|
|
717
842
|
fi
|
package/gate-4.sh
CHANGED
|
@@ -46,7 +46,14 @@ java_coverage_note() {
|
|
|
46
46
|
esac
|
|
47
47
|
done
|
|
48
48
|
[ -n "$_has_java" ] || return 0
|
|
49
|
-
|
|
49
|
+
# #628: `docs/` is not in the npm package's `files` list, so pointing a user at
|
|
50
|
+
# `docs/java-principles-coverage.md` was a dead path in every installed project --
|
|
51
|
+
# the note told them to read a file that does not exist on their machine. Cite the
|
|
52
|
+
# upstream URL instead, and keep the local path only when the doc is actually
|
|
53
|
+
# present (i.e. when running inside this repo).
|
|
54
|
+
local _where="https://github.com/boyingliu01/xp-gate/blob/main/docs/java-principles-coverage.md"
|
|
55
|
+
[ -f "docs/java-principles-coverage.md" ] && _where="docs/java-principles-coverage.md"
|
|
56
|
+
echo "ℹ️ Java rule coverage: $JAVA_COVERAGE_EFFECTIVE/$JAVA_COVERAGE_TOTAL rules effective (inert on Java: $JAVA_COVERAGE_INERT -- see $_where)"
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
2>&1 echo ""
|
|
@@ -501,6 +501,108 @@ gate13_javac_args() {
|
|
|
501
501
|
printf '%s\n' "-Xlint:all,-processing -d"
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
# ---------------------------------------------------------------------------
|
|
505
|
+
# AC-670-34 / AC-670-35 (#659) — counting javac lint findings, locale-proof.
|
|
506
|
+
#
|
|
507
|
+
# The previous detector was an allow-list of English `[category]` names applied to
|
|
508
|
+
# `[WARNING]` lines. Two independent ways it returned 0 on defective code:
|
|
509
|
+
#
|
|
510
|
+
# 1. NOVELTY: a JDK adds a category the list does not name (`future-category`),
|
|
511
|
+
# or the fix relies on the list staying in sync with javac. A fixed allow-list
|
|
512
|
+
# over an open enum is a fail-OPEN design.
|
|
513
|
+
# 2. LOCALISATION: on a non-English build javac's own hint drifts, and the
|
|
514
|
+
# `[WARNING]` prefix may too. Measured on a zh_CN Maven build, the tell is
|
|
515
|
+
# "使用了未经检查或不安全的操作 ... 请使用 -Xlint:unchecked 重新编译" -- a message
|
|
516
|
+
# javac emits precisely when -Xlint was NOT in effect.
|
|
517
|
+
#
|
|
518
|
+
# Fail-closed policy (#499/#570) applied to text matching: count any bracketed
|
|
519
|
+
# lowercase category (whatever it is named), and treat javac's "you should have
|
|
520
|
+
# used -Xlint" hint as a finding too -- the code has an unchecked/unsafe operation
|
|
521
|
+
# whether or not the flag that would name it was on.
|
|
522
|
+
#
|
|
523
|
+
# Prints a single integer. Never fails (always exits 0).
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
gate13_count_lint_findings() {
|
|
526
|
+
local src="${1:-}"
|
|
527
|
+
[ -n "$src" ] || src=$(cat)
|
|
528
|
+
printf '%s\n' "$src" | awk '
|
|
529
|
+
BEGIN { n = 0 }
|
|
530
|
+
# Any bracketed lowercase category: covers the whole javac -Xlint enum and any
|
|
531
|
+
# later addition to it. Restricted to lowercase/digit/hyphen so a Maven
|
|
532
|
+
# `[WARNING]` with no category, or a `[INFO]` banner, is not counted.
|
|
533
|
+
/\[[a-z][a-z0-9-]*\]/ { n++; next }
|
|
534
|
+
# javac localised or English: "warning:" prefix at line start.
|
|
535
|
+
/^[[:space:]]*warning:/ { n++; next }
|
|
536
|
+
# Unchecked/unsafe operations reported from any locale. The English and Chinese
|
|
537
|
+
# wordings are both matched; anything else falls through to the category rule
|
|
538
|
+
# above, which is marker-based and therefore locale-independent.
|
|
539
|
+
/unchecked|unsafe|未经检查|不安全/ { n++; next }
|
|
540
|
+
END { print n + 0 }
|
|
541
|
+
'
|
|
542
|
+
return 0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# ---------------------------------------------------------------------------
|
|
546
|
+
# AC-670-32 (#659) — PROVE that -Xlint takes effect, do not assume it.
|
|
547
|
+
#
|
|
548
|
+
# "Can assemble the flag" is not "the flag works". `compilerArgument` is a single
|
|
549
|
+
# appended argument, so `-Xlint:all,-processing` (a comma list) is not reliably
|
|
550
|
+
# passed through -- measured on Maven 3.9.12 + JDK 21, where the injected run
|
|
551
|
+
# produced no `[category]` markers at all and javac printed its own
|
|
552
|
+
# "recompile with -Xlint:unchecked" hint instead.
|
|
553
|
+
#
|
|
554
|
+
# The only honest test is a probe: compile a source that is KNOWN to warn (a raw
|
|
555
|
+
# type is the canonical one -- measured to emit `[rawtypes]` under -Xlint:all and
|
|
556
|
+
# nothing otherwise) using the exact same command shape, and look for the marker.
|
|
557
|
+
#
|
|
558
|
+
# Returns 0 = injection proven effective, 1 = not proven (caller must degrade).
|
|
559
|
+
# ---------------------------------------------------------------------------
|
|
560
|
+
gate13_maven_lint_effective() {
|
|
561
|
+
local mvn_bin="${1:-mvn}"
|
|
562
|
+
local budget tmpd probe out rc
|
|
563
|
+
budget=$(gate13_timeout_budget)
|
|
564
|
+
tmpd="${TMPDIR:-/tmp}/gate13_probe_$$"
|
|
565
|
+
mkdir -p "$tmpd" || return 1
|
|
566
|
+
|
|
567
|
+
# Raw type on purpose: javac emits NOTHING for this without -Xlint:all, so a
|
|
568
|
+
# silent run proves the flag did not reach the compiler.
|
|
569
|
+
probe="$tmpd/Gate13Probe.java"
|
|
570
|
+
cat > "$probe" <<'PROBE'
|
|
571
|
+
import java.util.ArrayList;
|
|
572
|
+
import java.util.List;
|
|
573
|
+
public class Gate13Probe {
|
|
574
|
+
private List items = new ArrayList();
|
|
575
|
+
public void add(Object o) { items.add(o); }
|
|
576
|
+
}
|
|
577
|
+
PROBE
|
|
578
|
+
|
|
579
|
+
# Same argument shape as gate13_maven_args(), against the probe only. A single
|
|
580
|
+
# -Djavac directly is not what we are testing -- the whole point is whether the
|
|
581
|
+
# MAVEN injection path works.
|
|
582
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
583
|
+
out=$(timeout "$budget" javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
584
|
+
else
|
|
585
|
+
out=$(javac -Xlint:all -d "$tmpd" "$probe" 2>&1)
|
|
586
|
+
fi
|
|
587
|
+
rc=$?
|
|
588
|
+
rm -rf "$tmpd"
|
|
589
|
+
|
|
590
|
+
# If the DIRECT javac probe cannot even produce a rawtypes warning, the JDK
|
|
591
|
+
# itself is not behaving as measured, so nothing about the Maven path can be
|
|
592
|
+
# concluded -- report "not proven" rather than "effective".
|
|
593
|
+
if [ "$rc" -ne 0 ]; then
|
|
594
|
+
return 1
|
|
595
|
+
fi
|
|
596
|
+
printf '%s\n' "$out" | grep -q 'rawtypes'
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
# Is `-Xlint` reachable at all on this machine? The Maven probe only makes sense
|
|
600
|
+
# when a raw-type warning is obtainable; when javac cannot produce one, callers
|
|
601
|
+
# stay in degraded mode instead of claiming a clean lint.
|
|
602
|
+
gate13_maven_probe_available() {
|
|
603
|
+
command -v javac >/dev/null 2>&1
|
|
604
|
+
}
|
|
605
|
+
|
|
504
606
|
gate13_java_sources() {
|
|
505
607
|
git diff --cached --name-only --diff-filter=d -- '*.java' 2>/dev/null
|
|
506
608
|
}
|
|
@@ -574,6 +676,23 @@ gate13_maven_injection_reliable() {
|
|
|
574
676
|
return 1
|
|
575
677
|
fi
|
|
576
678
|
|
|
679
|
+
# AC-670-33 (#659): every check above verifies that the arguments can be
|
|
680
|
+
# ASSEMBLED. None of them verifies that they take EFFECT, which is exactly how a
|
|
681
|
+
# pom declaring plugin 3.13.0 was declared "reliable" while Maven ran with no
|
|
682
|
+
# -Xlint at all and Gate 13 reported PASS on a file javac warned about 3 times.
|
|
683
|
+
#
|
|
684
|
+
# So prove it: one probe compile of a known-raw-type source through the same
|
|
685
|
+
# command shape. If the marker does not appear, injection did not happen and the
|
|
686
|
+
# caller must degrade -- "could not verify" is never "verified fine" (#499/#570).
|
|
687
|
+
if ! gate13_maven_probe_available; then
|
|
688
|
+
printf 'javac unavailable; -Xlint injection cannot be probed\n' >&2
|
|
689
|
+
return 1
|
|
690
|
+
fi
|
|
691
|
+
if ! gate13_maven_lint_effective "${MVN_BIN:-mvn}"; then
|
|
692
|
+
printf 'probe compile did not produce a [rawtypes] marker; -Xlint injection is NOT effective\n' >&2
|
|
693
|
+
return 1
|
|
694
|
+
fi
|
|
695
|
+
|
|
577
696
|
return 0
|
|
578
697
|
}
|
|
579
698
|
|
|
@@ -704,14 +823,20 @@ gate13_run_java_analysis() {
|
|
|
704
823
|
fi
|
|
705
824
|
|
|
706
825
|
# Count javac's lint output. Maven prefixes it with "[WARNING] ".
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
826
|
+
#
|
|
827
|
+
# #659: this used to be an English `[WARNING]...[allow-listed-category]`
|
|
828
|
+
# regex. Applied to a localised build -- or to a JDK category the list does
|
|
829
|
+
# not name -- it returned 0 on code javac had warned about, and the gate then
|
|
830
|
+
# printed PASSED. The shared counter below is marker- and locale-proof and
|
|
831
|
+
# fails closed (#499/#570). It lives in one place so the Maven path and the
|
|
832
|
+
# direct-javac path can never disagree about what counts as a finding.
|
|
833
|
+
mvn_findings=$(gate13_count_lint_findings "$mvn_out")
|
|
710
834
|
mvn_findings=${mvn_findings:-0}
|
|
835
|
+
case "$mvn_findings" in ''|*[!0-9]*) mvn_findings=0 ;; esac
|
|
711
836
|
|
|
712
837
|
if [ "$mvn_findings" -gt 0 ]; then
|
|
713
838
|
printf '%s\n' "$mvn_out" \
|
|
714
|
-
| grep -E '\[
|
|
839
|
+
| grep -E '\[[a-z][a-z0-9-]*\]|warning:' | head -10 | sed 's/^/ /'
|
|
715
840
|
echo " ❌ ${mvn_findings} javac -Xlint finding(s)"
|
|
716
841
|
return "$GATE13_FAIL"
|
|
717
842
|
fi
|
package/hooks/gate-4.sh
CHANGED
|
@@ -46,7 +46,14 @@ java_coverage_note() {
|
|
|
46
46
|
esac
|
|
47
47
|
done
|
|
48
48
|
[ -n "$_has_java" ] || return 0
|
|
49
|
-
|
|
49
|
+
# #628: `docs/` is not in the npm package's `files` list, so pointing a user at
|
|
50
|
+
# `docs/java-principles-coverage.md` was a dead path in every installed project --
|
|
51
|
+
# the note told them to read a file that does not exist on their machine. Cite the
|
|
52
|
+
# upstream URL instead, and keep the local path only when the doc is actually
|
|
53
|
+
# present (i.e. when running inside this repo).
|
|
54
|
+
local _where="https://github.com/boyingliu01/xp-gate/blob/main/docs/java-principles-coverage.md"
|
|
55
|
+
[ -f "docs/java-principles-coverage.md" ] && _where="docs/java-principles-coverage.md"
|
|
56
|
+
echo "ℹ️ Java rule coverage: $JAVA_COVERAGE_EFFECTIVE/$JAVA_COVERAGE_TOTAL rules effective (inert on Java: $JAVA_COVERAGE_INERT -- see $_where)"
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
2>&1 echo ""
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Test-suite hygiene detectors for Gate 5, shared by every branch that needs them.
|
|
3
|
+
# Sourced by githooks/pre-commit and by the #602/#601 test batteries; kept in its
|
|
4
|
+
# own file so the contract is unit-testable without executing the hook.
|
|
5
|
+
#
|
|
6
|
+
# Why this exists at all: 指南 §9.5/§12 says a failing build must never be "fixed"
|
|
7
|
+
# by deleting assertions, widening mocks, or skipping tests. XP-Gate had no
|
|
8
|
+
# detector for the two quietest ways to do exactly that -- a committed `.only`
|
|
9
|
+
# (CI then runs one case and reports green) and a deleted test file with no
|
|
10
|
+
# replacement (the safety net vanishes with no signal). Gate 5a guarded the
|
|
11
|
+
# ADD side ("a new source file must have a test"); these functions guard the
|
|
12
|
+
# FOCUS/SKIP/DELETE side so the constraint is symmetric.
|
|
13
|
+
#
|
|
14
|
+
# The patterns live here, once, because Gate 5d (#601, assertion quality) needs
|
|
15
|
+
# the same vocabulary. Two copies of "what counts as a skip" drift apart, and the
|
|
16
|
+
# drift is invisible: each copy looks locally correct.
|
|
17
|
+
|
|
18
|
+
# JS/TS focus markers. In vitest and jest, the presence of `.only` in a file makes
|
|
19
|
+
# the whole file run ONLY that case; every other case is silently not executed and
|
|
20
|
+
# the run still reports success. `fit`/`fdescribe` are the jasmine aliases that
|
|
21
|
+
# vitest still accepts.
|
|
22
|
+
#
|
|
23
|
+
# Only the dotted/camel spellings are matched, not a bare `fit(`: `fit(` is also a
|
|
24
|
+
# common local helper name (curve fitting, layout fitting) and a false BLOCK on
|
|
25
|
+
# ordinary source would make the gate unusable. The anchored `\b(fit|fdescribe)\(`
|
|
26
|
+
# alternative below covers the alias form at a statement boundary only.
|
|
27
|
+
#
|
|
28
|
+
# Prints `line:text` for each hit. Empty output means "no focus markers".
|
|
29
|
+
classify_test_focus() {
|
|
30
|
+
grep -nE '(\.only[[:space:]]*\()|(\b(fit|fdescribe)[[:space:]]*\()' || true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Skip/todo markers across the five languages this repo gates. These are WARNINGS,
|
|
34
|
+
# not blocks: a skipped test is sometimes legitimate and always visible in the test
|
|
35
|
+
# runner, unlike `.only`. The warning is what makes the XP-Gate report say it
|
|
36
|
+
# counted fewer cases than the file contains.
|
|
37
|
+
#
|
|
38
|
+
# `@skip-justified: <reason >= 10 chars>` exempts one occurrence; see
|
|
39
|
+
# is_skip_justified() -- a bare annotation with no reason is not an exemption, so
|
|
40
|
+
# the escape hatch cannot become the default path.
|
|
41
|
+
classify_test_skip() {
|
|
42
|
+
grep -nE '(\.(skip|todo)[[:space:]]*\()|(\b(xit|xdescribe|xtest)[[:space:]]*\()|(@pytest\.mark\.skip)|(\bt\.Skip[f]?\()|(@Disabled\b)|(@Ignore\b)' || true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# A skip marker is exempt when the surrounding file carries
|
|
46
|
+
# `@skip-justified: <reason>` with a reason of at least 10 non-space characters.
|
|
47
|
+
# Splitting marker-detection from justification keeps each rule single-purpose and
|
|
48
|
+
# makes "the annotation exists but has no reason" a testable state rather than a
|
|
49
|
+
# silent pass.
|
|
50
|
+
#
|
|
51
|
+
# $1 = the source text to inspect (usually the staged file content).
|
|
52
|
+
is_skip_justified() {
|
|
53
|
+
printf '%s\n' "$1" \
|
|
54
|
+
| grep -oE '@skip-justified:[[:space:]]*[^[:space:]*].*' \
|
|
55
|
+
| sed 's/^@skip-justified:[[:space:]]*//' \
|
|
56
|
+
| tr -d '\r' \
|
|
57
|
+
| awk 'length($0) >= 10 { found = 1; exit } END { exit(found ? 0 : 1) }'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Files whose basename marks them as a test. Shared with classify_deleted_tests so
|
|
61
|
+
# "is this a test file" has one definition. Covers the naming conventions of the
|
|
62
|
+
# gated languages: *.test.ts / *.spec.js / *_test.py / test_*.py / *_test.go /
|
|
63
|
+
# *Test.java / *Tests.kt / *.Tests.ps1.
|
|
64
|
+
is_test_file_path() {
|
|
65
|
+
printf '%s' "$1" \
|
|
66
|
+
| grep -qE '(^|/)(__tests__/)?(test_[^/]+\.py|[^/]+_test\.(py|go)|[^/]+\.(test|spec)\.[a-z]+|[^/]*(Test|Tests|TestSuite)\.(java|kt|cs|scala)|[^/]+\.Tests\.ps1)$'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Deleted test files that have NO same-named replacement.
|
|
70
|
+
#
|
|
71
|
+
# A pure deletion and a rename look identical in `--name-only --diff-filter=D`;
|
|
72
|
+
# git reports the old path as deleted either way. Comparing against the added list
|
|
73
|
+
# is what separates "the safety net was removed" from "the file moved", and
|
|
74
|
+
# reporting a rename would train developers to ignore the warning.
|
|
75
|
+
#
|
|
76
|
+
# $1 = file with deleted paths (one per line), $2 = file with added paths.
|
|
77
|
+
# Prints the offending paths; empty output means every deletion was a rename.
|
|
78
|
+
classify_deleted_tests() {
|
|
79
|
+
local deleted_list added_list
|
|
80
|
+
deleted_list="$1"
|
|
81
|
+
added_list="$2"
|
|
82
|
+
[ -f "$deleted_list" ] || return 0
|
|
83
|
+
local path
|
|
84
|
+
while IFS= read -r path; do
|
|
85
|
+
[ -n "$path" ] || continue
|
|
86
|
+
is_test_file_path "$path" || continue
|
|
87
|
+
# The same basename anywhere in the added list counts as a replacement: a
|
|
88
|
+
# rename is usually same-directory, but moving a test module into
|
|
89
|
+
# `__tests__/` is the common case here and must not be flagged.
|
|
90
|
+
local base
|
|
91
|
+
base=$(printf '%s' "$path" | sed 's#.*/##')
|
|
92
|
+
if [ -f "$added_list" ] && grep -qF "$base" "$added_list" 2>/dev/null; then
|
|
93
|
+
continue
|
|
94
|
+
fi
|
|
95
|
+
printf '%s\n' "$path"
|
|
96
|
+
done < "$deleted_list"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Gate 5d (#601): assertion quality. 指南 §9.2 forbids six assertion
|
|
101
|
+
# anti-patterns; before this, nothing in XP-Gate looked at assertions at all --
|
|
102
|
+
# Gate M2/M3 examine how test doubles are built, never whether the test actually
|
|
103
|
+
# asserts anything. `expect(true).toBe(true)` and an empty `catch {}` pass every
|
|
104
|
+
# other gate while verifying nothing.
|
|
105
|
+
#
|
|
106
|
+
# Every function below maps to one anti-pattern in
|
|
107
|
+
# skills/test-driven-development/testing-anti-patterns.md, and says so, so a
|
|
108
|
+
# reviewer reading a block message knows which section to consult.
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
# Anti-pattern 1: tautological assertion -- the assertion cannot fail. Two shapes,
|
|
112
|
+
# both BLOCK, because neither is a weak test: each is the absence of one wearing an
|
|
113
|
+
# assertion's syntax.
|
|
114
|
+
#
|
|
115
|
+
# (a) literal vs identical literal: `expect(true).toBe(true)`, `expect(1).toBe(1)`
|
|
116
|
+
# (b) a literal tested for the truthiness it already has:
|
|
117
|
+
# `expect(true).toBeTruthy()`, `expect(false).toBeFalsy()`,
|
|
118
|
+
# `expect("x").toBeTruthy()`, `expect(0).toBeFalsy()`, `expect(null).toBeFalsy()`
|
|
119
|
+
#
|
|
120
|
+
# (b) cannot be caught by (a)'s backreference, and the pairs are not equivalents to
|
|
121
|
+
# compare: `toBeTruthy`/`toBeFalsy` ask a different question than `toBe`. Matching
|
|
122
|
+
# the literal's CLASS against the matcher is what distinguishes them, so the two
|
|
123
|
+
# are separate alternatives rather than one clever expression.
|
|
124
|
+
#
|
|
125
|
+
# Deliberately NOT matched: `expect(x).toBe(x)` (same variable both sides). It is
|
|
126
|
+
# common in referential-identity checks (`expect(await load(a)).toBe(await load(a))`
|
|
127
|
+
# for memoisation) and a false block there is worse than the miss. Likewise
|
|
128
|
+
# `expect(someVar).toBeTruthy()` -- a variable's truthiness is a real question.
|
|
129
|
+
classify_tautological_assertions() {
|
|
130
|
+
# Implemented in awk rather than grep because POSIX grep -E has no portable
|
|
131
|
+
# backreference support: the Windows Git Bash grep used here matched nothing with
|
|
132
|
+
# `\1`, so a literal-vs-identical-literal check cannot be expressed there.
|
|
133
|
+
#
|
|
134
|
+
# The literal is extracted with a plain ERE-substitution loop instead of gawk's
|
|
135
|
+
# 3-argument match(), which mawk/BSD awk do not provide.
|
|
136
|
+
#
|
|
137
|
+
# Accepts stdin OR $1: the hook pipes the staged content in, while the test
|
|
138
|
+
# battery passes a string. Supporting only one of the two silently makes the
|
|
139
|
+
# other read nothing and report "no violations" -- the fail-open shape this
|
|
140
|
+
# whole release is about.
|
|
141
|
+
local _aq_src
|
|
142
|
+
if [ -n "${1:-}" ]; then _aq_src="$1"; else _aq_src=$(cat); fi
|
|
143
|
+
printf '%s\n' "$_aq_src" | awk '
|
|
144
|
+
BEGIN { TOL = "[[:space:]]*" }
|
|
145
|
+
function trim(s) { gsub(/^[[:space:]]+/, "", s); gsub(/[[:space:]]+$/, "", s); return s }
|
|
146
|
+
# (a) identical literal on both sides of toBe/toEqual/toStrictEqual.
|
|
147
|
+
/expect[^;]*\.to(Be|Equal|StrictEqual)[[:space:]]*\(/ {
|
|
148
|
+
line = $0
|
|
149
|
+
# Peel "expect(" ... ")" first, then the matcher argument.
|
|
150
|
+
if (match(line, /expect[[:space:]]*\([^()]*\)/)) {
|
|
151
|
+
left = substr(line, RSTART, RLENGTH)
|
|
152
|
+
sub(/^expect[[:space:]]*\(/, "", left); sub(/\)$/, "", left)
|
|
153
|
+
left = trim(left)
|
|
154
|
+
rest = substr(line, RSTART + RLENGTH)
|
|
155
|
+
if (match(rest, /\.to(Be|Equal|StrictEqual)[[:space:]]*\([^()]*\)/)) {
|
|
156
|
+
right = substr(rest, RSTART, RLENGTH)
|
|
157
|
+
sub(/^\.to(Be|Equal|StrictEqual)[[:space:]]*\(/, "", right); sub(/\)$/, "", right)
|
|
158
|
+
right = trim(right)
|
|
159
|
+
if (left != "" && left == right && left ~ /^(true|false|null|undefined|-?[0-9]+|".*"|\047.*\047)$/) {
|
|
160
|
+
print NR ":" line; next
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
# (b) a literal tested against the truthiness it already has.
|
|
166
|
+
/expect[[:space:]]*\([[:space:]]*(true|1|-1|"[^"]+"|\047[^\047]+\047)[[:space:]]*\)[[:space:]]*\.[[:space:]]*toBeTruthy[[:space:]]*\(/ { print NR ":" $0; next }
|
|
167
|
+
/expect[[:space:]]*\([[:space:]]*(false|0|null|undefined|""|\047\047)[[:space:]]*\)[[:space:]]*\.[[:space:]]*toBeFalsy[[:space:]]*\(/ { print NR ":" $0; next }
|
|
168
|
+
'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Anti-pattern 2: swallowed error -- a `catch` that neither asserts nor rethrows.
|
|
172
|
+
# The test cannot fail on the very path it was written to guard. BLOCK.
|
|
173
|
+
#
|
|
174
|
+
# Three spellings: `catch {}`, `catch (e) {}` (empty body), and `catch (e) { }`
|
|
175
|
+
# with only whitespace. A catch containing `expect`/`assert`/`throw`/`fail` is
|
|
176
|
+
# doing its job and is not matched.
|
|
177
|
+
classify_swallowed_errors() {
|
|
178
|
+
grep -nE 'catch([[:space:]]*\([^)]*\))?[[:space:]]*\{[[:space:]]*\}' || true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Anti-patterns 3-5 (WARNING tier): weak-only assertions, mock-echo assertions, and
|
|
182
|
+
# call-order over-verification. These can be legitimate, so each is reported but
|
|
183
|
+
# never blocks.
|
|
184
|
+
#
|
|
185
|
+
# Implemented as a per-`it`/`test` block judgement rather than a per-line grep:
|
|
186
|
+
# "only weak assertions" is a property of the whole test body, and a line-wise
|
|
187
|
+
# check cannot tell `expect(x).toBeDefined()` alone from the same line followed by
|
|
188
|
+
# `expect(x).toBe(42)`.
|
|
189
|
+
#
|
|
190
|
+
# $1 = source text. Prints `line:reason` for each offending test block.
|
|
191
|
+
classify_weak_assertions() {
|
|
192
|
+
# Stdin or $1, for the same reason classify_tautological_assertions accepts both.
|
|
193
|
+
local _aq_src
|
|
194
|
+
if [ -n "${1:-}" ]; then _aq_src="$1"; else _aq_src=$(cat); fi
|
|
195
|
+
printf '%s\n' "$_aq_src" | awk '
|
|
196
|
+
# A block opens on it( / test( and closes on the matching "});" at column 0-2.
|
|
197
|
+
function flush() {
|
|
198
|
+
if (open && nassert > 0 && nstrong == 0) {
|
|
199
|
+
printf "%d:weak_assertion_only\n", startline
|
|
200
|
+
}
|
|
201
|
+
open = 0; nassert = 0; nstrong = 0
|
|
202
|
+
}
|
|
203
|
+
/(^|[^.[:alnum:]_])(it|test)(\.(only|skip|todo))?[[:space:]]*\(/ {
|
|
204
|
+
flush(); open = 1; startline = NR; next
|
|
205
|
+
}
|
|
206
|
+
open && /expect[[:space:]]*\(/ {
|
|
207
|
+
nassert++
|
|
208
|
+
# A "strong" assertion inspects a concrete value: not toBeDefined/toBeTruthy/
|
|
209
|
+
# toBeFalsy/toBeNull/toBeUndefined, and not a bare toHaveBeenCalled().
|
|
210
|
+
if ($0 !~ /\.(toBeDefined|toBeTruthy|toBeFalsy|toBeNull|toBeUndefined|toBeInstanceOf)[[:space:]]*\(/ \
|
|
211
|
+
&& $0 !~ /\.toHaveBeenCalled[[:space:]]*\([[:space:]]*\)/) {
|
|
212
|
+
nstrong++
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
open && /^\s*\}\);?\s*$/ { flush() }
|
|
216
|
+
END { flush() }
|
|
217
|
+
'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Anti-pattern 6 exemption: `@assertion-justified: <reason>`, mirroring
|
|
221
|
+
# `@mock-justified`. The reason must be at least 10 non-space characters -- an
|
|
222
|
+
# annotation without a reason is not an exemption, which is what stops the escape
|
|
223
|
+
# hatch from becoming the default.
|
|
224
|
+
#
|
|
225
|
+
# $1 = the source text to inspect.
|
|
226
|
+
is_assertion_justified() {
|
|
227
|
+
printf '%s\n' "$1" \
|
|
228
|
+
| grep -oE '@assertion-justified:[[:space:]]*[^[:space:]*].*' \
|
|
229
|
+
| sed 's/^@assertion-justified:[[:space:]]*//' \
|
|
230
|
+
| tr -d '\r' \
|
|
231
|
+
| awk 'length($0) >= 10 { found = 1; exit } END { exit(found ? 0 : 1) }'
|
|
232
|
+
}
|