Skip to content

Turn fabbot into a reusable github action #61256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions .github/workflows/callable-fabbot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
name: Fabbot

on:
workflow_call:
inputs:
package:
required: true
type: string

env:
GH_TOKEN: ${{ github.token }}

jobs:
check:
name: Checks
runs-on: ubuntu-24.04
steps:
- name: Checkout code
run: |
# Checkout patched files using the REST API and install dependencies concurrently
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"

pip install codespell &
composer global require -q friendsofphp/php-cs-fixer seld/jsonlint symfony/yaml &

mkdir a

gh api -H "Accept: application/vnd.github.v3.raw" \
"/repos/$REPO_OWNER/$REPO_NAME/contents/.php-cs-fixer.dist.php?ref=$PR_HEAD_SHA" \
> a/.php-cs-fixer.dist.php || rm a/.php-cs-fixer.dist.php &

gh api --paginate "/repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER/files" \
| jq -c '.[] | select(.status != "removed") | {filename, sha}' \
| while read -r FILE_OBJ; do
FILENAME=$(echo "$FILE_OBJ" | jq -r '.filename')
FILE_SHA=$(echo "$FILE_OBJ" | jq -r '.sha')

mkdir -p "a/$(dirname "$FILENAME")"
gh api -H "Accept: application/vnd.github.raw" \
"/repos/$REPO_OWNER/$REPO_NAME/git/blobs/$FILE_SHA" \
> "a/$FILENAME" &
done

wait

- name: Check code style
if: always()
run: |
# Run PHP-CS-Fixer
cp -a a b && cd b
~/.composer/vendor/bin/php-cs-fixer fix --using-cache no --show-progress none
cd ..

if ! diff -qr --no-dereference a/ b/ >/dev/null; then
echo "::error::PHP-CS-Fixer found style issues. Please apply the patch below."
echo -e "\n \n git apply - <<'EOF_PATCH'"
diff -pru2 --no-dereference --color=always a/ b/ || true
echo -e "EOF_PATCH\n \n"
echo "Then commit the changes and push to your PR branch."
exit 1
fi

- name: Check for common typos
if: always()
run: |
# Run codespell
rm -rf b && cp -a a b && cd b
codespell -L invokable --check-filenames -w || true
cd ..

if ! diff -qr --no-dereference a/ b/ >/dev/null; then
echo "::error::PHP-CS-Fixer found typos. Please apply the patch below."
echo -e "\n \n git apply - <<'EOF_PATCH'"
diff -pru2 --no-dereference --color=always a/ b/ || true
echo -e "EOF_PATCH\n \n"
echo "Then commit the changes and push to your PR branch."
exit 1
fi

- name: Check for merge commits
if: always()
run: |
# If a PR contains merge commits, fail the job
gh api -H "Accept: application/vnd.github.v3+json" \
"/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}/commits" \
| jq -r '.[].parents | length > 1' | grep true > /dev/null && {
echo "::error::Merge commits are not allowed in pull requests."
echo "Please rebase your branch."
exit 1
} || true

- name: Check test-case methods
if: always()
run: |
# Test method names should not have a return type
rm -rf b && cp -a a b && cd b
find -wholename '**/Tests/**.php' \
| while read -r FILE; do
sed -i -E 's/^( public function test.*): void$/\1/' "$FILE"
done
cd ..

if ! diff -qr --no-dereference a/ b/ >/dev/null; then
echo "::error::Test case methods should not have a return type. Please apply the patch below."
echo -e "\n \n git apply - <<'EOF_PATCH'"
diff -pru2 --no-dereference --color=always a/ b/ || true
echo -e "EOF_PATCH\n \n"
echo "Then commit the changes and push to your PR branch."
exit 1
fi

- name: Check @deprecated annotations
if: always()
run: |
# @deprecated annotations should mention ${{ inputs.package }}
rm -rf b && cp -a a b && cd b
find -name '*.php' \
| while read -r FILE; do
sed -i -E 's/(@deprecated since )([0-9])/\1${{ inputs.package }} \2/' "$FILE"
done
cd ..

if ! diff -qr --no-dereference a/ b/ >/dev/null; then
echo "::error::@deprecated annotations should mention ${{ inputs.package }}. Please apply the patch below."
echo -e "\n \n git apply - <<'EOF_PATCH'"
diff -pru2 --no-dereference --color=always a/ b/ || true
echo -e "EOF_PATCH\n \n"
echo "Then commit the changes and push to your PR branch."
exit 1
fi

