Skip to content

Commit 2612804

Browse files
dondonzclaude
andcommitted
Merge branch 'master' into copilot/add-jspecify-annotations
Resolve JSpecifyAnnotationsCheck conflict by taking master's exemption list updates (classes already annotated on master removed, new exemptions added). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 2d02ff0 + 00d1c16 commit 2612804

File tree

201 files changed

+195642
-4634
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+195642
-4634
lines changed

.claude/commands/jspecify-annotate.md

Lines changed: 0 additions & 126 deletions
This file was deleted.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.github/workflows/*.lock.yml linguist-generated=true merge=ours

.githooks/pre-commit

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#!/bin/bash
22

3-
# Pre-commit hook to enforce Windows compatibility and file size limits
3+
# Pre-commit hook to enforce Windows compatibility, file size limits,
4+
# and dangerous Unicode character detection.
45
#
56
# 1. Windows filenames: prevents characters that are reserved on Windows (< > : " | ? * \)
67
# so the repo can be cloned on Windows systems.
78
# 2. File size: rejects files larger than 10 MB. Many enterprise users mirror graphql-java
89
# into internal repositories that enforce file size limits.
10+
# 3. Dangerous Unicode: detects invisible/control characters that can be used for
11+
# "Trojan Source" (BiDi override), homoglyph, or glassworm-style attacks.
912

1013
# ANSI color codes for better output readability
1114
RED='\033[0;31m'
@@ -75,6 +78,45 @@ if [ -n "$LARGE_FILES" ]; then
7578
ERRORS_FOUND=1
7679
fi
7780

81+
# Check 3: Dangerous Unicode characters (Trojan Source / glassworm attacks)
82+
# Detects: C0/C1 control chars (except TAB, LF, CR), zero-width characters,
83+
# BiDi override/embedding/isolate chars.
84+
# Uses perl for macOS compatibility (grep -P is not available on macOS).
85+
echo " Checking for dangerous Unicode characters..."
86+
87+
UNICODE_FILES=""
88+
if [ -n "$STAGED_FILES" ]; then
89+
while IFS= read -r file; do
90+
if [ ! -f "$file" ]; then
91+
continue
92+
fi
93+
# Skip binary files
94+
if file --mime-type "$file" 2>/dev/null | grep -qv 'text/'; then
95+
continue
96+
fi
97+
MATCHES=$(perl -CSD -ne '
98+
if (/[\x{0000}-\x{0008}\x{000B}\x{000C}\x{000E}-\x{001F}\x{007F}-\x{009F}\x{200B}-\x{200D}\x{FEFF}\x{202A}-\x{202E}\x{2066}-\x{2069}]/) {
99+
print " line $.: $_";
100+
}
101+
' "$file" 2>/dev/null || true)
102+
if [ -n "$MATCHES" ]; then
103+
UNICODE_FILES="${UNICODE_FILES} - ${file}\n${MATCHES}\n"
104+
fi
105+
done <<< "$STAGED_FILES"
106+
fi
107+
108+
if [ -n "$UNICODE_FILES" ]; then
109+
echo -e "${RED}Error: The following files contain dangerous Unicode characters:${NC}"
110+
echo -e "$UNICODE_FILES"
111+
echo -e "${YELLOW}These characters are invisible or alter text rendering and can be used for${NC}"
112+
echo -e "${YELLOW}Trojan Source or glassworm-style attacks. Detected character categories:${NC}"
113+
echo -e "${YELLOW} - C0/C1 control characters (U+0000-001F, U+007F-009F, except TAB/LF/CR)${NC}"
114+
echo -e "${YELLOW} - Zero-width characters (U+200B-200D, U+FEFF)${NC}"
115+
echo -e "${YELLOW} - BiDi override/isolate (U+202A-202E, U+2066-2069)${NC}"
116+
echo -e "${YELLOW}Please remove these characters from the affected files.${NC}"
117+
ERRORS_FOUND=1
118+
fi
119+
78120
# Exit with error if any checks failed
79121
if [ "$ERRORS_FOUND" -eq 1 ]; then
80122
echo -e "${RED}Pre-commit checks failed. Please fix the issues above and try again.${NC}"

.github/aw/actions-lock.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"entries": {
3+
"actions/github-script@v8": {
4+
"repo": "actions/github-script",
5+
"version": "v8",
6+
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
7+
},
8+
"github/gh-aw/actions/setup@v0.49.0": {
9+
"repo": "github/gh-aw/actions/setup",
10+
"version": "v0.49.0",
11+
"sha": "0eb518a648ba8178f4f42559a4c250d3e513acd1"
12+
}
13+
}
14+
}

.github/dependabot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ updates:
33
- package-ecosystem: "gradle"
44
directory: "/"
55
schedule:
6-
interval: "weekly"
6+
interval: "monthly"
77
- package-ecosystem: "github-actions"
88
directory: "/"
99
schedule:
10-
interval: "weekly"
10+
interval: "monthly"

.github/scripts/parse-jacoco.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Shared JaCoCo XML parser used by CI workflows.
2+
// Extracts overall and per-class coverage counters from a JaCoCo XML report.
3+
4+
const fs = require('fs');
5+
6+
const zeroCov = { covered: 0, missed: 0 };
7+
8+
function parseJacocoXml(jacocoFile) {
9+
const result = { overall: {}, classes: {} };
10+
11+
if (!fs.existsSync(jacocoFile)) {
12+
return null;
13+
}
14+
15+
const xml = fs.readFileSync(jacocoFile, 'utf8');
16+
17+
// Overall counters (outside <package> tags)
18+
const stripped = xml.replace(/<package[\s\S]*?<\/package>/g, '');
19+
const re = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
20+
let m;
21+
while ((m = re.exec(stripped)) !== null) {
22+
const entry = { covered: parseInt(m[3]), missed: parseInt(m[2]) };
23+
if (m[1] === 'LINE') result.overall.line = entry;
24+
else if (m[1] === 'BRANCH') result.overall.branch = entry;
25+
else if (m[1] === 'METHOD') result.overall.method = entry;
26+
}
27+
28+
// Per-class counters from <package>/<class> elements.
29+
// The negative lookbehind (?<!\/) prevents matching self-closing <class .../> tags
30+
// (interfaces, annotations) which have no body and would otherwise steal the next
31+
// class's counters.
32+
const pkgRe = /<package\s+name="([^"]+)">([\s\S]*?)<\/package>/g;
33+
let pkgMatch;
34+
while ((pkgMatch = pkgRe.exec(xml)) !== null) {
35+
const pkgBody = pkgMatch[2];
36+
const classRe = /<class\s+name="([^"]+)"[^>]*(?<!\/)>([\s\S]*?)<\/class>/g;
37+
let classMatch;
38+
while ((classMatch = classRe.exec(pkgBody)) !== null) {
39+
const className = classMatch[1].replace(/\//g, '.');
40+
const classBody = classMatch[2];
41+
const counters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
42+
const cntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
43+
let cntMatch;
44+
while ((cntMatch = cntRe.exec(classBody)) !== null) {
45+
const entry = { covered: parseInt(cntMatch[3]), missed: parseInt(cntMatch[2]) };
46+
if (cntMatch[1] === 'LINE') counters.line = entry;
47+
else if (cntMatch[1] === 'BRANCH') counters.branch = entry;
48+
else if (cntMatch[1] === 'METHOD') counters.method = entry;
49+
}
50+
// Extract per-method counters within this class.
51+
// JaCoCo XML contains <method name="..." desc="..." line="..."> elements
52+
// each with their own <counter> children.
53+
const methods = [];
54+
const methodRe = /<method\s+name="([^"]+)"\s+desc="([^"]+)"(?:\s+line="(\d+)")?[^>]*>([\s\S]*?)<\/method>/g;
55+
let methodMatch;
56+
while ((methodMatch = methodRe.exec(classBody)) !== null) {
57+
const mCounters = { line: { ...zeroCov }, branch: { ...zeroCov }, method: { ...zeroCov } };
58+
const mCntRe = /<counter type="(\w+)" missed="(\d+)" covered="(\d+)"\/>/g;
59+
let mCntMatch;
60+
while ((mCntMatch = mCntRe.exec(methodMatch[4])) !== null) {
61+
const entry = { covered: parseInt(mCntMatch[3]), missed: parseInt(mCntMatch[2]) };
62+
if (mCntMatch[1] === 'LINE') mCounters.line = entry;
63+
else if (mCntMatch[1] === 'BRANCH') mCounters.branch = entry;
64+
else if (mCntMatch[1] === 'METHOD') mCounters.method = entry;
65+
}
66+
const totalLines = mCounters.line.covered + mCounters.line.missed;
67+
if (totalLines > 0) {
68+
methods.push({
69+
name: methodMatch[1],
70+
desc: methodMatch[2],
71+
line: methodMatch[3] ? parseInt(methodMatch[3]) : null,
72+
counters: mCounters,
73+
});
74+
}
75+
}
76+
77+
// Skip classes with 0 total lines (empty interfaces, annotations)
78+
if (counters.line.covered + counters.line.missed > 0) {
79+
result.classes[className] = counters;
80+
if (methods.length > 0) {
81+
result.classes[className].methods = methods;
82+
}
83+
}
84+
}
85+
}
86+
87+
return result;
88+
}
89+
90+
function pct(covered, missed) {
91+
const total = covered + missed;
92+
return total === 0 ? 0 : (covered / total * 100);
93+
}
94+
95+
// A coverage metric is a "real regression" when BOTH the percentage drops
96+
// beyond the tolerance AND the absolute number of missed items increases.
97+
// This avoids false positives when well-covered code is extracted/moved out
98+
// of a class (which lowers the percentage without actually losing coverage).
99+
function isRegression(currPct, basePct, currMissed, baseMissed, tolerance = 0.05) {
100+
return currPct < basePct - tolerance && currMissed > baseMissed;
101+
}
102+
103+
module.exports = { parseJacocoXml, pct, zeroCov, isRegression };

0 commit comments

Comments
 (0)