Last active
May 14, 2025 13:30
-
-
Save chuckadams/a2622fe57d0ebc1b40d673b6c75af70f to your computer and use it in GitHub Desktop.
Auto-install and run rector, one level at a time
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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