- name: Check PR header
if: always()
run: |
# Check if the PR title and body follow the Symfony contribution guidelines
PR_TITLE="${{ github.event.pull_request.title }}"
PR_BODY="${{ github.event.pull_request.body }}"

if [[ ! "$PR_BODY" =~ \|\ License[\ ]+\|\ MIT ]]; then
echo "::error::You must add the standard contribution header in the PR description"
echo "See https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request"
exit 1
fi

if [[ "$PR_TITLE" =~ (feat|fix|docs|style|refactor|perf|test|chore|revert|build|ci|types?|wip)[:()] ]]; then
echo "::error::Don't use conventional commits in PR titles."
echo "We'll add the appropriate prefix while merging."
echo "Use the component name instead, e.g., [Component] Description."
exit 1
fi

- name: Check YAML files
if: always()
run: |
# Check YAML files for syntax errors
rm -rf b && cp -a a b && cd b
find . -name '*.yml' -o -name '*.yaml' \
| while read -r FILE; do php -r '
use Symfony\Component\Yaml\{Parser,Yaml};
require $_SERVER["HOME"]."/.composer/vendor/autoload.php";
try { (new Parser())->parse(file_get_contents($argv[1]), Yaml::PARSE_CUSTOM_TAGS); }
catch (Exception $e) { echo "::error::in $argv[1]:\n{$e->getMessage()}\n"; exit(1); }
' "$FILE"
done
cd ..

- name: Check JSON files
if: always()
run: |
# Check JSON files for syntax errors
rm -rf b && cp -a a b && cd b
find . -name '*.json' \
| while read -r FILE; do php -r '
use Seld\JsonLint\JsonParser;
require $_SERVER["HOME"]."/.composer/vendor/autoload.php";
try { (new JsonParser())->parse(file_get_contents($argv[1])); }
catch (Exception $e) { echo "::error:: in $argv[1]: {$e->getMessage()}\n"; exit(1); }
' "$FILE"
done
cd ..

- name: Check exception messages
if: always()
run: |
# Placeholders should be enclosed in double-quotes and messages should end with a dot
rm -rf b && cp -a a b && cd b
find -name '*.php' \
| while read -r FILE; do php -r "$(cat <<'EOF'
$new = preg_replace_callback('{throw new ([^\(]+)\((.+?)\);}', function ($match) {
$contents = $match[2];

// %s::%s() -> "%s::%s()"
$contents = preg_replace('{(?<= )%s\:\:%s(\(\))?}', '"%s::%s()"', $contents);
$contents = preg_replace('{\(\'%s\:\:%s(\(\))?}', '(\'"%s::%s()"', $contents);

// %s() -> "%s()"
$contents = preg_replace('{(?<= )%s(\(\))}', '"%s$1"', $contents);
$contents = preg_replace('{\(\'%s(\(\))}', '(\'"%s$1"', $contents);

// %s -> "%s" after a space
$contents = preg_replace('{(?<= )%s}', '"%s"', $contents);
$contents = preg_replace('{\(\'%s}', '(\'"%s"', $contents);

return sprintf('throw new %s(%s);', $match[1], $contents);
}, $old = file_get_contents($argv[1]));

// ensure there is a dot at the end of the exception message
// except for files under Tests/
if (false === strpos($argv[1], '/Tests/')) {
$new = preg_replace_callback('{throw new ([^\(]+)\((sprintf\()?(\'|")(.+?)(?<!\\\)(\3)}', function ($match) {
if ('UnexpectedTypeException' === $match[1]) {
return $match[0];
}

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]);
}, $new);
}

if ($new !== $old) {
file_put_contents($argv[1], $new);
}
EOF
)" "$FILE"
done
cd ..

if ! diff -qr --no-dereference a/ b/ >/dev/null; then
echo "::error::Some exception messages might need a tweak. Please consider the patch below."
echo -e "\n \n git apply - <<'EOF_PATCH'"
diff -pru2 --no-dereference --color=always a/ b/ || true
echo -e "EOF_PATCH\n \n"
echo "Then commit the changes and push to your PR branch."
exit 1
fi

- name: 🧠 Fabbot can generate false-positives. Cherry-pick as fits 🍒. Reviewers will help.
if: always()
run: exit 0
14 changes: 14 additions & 0 deletions .github/workflows/fabbot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: CS

on:
pull_request:

permissions:
contents: read

jobs:
call-fabbot:
name: Fabbot
uses: ./.github/workflows/callable-fabbot.yml
with:
package: Symfony
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy