Skip to content

Commit 64feae6

Browse files
Merge branch '6.4' into 7.2
* 6.4: Turn fabbot into a reusable github action
2 parents 2b566e4 + 571373d commit 64feae6

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

.github/workflows/callable-fabbot.yml

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
name: Fabbot
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
package:
7+
required: true
8+
type: string
9+
10+
env:
11+
GH_TOKEN: ${{ github.token }}
12+
13+
jobs:
14+
check:
15+
name: Checks
16+
runs-on: ubuntu-24.04
17+
steps:
18+
- name: Checkout code
19+
run: |
20+
# Checkout patched files using the REST API and install dependencies concurrently
21+
PR_NUMBER="${{ github.event.pull_request.number }}"
22+
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
23+
REPO_OWNER="${{ github.repository_owner }}"
24+
REPO_NAME="${{ github.event.repository.name }}"
25+
26+
pip install codespell &
27+
composer global require -q friendsofphp/php-cs-fixer seld/jsonlint symfony/yaml &
28+
29+
mkdir a
30+
31+
gh api -H "Accept: application/vnd.github.v3.raw" \
32+
"/repos/$REPO_OWNER/$REPO_NAME/contents/.php-cs-fixer.dist.php?ref=$PR_HEAD_SHA" \
33+
> a/.php-cs-fixer.dist.php || rm a/.php-cs-fixer.dist.php &
34+
35+
gh api --paginate "/repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER/files" \
36+
| jq -c '.[] | select(.status != "removed") | {filename, sha}' \
37+
| while read -r FILE_OBJ; do
38+
FILENAME=$(echo "$FILE_OBJ" | jq -r '.filename')
39+
FILE_SHA=$(echo "$FILE_OBJ" | jq -r '.sha')
40+
41+
mkdir -p "a/$(dirname "$FILENAME")"
42+
gh api -H "Accept: application/vnd.github.raw" \
43+
"/repos/$REPO_OWNER/$REPO_NAME/git/blobs/$FILE_SHA" \
44+
> "a/$FILENAME" &
45+
done
46+
47+
wait
48+
49+
- name: Check code style
50+
if: always()
51+
run: |
52+
# Run PHP-CS-Fixer
53+
cp -a a b && cd b
54+
~/.composer/vendor/bin/php-cs-fixer fix --using-cache no --show-progress none
55+
cd ..
56+
57+
if ! diff -qr --no-dereference a/ b/ >/dev/null; then
58+
echo "::error::PHP-CS-Fixer found style issues. Please apply the patch below."
59+
echo -e "\n \n git apply - <<'EOF_PATCH'"
60+
diff -pru2 --no-dereference --color=always a/ b/ || true
61+
echo -e "EOF_PATCH\n \n"
62+
echo "Then commit the changes and push to your PR branch."
63+
exit 1
64+
fi
65+
66+
- name: Check for common typos
67+
if: always()
68+
run: |
69+
# Run codespell
70+
rm -rf b && cp -a a b && cd b
71+
codespell -L invokable --check-filenames -w || true
72+
cd ..
73+
74+
if ! diff -qr --no-dereference a/ b/ >/dev/null; then
75+
echo "::error::PHP-CS-Fixer found typos. Please apply the patch below."
76+
echo -e "\n \n git apply - <<'EOF_PATCH'"
77+
diff -pru2 --no-dereference --color=always a/ b/ || true
78+
echo -e "EOF_PATCH\n \n"
79+
echo "Then commit the changes and push to your PR branch."
80+
exit 1
81+
fi
82+
83+
- name: Check for merge commits
84+
if: always()
85+
run: |
86+
# If a PR contains merge commits, fail the job
87+
gh api -H "Accept: application/vnd.github.v3+json" \
88+
"/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}/commits" \
89+
| jq -r '.[].parents | length > 1' | grep true > /dev/null && {
90+
echo "::error::Merge commits are not allowed in pull requests."
91+
echo "Please rebase your branch."
92+
exit 1
93+
} || true
94+
95+
- name: Check test-case methods
96+
if: always()
97+
run: |
98+
# Test method names should not have a return type
99+
rm -rf b && cp -a a b && cd b
100+
find -wholename '**/Tests/**.php' \
101+
| while read -r FILE; do
102+
sed -i -E 's/^( public function test.*): void$/\1/' "$FILE"
103+
done
104+
cd ..
105+
106+
if ! diff -qr --no-dereference a/ b/ >/dev/null; then
107+
echo "::error::Test case methods should not have a return type. Please apply the patch below."
108+
echo -e "\n \n git apply - <<'EOF_PATCH'"
109+
diff -pru2 --no-dereference --color=always a/ b/ || true
110+
echo -e "EOF_PATCH\n \n"
111+
echo "Then commit the changes and push to your PR branch."
112+
exit 1
113+
fi
114+
115+
- name: Check @deprecated annotations
116+
if: always()
117+
run: |
118+
# @deprecated annotations should mention ${{ inputs.package }}
119+
rm -rf b && cp -a a b && cd b
120+
find -name '*.php' \
121+
| while read -r FILE; do
122+
sed -i -E 's/(@deprecated since )([0-9])/\1${{ inputs.package }} \2/' "$FILE"
123+
done
124+
cd ..
125+
126+
if ! diff -qr --no-dereference a/ b/ >/dev/null; then
127+
echo "::error::@deprecated annotations should mention ${{ inputs.package }}. Please apply the patch below."
128+
echo -e "\n \n git apply - <<'EOF_PATCH'"
129+
diff -pru2 --no-dereference --color=always a/ b/ || true
130+
echo -e "EOF_PATCH\n \n"
131+
echo "Then commit the changes and push to your PR branch."
132+
exit 1
133+
fi
134+
135+
- name: Check PR header
136+
if: always()
137+
run: |
138+
# Check if the PR title and body follow the Symfony contribution guidelines
139+
PR_TITLE="${{ github.event.pull_request.title }}"
140+
PR_BODY="${{ github.event.pull_request.body }}"
141+
142+
if [[ ! "$PR_BODY" =~ \|\ License[\ ]+\|\ MIT ]]; then
143+
echo "::error::You must add the standard contribution header in the PR description"
144+
echo "See https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request"
145+
exit 1
146+
fi
147+
148+
if [[ "$PR_TITLE" =~ (feat|fix|docs|style|refactor|perf|test|chore|revert|build|ci|types?|wip)[:()] ]]; then
149+
echo "::error::Don't use conventional commits in PR titles."
150+
echo "We'll add the appropriate prefix while merging."
151+
echo "Use the component name instead, e.g., [Component] Description."
152+
exit 1
153+
fi
154+
155+
- name: Check YAML files
156+
if: always()
157+
run: |
158+
# Check YAML files for syntax errors
159+
rm -rf b && cp -a a b && cd b
160+
find . -name '*.yml' -o -name '*.yaml' \
161+
| while read -r FILE; do php -r '
162+
use Symfony\Component\Yaml\{Parser,Yaml};
163+
require $_SERVER["HOME"]."/.composer/vendor/autoload.php";
164+
try { (new Parser())->parse(file_get_contents($argv[1]), Yaml::PARSE_CUSTOM_TAGS); }
165+
catch (Exception $e) { echo "::error::in $argv[1]:\n{$e->getMessage()}\n"; exit(1); }
166+
' "$FILE"
167+
done
168+
cd ..
169+
170+
- name: Check JSON files
171+
if: always()
172+
run: |
173+
# Check JSON files for syntax errors
174+
rm -rf b && cp -a a b && cd b
175+
find . -name '*.json' \
176+
| while read -r FILE; do php -r '
177+
use Seld\JsonLint\JsonParser;
178+
require $_SERVER["HOME"]."/.composer/vendor/autoload.php";
179+
try { (new JsonParser())->parse(file_get_contents($argv[1])); }
180+
catch (Exception $e) { echo "::error:: in $argv[1]: {$e->getMessage()}\n"; exit(1); }
181+
' "$FILE"
182+
done
183+
cd ..
184+
185+
- name: Check exception messages
186+
if: always()
187+
run: |
188+
# Placeholders should be enclosed in double-quotes and messages should end with a dot
189+
rm -rf b && cp -a a b && cd b
190+
find -name '*.php' \
191+
| while read -r FILE; do php -r "$(cat <<'EOF'
192+
$new = preg_replace_callback('{throw new ([^\(]+)\((.+?)\);}', function ($match) {
193+
$contents = $match[2];
194+
195+
// %s::%s() -> "%s::%s()"
196+
$contents = preg_replace('{(?<= )%s\:\:%s(\(\))?}', '"%s::%s()"', $contents);
197+
$contents = preg_replace('{\(\'%s\:\:%s(\(\))?}', '(\'"%s::%s()"', $contents);
198+
199+
// %s() -> "%s()"
200+
$contents = preg_replace('{(?<= )%s(\(\))}', '"%s$1"', $contents);
201+
$contents = preg_replace('{\(\'%s(\(\))}', '(\'"%s$1"', $contents);
202+
203+
// %s -> "%s" after a space
204+
$contents = preg_replace('{(?<= )%s}', '"%s"', $contents);
205+
$contents = preg_replace('{\(\'%s}', '(\'"%s"', $contents);
206+
207+
return sprintf('throw new %s(%s);', $match[1], $contents);
208+
}, $old = file_get_contents($argv[1]));
209+
210+
// ensure there is a dot at the end of the exception message
211+
// except for files under Tests/
212+
if (false === strpos($argv[1], '/Tests/')) {
213+
$new = preg_replace_callback('{throw new ([^\(]+)\((sprintf\()?(\'|")(.+?)(?<!\\\)(\3)}', function ($match) {
214+
if ('UnexpectedTypeException' === $match[1]) {
215+
return $match[0];
216+
}
217+
218+
return sprintf('throw new %s(%s%s%s%s%s', $match[1], $match[2], $match[3], $match[4], \in_array($match[4][\strlen($match[4]) - 1], ['.', '!', '?', ' ']) ? '' : '.', $match[5]);
219+
}, $new);
220+
}
221+
222+
if ($new !== $old) {
223+
file_put_contents($argv[1], $new);
224+
}
225+
EOF
226+
)" "$FILE"
227+
done
228+
cd ..
229+
230+
if ! diff -qr --no-dereference a/ b/ >/dev/null; then
231+
echo "::error::Some exception messages might need a tweak. Please consider the patch below."
232+
echo -e "\n \n git apply - <<'EOF_PATCH'"
233+
diff -pru2 --no-dereference --color=always a/ b/ || true
234+
echo -e "EOF_PATCH\n \n"
235+
echo "Then commit the changes and push to your PR branch."
236+
exit 1
237+
fi
238+
239+
- name: 🧠 Fabbot can generate false-positives. Cherry-pick as fits 🍒. Reviewers will help.
240+
if: always()
241+
run: exit 0

.github/workflows/fabbot.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: CS
2+
3+
on:
4+
pull_request:
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
call-fabbot:
11+
name: Fabbot
12+
uses: ./.github/workflows/callable-fabbot.yml
13+
with:
14+
package: Symfony

0 commit comments

Comments
 (0)