Skip to content

Instantly share code, notes, and snippets.

@chuckadams
Last active May 14, 2025 13:30
Show Gist options
  • Save chuckadams/a2622fe57d0ebc1b40d673b6c75af70f to your computer and use it in GitHub Desktop.
Save chuckadams/a2622fe57d0ebc1b40d673b6c75af70f to your computer and use it in GitHub Desktop.
Auto-install and run rector, one level at a time
#!/bin/bash
# recommend running this as "./rectorize | tee rectorize.log" in order to commit the log to git as well
RECTOR_VERSION='^2.0.15'
function main () {
if [[ -f rector.php ]]; then
echo "rector.php already exists -- move or delete it first" >&2
exit 1
fi
install_rector
do_php_versions
do_levels
}
function do_php_versions () {
for version in 53 54 55 56 70 71 72 73 74 80 81 82 83 84
do
set_php_version $version
done
}
function do_levels () {
# Upper bounds of these seqs are for Rector 2.0.15
# Later versions do add more levels, but by then you should be good using withPreparedSets() instead
for i in $(seq 0 50)
do
set_level withTypeCoverageLevel $i
done
for i in $(seq 0 71)
do
set_level withCodeQualityLevel $i
done
for i in $(seq 0 49)
do
set_level withDeadCodeLevel $i
done
}
function set_php_version () {
version=$1
arg="php$version: true"
perl -pi -e "s|(?:// *)?->withPhpSets.*|->withPhpSets($arg)|" rector.php
run_rector "withPhpSets($arg)"
}
function set_level () {
ruleset=$1
level=$2
perl -pi -e "s|(?:// *)?->$ruleset.*|->$ruleset($level)|" rector.php
run_rector "$ruleset($level)"
}
function run_rector () {
message=$1
echo "==== PROCESSING: $message ===="
out=$(mktemp /tmp/rectorize.XXXXXXXX)
vendor/bin/rector | grep -v '.WARNING. Skipped rule' | grep -v '"->withSkip()"' | uniq | tee $out
changed=$(perl -ne '/^\s*\[OK\]\s+(\d+) files? ha[sve]+ been changed by Rector/ and print $1' $out)
if [[ -n $changed ]]; then
git add .
git commit -m "RECTOR: $message"
fi
rm $out
}
function install_rector () {
composer require --dev --with-all-dependencies rector/rector $RECTOR_VERSION
cat > rector.php <<EOF
<?php
declare(strict_types=1);
use Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector;
use Rector\CodeQuality\Rector\Expression\InlineIfToExplicitIfRector;
use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
use Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector;
use Rector\CodeQuality\Rector\LogicalAnd\LogicalToBooleanRector;
use Rector\CodeQuality\Rector\Ternary\UnnecessaryTernaryExpressionRector;
use Rector\Config\RectorConfig;
use Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector;
use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector;
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnRector;
return RectorConfig::configure()
->withCache(cacheDirectory: __DIR__ . '/.cache/rector')
->withPaths([
__DIR__ . '/app',
__DIR__ . '/tests',
])
->withImportNames()
// ->withPhpSets()
// ->withTypeCoverageLevel(0)
// ->withCodeQualityLevel(0)
// ->withDeadCodeLevel(0)
->withSkip([
ClassPropertyAssignToConstructorPromotionRector::class, // butchers docblocks, so do this by hand in your IDE
AddClosureVoidReturnTypeWhereNoReturnRector::class, // adds noise for no benefit
AddArrowFunctionReturnTypeRector::class, // also noisy, should have an inferred return type
UnnecessaryTernaryExpressionRector::class, // makes many things truthy but not bool
ExplicitBoolCompareRector::class, // but we're not trying to wipe out all truthiness!
InlineIfToExplicitIfRector::class, // "foo() or bar()" is legit control flow in my book
LogicalToBooleanRector::class, // and we keep "and" and "or" around for that reason
FlipTypeControlToUseExclusiveTypeRector::class, // instanceof is safer, but I still don't like this
LocallyCalledStaticMethodToNonStaticRector::class, // I can't see the justification for this at all
RemoveNullPropertyInitializationRector::class, // I prefer to be explicit about null initialization
RemoveAlwaysTrueIfConditionRector::class, // always-true conditions are often there for debugging
RemoveUnusedVariableAssignRector::class, // blindly removes side-effectful assignments too
]);
EOF
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment