diff --git a/config/set/php-version-based.php b/config/set/php-version-based.php new file mode 100644 index 00000000000..37cf0f579ed --- /dev/null +++ b/config/set/php-version-based.php @@ -0,0 +1,597 @@ +rules([ + // PHP 5.2 + VarToPublicPropertyRector::class, + ContinueToBreakInSwitchRector::class, + + // PHP 5.3 + ExplicitPublicClassMethodRector::class, + TernaryToElvisRector::class, + DirNameFileConstantToDirConstantRector::class, + ReplaceHttpServerVarsByServerRector::class, + + // PHP 5.4 + LongArrayToShortArrayRector::class, + RemoveReferenceFromCallRector::class, + RemoveZeroBreakContinueRector::class, + + // PHP 5.5 + StringClassNameToClassConstantRector::class, + ClassConstantToSelfClassRector::class, + PregReplaceEModifierRector::class, + GetCalledClassToSelfClassRector::class, + GetCalledClassToStaticClassRector::class, + StaticToSelfOnFinalClassRector::class, + + // PHP 5.6 + PowToExpRector::class, + + // PHP 7.0 + Php4ConstructorRector::class, + TernaryToNullCoalescingRector::class, + RandomFunctionRector::class, + ExceptionHandlerTypehintRector::class, + MultiDirnameRector::class, + ListSplitStringRector::class, + EmptyListRector::class, + ListSwapArrayOrderRector::class, + CallUserMethodRector::class, + EregToPregMatchRector::class, + ReduceMultipleDefaultSwitchRector::class, + TernaryToSpaceshipRector::class, + WrapVariableVariableNameInCurlyBracesRector::class, + IfToSpaceshipRector::class, + ThisCallOnStaticMethodToStaticCallRector::class, + BreakNotInLoopOrSwitchToReturnRector::class, + RenameMktimeWithoutArgsToTimeRector::class, + IfIssetToCoalescingRector::class, + + // PHP 7.1 + IsIterableRector::class, + MultiExceptionCatchRector::class, + AssignArrayToStringRector::class, + RemoveExtraParametersRector::class, + BinaryOpBetweenNumberAndStringRector::class, + ListToArrayDestructRector::class, + + // PHP 7.2 + GetClassOnNullRector::class, + ParseStrWithResultArgumentRector::class, + StringsAssertNakedRector::class, + CreateFunctionToAnonymousFunctionRector::class, + StringifyDefineRector::class, + WhileEachToForeachRector::class, + ListEachRector::class, + ReplaceEachAssignmentWithKeyCurrentRector::class, + UnsetCastRector::class, + + // PHP 7.3 + StringifyStrNeedlesRector::class, + RegexDashEscapeRector::class, + SetCookieRector::class, + IsCountableRector::class, + ArrayKeyFirstLastRector::class, + ArrayKeysToArrayKeyFirstLastRector::class, + SensitiveDefineRector::class, + SensitiveConstantNameRector::class, + SensitiveHereNowDocRector::class, + + // PHP 7.4 + ArrayKeyExistsOnPropertyRector::class, + FilterVarToAddSlashesRector::class, + ExportToReflectionFunctionRector::class, + MbStrrposEncodingArgumentPositionRector::class, + NullCoalescingOperatorRector::class, + IfToNullCoalescingAssignRector::class, + ClosureToArrowFunctionRector::class, + RestoreDefaultNullToNullableTypePropertyRector::class, + CurlyToSquareBracketArrayStringRector::class, + MoneyFormatToNumberFormatRector::class, + ParenthesizeNestedTernaryRector::class, + RestoreIncludePathToIniRestoreRector::class, + HebrevcToNl2brHebrevRector::class, + + // PHP 8.0 + StrContainsRector::class, + StrStartsWithRector::class, + StrEndsWithRector::class, + StringableForToStringRector::class, + ClassOnObjectRector::class, + GetDebugTypeRector::class, + RemoveUnusedVariableInCatchRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + ChangeSwitchToMatchRector::class, + RemoveParentCallWithoutParentRector::class, + SetStateToStaticRector::class, + FinalPrivateToPrivateVisibilityRector::class, + AddParamBasedOnParentClassMethodRector::class, + ClassOnThisVariableObjectRector::class, + ConsistentImplodeRector::class, + OptionalParametersAfterRequiredRector::class, + + // PHP 8.1 + ReturnNeverTypeRector::class, + MyCLabsClassToEnumRector::class, + MyCLabsMethodCallToEnumConstRector::class, + MyCLabsConstructorCallToEnumFromRector::class, + ReadOnlyPropertyRector::class, + SpatieEnumClassToEnumRector::class, + SpatieEnumMethodCallToEnumConstRector::class, + NullToStrictIntPregSlitFuncCallLimitArgRector::class, + ArrayToFirstClassCallableRector::class, + ArrowFunctionDelegatingCallToFirstClassCallableRector::class, + ClosureDelegatingCallToFirstClassCallableRector::class, + ClosureFromCallableToFirstClassCallableRector::class, + FunctionFirstClassCallableRector::class, + RemoveReflectionSetAccessibleCallsRector::class, + + // PHP 8.2 + ReadOnlyClassRector::class, + Utf8DecodeEncodeToMbConvertEncodingRector::class, + FilesystemIteratorSkipDotsRector::class, + VariableInStringInterpolationFixerRector::class, + + // PHP 8.3 + AddTypeToConstRector::class, + CombineHostPortLdapUriRector::class, + RemoveGetClassGetParentClassNoArgsRector::class, + ReadOnlyAnonymousClassRector::class, + DynamicClassConstFetchRector::class, + JsonValidateRector::class, + + // PHP 8.4 + ExplicitNullableParamTypeRector::class, + RoundingModeEnumRector::class, + AddEscapeArgumentRector::class, + NewMethodCallWithoutParenthesesRector::class, + ForeachToArrayFindRector::class, + ForeachToArrayFindKeyRector::class, + ForeachToArrayAllRector::class, + ForeachToArrayAnyRector::class, + + // PHP 8.5 + ArrayFirstLastRector::class, + RemoveFinfoBufferContextArgRector::class, + NullDebugInfoReturnRector::class, + ColonAfterSwitchCaseRector::class, + ArrayKeyExistsNullToEmptyStringRector::class, + ChrArgModuloRector::class, + SleepToSerializeRector::class, + OrdSingleByteRector::class, + WakeupToUnserializeRector::class, + ShellExecFunctionCallOverBackticksRector::class, + AddOverrideAttributeToOverriddenPropertiesRector::class, + + // PHP 8.6 + MinMaxToClampRector::class, + ]); + + // configured rules, each bound to the PHP version its configuration targets + + // PHP 5.2 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RemoveFuncCallArgRector::class, [ + // see https://www.php.net/manual/en/function.ldap-first-attribute.php + new RemoveFuncCallArg('ldap_first_attribute', 2), + ], PhpVersion::PHP_52); + + // PHP 5.4 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + 'mysqli_param_count' => 'mysqli_stmt_param_count', + ], PhpVersion::PHP_54); + + // PHP 5.6 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + 'mcrypt_generic_end' => 'mcrypt_generic_deinit', + 'set_socket_blocking' => 'stream_set_blocking', + 'ocibindbyname' => 'oci_bind_by_name', + 'ocicancel' => 'oci_cancel', + 'ocicolumnisnull' => 'oci_field_is_null', + 'ocicolumnname' => 'oci_field_name', + 'ocicolumnprecision' => 'oci_field_precision', + 'ocicolumnscale' => 'oci_field_scale', + 'ocicolumnsize' => 'oci_field_size', + 'ocicolumntype' => 'oci_field_type', + 'ocicolumntyperaw' => 'oci_field_type_raw', + 'ocicommit' => 'oci_commit', + 'ocidefinebyname' => 'oci_define_by_name', + 'ocierror' => 'oci_error', + 'ociexecute' => 'oci_execute', + 'ocifetch' => 'oci_fetch', + 'ocifetchstatement' => 'oci_fetch_all', + 'ocifreecursor' => 'oci_free_statement', + 'ocifreestatement' => 'oci_free_statement', + 'ociinternaldebug' => 'oci_internal_debug', + 'ocilogoff' => 'oci_close', + 'ocilogon' => 'oci_connect', + 'ocinewcollection' => 'oci_new_collection', + 'ocinewcursor' => 'oci_new_cursor', + 'ocinewdescriptor' => 'oci_new_descriptor', + 'ocinlogon' => 'oci_new_connect', + 'ocinumcols' => 'oci_num_fields', + 'ociparse' => 'oci_parse', + 'ociplogon' => 'oci_pconnect', + 'ociresult' => 'oci_result', + 'ocirollback' => 'oci_rollback', + 'ocirowcount' => 'oci_num_rows', + 'ociserverversion' => 'oci_server_version', + 'ocisetprefetch' => 'oci_set_prefetch', + 'ocistatementtype' => 'oci_statement_type', + ], PhpVersion::PHP_56); + + // PHP 7.2 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + # and imagewbmp + 'jpeg2wbmp' => 'imagecreatefromjpeg', + # or imagewbmp + 'png2wbmp' => 'imagecreatefrompng', + # migration72.deprecated.gmp_random-function + # http://php.net/manual/en/migration72.deprecated.php + # or gmp_random_range + 'gmp_random' => 'gmp_random_bits', + 'read_exif_data' => 'exif_read_data', + ], PhpVersion::PHP_72); + + // PHP 7.3 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + # https://wiki.php.net/rfc/deprecations_php_7_3 + 'image2wbmp' => 'imagewbmp', + 'mbregex_encoding' => 'mb_regex_encoding', + 'mbereg' => 'mb_ereg', + 'mberegi' => 'mb_eregi', + 'mbereg_replace' => 'mb_ereg_replace', + 'mberegi_replace' => 'mb_eregi_replace', + 'mbsplit' => 'mb_split', + 'mbereg_match' => 'mb_ereg_match', + 'mbereg_search' => 'mb_ereg_search', + 'mbereg_search_pos' => 'mb_ereg_search_pos', + 'mbereg_search_regs' => 'mb_ereg_search_regs', + 'mbereg_search_init' => 'mb_ereg_search_init', + 'mbereg_search_getregs' => 'mb_ereg_search_getregs', + 'mbereg_search_getpos' => 'mb_ereg_search_getpos', + ], PhpVersion::PHP_73); + + // PHP 7.4 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + # the_real_type + # https://wiki.php.net/rfc/deprecations_php_7_4 + 'is_real' => 'is_float', + ], PhpVersion::PHP_74); + + $rectorConfig->ruleWithConfigurationPhpVersionBound( + RenameCastRector::class, + [new RenameCast(Double::class, Double::KIND_REAL, Double::KIND_FLOAT)], + PhpVersion::PHP_74 + ); + + // PHP 8.0 + $rectorConfig->ruleWithConfigurationPhpVersionBound(StaticCallToFuncCallRector::class, [ + new StaticCallToFuncCall('Nette\Utils\Strings', 'startsWith', 'str_starts_with'), + new StaticCallToFuncCall('Nette\Utils\Strings', 'endsWith', 'str_ends_with'), + new StaticCallToFuncCall('Nette\Utils\Strings', 'contains', 'str_contains'), + ], PhpVersion::PHP_80); + + // nette\utils and Strings::replace() + $rectorConfig->ruleWithConfigurationPhpVersionBound( + ArgumentAdderRector::class, + [new ArgumentAdder('Nette\Utils\Strings', 'replace', 2, 'replacement', '')], + PhpVersion::PHP_80 + ); + + // @see https://php.watch/versions/8.0/pgsql-aliases-deprecated + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + 'pg_clientencoding' => 'pg_client_encoding', + 'pg_cmdtuples' => 'pg_affected_rows', + 'pg_errormessage' => 'pg_last_error', + 'pg_fieldisnull' => 'pg_field_is_null', + 'pg_fieldname' => 'pg_field_name', + 'pg_fieldnum' => 'pg_field_num', + 'pg_fieldprtlen' => 'pg_field_prtlen', + 'pg_fieldsize' => 'pg_field_size', + 'pg_fieldtype' => 'pg_field_type', + 'pg_freeresult' => 'pg_free_result', + 'pg_getlastoid' => 'pg_last_oid', + 'pg_loclose' => 'pg_lo_close', + 'pg_locreate' => 'pg_lo_create', + 'pg_loexport' => 'pg_lo_export', + 'pg_loimport' => 'pg_lo_import', + 'pg_loopen' => 'pg_lo_open', + 'pg_loread' => 'pg_lo_read', + 'pg_loreadall' => 'pg_lo_read_all', + 'pg_lounlink' => 'pg_lo_unlink', + 'pg_lowrite' => 'pg_lo_write', + 'pg_numfields' => 'pg_num_fields', + 'pg_numrows' => 'pg_num_rows', + 'pg_result' => 'pg_fetch_result', + 'pg_setclientencoding' => 'pg_set_client_encoding', + ], PhpVersion::PHP_80); + + $rectorConfig->ruleWithConfigurationPhpVersionBound(FunctionArgumentDefaultValueReplacerRector::class, [ + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'), + new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne'), + new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 0, false), + new ReplaceFuncCallArgumentDefaultValue('get_headers', 1, 1, true), + ], PhpVersion::PHP_80); + + // PHP 8.5 + $rectorConfig->ruleWithConfigurationPhpVersionBound(RemoveFuncCallArgRector::class, [ + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_key_length_parameter_of_openssl_pkey_derive + new RemoveFuncCallArg('openssl_pkey_derive', 2), + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_exclude_disabled_parameter_of_get_defined_functions + new RemoveFuncCallArg('get_defined_functions', 0), + ], PhpVersion::PHP_85); + + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameMethodRector::class, [ + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_splobjectstoragecontains_splobjectstorageattach_and_splobjectstoragedetach + new MethodCallRename('SplObjectStorage', 'contains', 'offsetExists'), + new MethodCallRename('SplObjectStorage', 'attach', 'offsetSet'), + new MethodCallRename('SplObjectStorage', 'detach', 'offsetUnset'), + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_driver_specific_pdo_constants_and_methods + new MethodCallRename('PDO', 'pgsqlCopyFromArray', 'copyFromArray'), + new MethodCallRename('PDO', 'pgsqlCopyFromFile', 'copyFromFile'), + new MethodCallRename('PDO', 'pgsqlCopyToArray', 'copyToArray'), + new MethodCallRename('PDO', 'pgsqlCopyToFile', 'copyToFile'), + new MethodCallRename('PDO', 'pgsqlGetNotify', 'getNotify'), + new MethodCallRename('PDO', 'pgsqlGetPid', 'getPid'), + new MethodCallRename('PDO', 'pgsqlLOBCreate', 'lobCreate'), + new MethodCallRename('PDO', 'pgsqlLOBOpen', 'lobOpen'), + new MethodCallRename('PDO', 'pgsqlLOBUnlink', 'lobUnlink'), + new MethodCallRename('PDO', 'sqliteCreateAggregate', 'createAggregate'), + new MethodCallRename('PDO', 'sqliteCreateCollation', 'createCollation'), + new MethodCallRename('PDO', 'sqliteCreateFunction', 'createFunction'), + ], PhpVersion::PHP_85); + + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameFunctionRector::class, [ + // https://wiki.php.net/rfc/deprecations_php_8_5#formally_deprecate_socket_set_timeout + 'socket_set_timeout' => 'stream_set_timeout', + + // https://wiki.php.net/rfc/deprecations_php_8_5#formally_deprecate_mysqli_execute + 'mysqli_execute' => 'mysqli_stmt_execute', + ], PhpVersion::PHP_85); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_driver_specific_pdo_constants_and_methods + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameClassConstFetchRector::class, [ + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_CONNECTION_TIMEOUT', 'Pdo\Dblib', 'ATTR_CONNECTION_TIMEOUT'), + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_QUERY_TIMEOUT', 'Pdo\Dblib', 'ATTR_QUERY_TIMEOUT'), + new RenameClassAndConstFetch( + 'PDO', + 'DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER', + 'Pdo\Dblib', + 'ATTR_STRINGIFY_UNIQUEIDENTIFIER' + ), + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_VERSION', 'Pdo\Dblib', 'ATTR_VERSION'), + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_TDS_VERSION', 'Pdo\Dblib', 'ATTR_TDS_VERSION'), + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_SKIP_EMPTY_ROWSETS', 'Pdo\Dblib', 'ATTR_SKIP_EMPTY_ROWSETS'), + new RenameClassAndConstFetch('PDO', 'DBLIB_ATTR_DATETIME_CONVERT', 'Pdo\Dblib', 'ATTR_DATETIME_CONVERT'), + new RenameClassAndConstFetch('PDO', 'FB_ATTR_DATE_FORMAT', 'Pdo\Firebird', 'ATTR_DATE_FORMAT'), + new RenameClassAndConstFetch('PDO', 'FB_ATTR_TIME_FORMAT', 'Pdo\Firebird', 'ATTR_TIME_FORMAT'), + new RenameClassAndConstFetch('PDO', 'FB_ATTR_TIMESTAMP_FORMAT', 'Pdo\Firebird', 'ATTR_TIMESTAMP_FORMAT'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_USE_BUFFERED_QUERY', 'Pdo\Mysql', 'ATTR_USE_BUFFERED_QUERY'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_LOCAL_INFILE', 'Pdo\Mysql', 'ATTR_LOCAL_INFILE'), + new RenameClassAndConstFetch( + 'PDO', + 'MYSQL_ATTR_LOCAL_INFILE_DIRECTORY', + 'Pdo\Mysql', + 'ATTR_LOCAL_INFILE_DIRECTORY' + ), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_INIT_COMMAND', 'Pdo\Mysql', 'ATTR_INIT_COMMAND'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_MAX_BUFFER_SIZE', 'Pdo\Mysql', 'ATTR_MAX_BUFFER_SIZE'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_READ_DEFAULT_FILE', 'Pdo\Mysql', 'ATTR_READ_DEFAULT_FILE'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_READ_DEFAULT_GROUP', 'Pdo\Mysql', 'ATTR_READ_DEFAULT_GROUP'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_COMPRESS', 'Pdo\Mysql', 'ATTR_COMPRESS'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_DIRECT_QUERY', 'Pdo\Mysql', 'ATTR_DIRECT_QUERY'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_FOUND_ROWS', 'Pdo\Mysql', 'ATTR_FOUND_ROWS'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_IGNORE_SPACE', 'Pdo\Mysql', 'ATTR_IGNORE_SPACE'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SSL_KEY', 'Pdo\Mysql', 'ATTR_SSL_KEY'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SSL_CERT', 'Pdo\Mysql', 'ATTR_SSL_CERT'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SSL_CA', 'Pdo\Mysql', 'ATTR_SSL_CA'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SSL_CAPATH', 'Pdo\Mysql', 'ATTR_SSL_CAPATH'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SSL_CIPHER', 'Pdo\Mysql', 'ATTR_SSL_CIPHER'), + new RenameClassAndConstFetch( + 'PDO', + 'MYSQL_ATTR_SSL_VERIFY_SERVER_CERT', + 'Pdo\Mysql', + 'ATTR_SSL_VERIFY_SERVER_CERT' + ), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_SERVER_PUBLIC_KEY', 'Pdo\Mysql', 'ATTR_SERVER_PUBLIC_KEY'), + new RenameClassAndConstFetch('PDO', 'MYSQL_ATTR_MULTI_STATEMENTS', 'Pdo\Mysql', 'ATTR_MULTI_STATEMENTS'), + new RenameClassAndConstFetch('PDO', 'ODBC_ATTR_USE_CURSOR_LIBRARY', 'Pdo\Odbc', 'ATTR_USE_CURSOR_LIBRARY'), + new RenameClassAndConstFetch('PDO', 'ODBC_ATTR_ASSUME_UTF8', 'Pdo\Odbc', 'ATTR_ASSUME_UTF8'), + new RenameClassAndConstFetch('PDO', 'ODBC_SQL_USE_IF_NEEDED', 'Pdo\Odbc', 'SQL_USE_IF_NEEDED'), + new RenameClassAndConstFetch('PDO', 'ODBC_SQL_USE_DRIVER', 'Pdo\Odbc', 'SQL_USE_DRIVER'), + new RenameClassAndConstFetch('PDO', 'ODBC_SQL_USE_ODBC', 'Pdo\Odbc', 'SQL_USE_ODBC'), + new RenameClassAndConstFetch('PDO', 'PGSQL_ATTR_DISABLE_PREPARES', 'Pdo\Pgsql', 'ATTR_DISABLE_PREPARES'), + new RenameClassAndConstFetch('PDO', 'SQLITE_ATTR_EXTENDED_RESULT_CODES', 'Pdo\Sqlite', 'ATTR_EXTENDED_RESULT_CODES'), + new RenameClassAndConstFetch('PDO', 'SQLITE_ATTR_OPEN_FLAGS', 'Pdo\Sqlite', 'OPEN_FLAGS'), + new RenameClassAndConstFetch('PDO', 'SQLITE_ATTR_READONLY_STATEMENT', 'Pdo\Sqlite', 'ATTR_READONLY_STATEMENT'), + new RenameClassAndConstFetch('PDO', 'SQLITE_DETERMINISTIC', 'Pdo\Sqlite', 'DETERMINISTIC'), + new RenameClassAndConstFetch('PDO', 'SQLITE_OPEN_READONLY', 'Pdo\Sqlite', 'OPEN_READONLY'), + new RenameClassAndConstFetch('PDO', 'SQLITE_OPEN_READWRITE', 'Pdo\Sqlite', 'OPEN_READWRITE'), + new RenameClassAndConstFetch('PDO', 'SQLITE_OPEN_CREATE', 'Pdo\Sqlite', 'OPEN_CREATE'), + ], PhpVersion::PHP_85); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_non-standard_cast_names + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameCastRector::class, [ + new RenameCast(Int_::class, Int_::KIND_INTEGER, Int_::KIND_INT), + new RenameCast(Bool_::class, Bool_::KIND_BOOLEAN, Bool_::KIND_BOOL), + new RenameCast(Double::class, Double::KIND_DOUBLE, Double::KIND_FLOAT), + new RenameCast(String_::class, String_::KIND_BINARY, String_::KIND_STRING), + ], PhpVersion::PHP_85); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_no-op_functions_from_the_resource_to_object_conversion + // these function have no effect when use + $rectorConfig->ruleWithConfigurationPhpVersionBound(RemoveFuncCallRector::class, [ + 'curl_close', 'curl_share_close', 'finfo_close', 'imagedestroy', 'xml_parser_free', + ], PhpVersion::PHP_85); + + // https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_filter_default_constant + $rectorConfig->ruleWithConfigurationPhpVersionBound(RenameConstantRector::class, [ + 'FILTER_DEFAULT' => 'FILTER_UNSAFE_RAW', + ], PhpVersion::PHP_85); +}; diff --git a/phpstan.neon b/phpstan.neon index 3a0fd261b44..6923a5ea250 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -76,6 +76,13 @@ parameters: paths: - src/Configuration/RectorConfigBuilder.php + # the deprecated SetList::PHP_* constants are still resolved internally by the level sets and their tests + - + identifier: classConstant.deprecated + paths: + - config/set/level + - tests/Bridge/SetRectorsResolverTest.php + # the deprecated github/gitlab output formatters are still tested until removed in next minor version - identifier: new.deprecatedClass diff --git a/src/Config/RectorConfig.php b/src/Config/RectorConfig.php index b57fb7e4129..384869e3faa 100644 --- a/src/Config/RectorConfig.php +++ b/src/Config/RectorConfig.php @@ -16,6 +16,7 @@ use Rector\Contract\Rector\RectorInterface; use Rector\Enum\Config\Defaults; use Rector\Exception\ShouldNotHappenException; +use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver; use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver; use Rector\Validation\RectorConfigValidator; use Rector\ValueObject\Configuration\LevelOverflow; @@ -277,6 +278,26 @@ public function ruleWithConfigurationComposerVersionBound( $this->ruleWithConfiguration($rectorClass, $configuration); } + /** + * Register the rule configuration only if the target PHP version is at least $phpVersion. + * Useful for a configuration valid from a specific PHP version, e.g. a function renamed in PHP 8.0. + * + * @param class-string $rectorClass + * @param mixed[] $configuration + * @param PhpVersion::* $phpVersion + */ + public function ruleWithConfigurationPhpVersionBound( + string $rectorClass, + array $configuration, + int $phpVersion + ): void { + if ($this->resolveTargetPhpVersion() < $phpVersion) { + return; + } + + $this->ruleWithConfiguration($rectorClass, $configuration); + } + /** * @param class-string $rectorClass */ @@ -626,4 +647,22 @@ private function resolveInstalledPackageVersion(string $packageName): ?string return $this->installedPackageResolver->resolvePackageVersion($packageName); } + + private function resolveTargetPhpVersion(): int + { + // an explicitly picked withPhpSets(phpXX: true) version is the target for its sets, + // even when it is above the project composer.json PHP version + if (SimpleParameterProvider::hasParameter(Option::POLYFILL_CEILING_PHP_VERSION)) { + $ceilingPhpVersion = SimpleParameterProvider::provideIntParameter(Option::POLYFILL_CEILING_PHP_VERSION); + if ($ceilingPhpVersion > 0) { + return $ceilingPhpVersion; + } + } + + if (SimpleParameterProvider::hasParameter(Option::PHP_VERSION_FEATURES)) { + return SimpleParameterProvider::provideIntParameter(Option::PHP_VERSION_FEATURES); + } + + return ComposerJsonPhpVersionResolver::resolve(getcwd() . '/composer.json') ?? PHP_VERSION_ID; + } } diff --git a/src/Configuration/PhpLevelSetResolver.php b/src/Configuration/PhpLevelSetResolver.php deleted file mode 100644 index 001806d2088..00000000000 --- a/src/Configuration/PhpLevelSetResolver.php +++ /dev/null @@ -1,59 +0,0 @@ - - */ - private const array VERSION_LOWER_BOUND_CONFIGS = [ - PhpVersion::PHP_52 => SetList::PHP_52, - PhpVersion::PHP_53 => SetList::PHP_53, - PhpVersion::PHP_54 => SetList::PHP_54, - PhpVersion::PHP_55 => SetList::PHP_55, - PhpVersion::PHP_56 => SetList::PHP_56, - PhpVersion::PHP_70 => SetList::PHP_70, - PhpVersion::PHP_71 => SetList::PHP_71, - PhpVersion::PHP_72 => SetList::PHP_72, - PhpVersion::PHP_73 => SetList::PHP_73, - PhpVersion::PHP_74 => SetList::PHP_74, - PhpVersion::PHP_80 => SetList::PHP_80, - PhpVersion::PHP_81 => SetList::PHP_81, - PhpVersion::PHP_82 => SetList::PHP_82, - PhpVersion::PHP_83 => SetList::PHP_83, - PhpVersion::PHP_84 => SetList::PHP_84, - PhpVersion::PHP_85 => SetList::PHP_85, - PhpVersion::PHP_86 => SetList::PHP_86, - ]; - - /** - * @param PhpVersion::* $phpVersion - * @return string[] - */ - public static function resolveFromPhpVersion(int $phpVersion): array - { - $configFilePaths = []; - - foreach (self::VERSION_LOWER_BOUND_CONFIGS as $versionLowerBound => $phpSetFilePath) { - if ($versionLowerBound <= $phpVersion) { - $configFilePaths[] = $phpSetFilePath; - } - } - - Assert::allFileExists($configFilePaths); - - return $configFilePaths; - } -} diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index e11f2105a0f..edd972ba24c 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -5,6 +5,8 @@ namespace Rector\Configuration; use Deprecated; +use DrupalRector\Set\DrupalSetList; +use Nette\Utils\Strings; use PhpParser\NodeVisitor; use Rector\Bridge\SetRectorsResolver; use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface; @@ -42,6 +44,11 @@ final class RectorConfigBuilder { private const int MAX_LEVEL_GAP = 10; + /** + * Matches the deprecated per-version PHP set files, e.g. .../config/set/php82.php + */ + private const string DEPRECATED_PHP_SET_REGEX = '#/config/set/php\d+\.php$#'; + /** * A level method and the set that contains the very same rules, * so they are never enabled both at once @@ -172,6 +179,12 @@ final class RectorConfigBuilder private ?int $pickedPhpSetsVersion = null; + /** + * Only an explicitly picked withPhpSets(phpXX) version acts as a ceiling; the composer.json + * fallback must not, so polyfilled rules can still be raised above the project PHP version + */ + private bool $isPhpSetsVersionPicked = false; + /** * @var LevelOverflow[] */ @@ -184,7 +197,7 @@ public function __invoke(RectorConfig $rectorConfig): void $this->sets[] = SetList::PHP_POLYFILLS; } - if ($this->pickedPhpSetsVersion !== null) { + if ($this->isPhpSetsVersionPicked && $this->pickedPhpSetsVersion !== null) { SimpleParameterProvider::setParameter( Option::POLYFILL_CEILING_PHP_VERSION, $this->pickedPhpSetsVersion @@ -408,6 +421,14 @@ public function withRootFiles(): self */ public function withSets(array $sets): self { + foreach ($sets as $set) { + if (Strings::match($set, self::DEPRECATED_PHP_SET_REGEX) === null) { + continue; + } + + Notifier::notifyDeprecatedPhpSet($set); + } + $this->sets = array_merge($this->sets, $sets); return $this; @@ -551,13 +572,13 @@ public function withPhpSets( ); } - // no version picked, resolve it from the project composer.json + // no version picked, target the project composer.json PHP version if ($pickedPhpVersions === []) { return $this->addPhpLevelSets(ComposerJsonPhpVersionResolver::resolveFromCwdOrFail()); } // explicitly picked version is a ceiling, even for polyfilled rules - $this->pickedPhpSetsVersion = $pickedPhpVersions[0]; + $this->isPhpSetsVersionPicked = true; return $this->addPhpLevelSets($pickedPhpVersions[0]); } @@ -688,7 +709,7 @@ public function withComposerBased( if ($drupal && class_exists('DrupalRector\Set\DrupalSetList') && constant('DrupalRector\Set\DrupalSetList::COMPOSER_BASED')) { // waits on https://github.com/palantirnet/drupal-rector/pull/419/files#diff-c6bd4ee854830efc1363a7d99c1b6a2e7e64f2499a51e503174ab777de7e64e5 - $this->sets[] = \DrupalRector\Set\DrupalSetList::COMPOSER_BASED; + $this->sets[] = DrupalSetList::COMPOSER_BASED; } if ($phpunit) { @@ -962,12 +983,10 @@ public function withPhpLevel(int $level): self $this->isWithPhpLevelUsed = true; - $phpVersion = ComposerJsonPhpVersionResolver::resolveFromCwdOrFail(); - $setRectorsResolver = new SetRectorsResolver(); - $setFilePaths = PhpLevelSetResolver::resolveFromPhpVersion($phpVersion); - - $rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathsIncludingConfiguration($setFilePaths); + $rectorRulesWithConfiguration = $setRectorsResolver->resolveFromFilePathIncludingConfiguration( + SetList::PHP_VERSION_BASED_SET + ); foreach ($rectorRulesWithConfiguration as $position => $rectorRuleWithConfiguration) { // add rules until level is reached @@ -1143,8 +1162,9 @@ public function withSetProviders(): self private function addPhpLevelSets(int $phpVersion): self { $this->isWithPhpSetsUsed = true; + $this->pickedPhpSetsVersion = $phpVersion; - $this->sets = array_merge($this->sets, PhpLevelSetResolver::resolveFromPhpVersion($phpVersion)); + $this->sets[] = SetList::PHP_VERSION_BASED_SET; return $this; } diff --git a/src/Console/Notifier.php b/src/Console/Notifier.php index 76fd77bcd12..06539f12f5b 100644 --- a/src/Console/Notifier.php +++ b/src/Console/Notifier.php @@ -28,6 +28,18 @@ public static function notifyNotSuitableMethodForPHP74(string $calledMethod): vo sleep(3); } + public static function notifyDeprecatedPhpSet(string $set): void + { + $message = sprintf( + 'The per-version PHP set "%s" is deprecated. Use "withPhpSets()" or "withPhpLevel()" instead, ' + . 'they pick the rules by your PHP version automatically.', + $set + ); + + $symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); + $symfonyStyle->warning($message); + } + public static function errorWithPhpSetsNotSuitableForPHP74AndLower(): void { if (PHP_VERSION_ID >= 80000) { diff --git a/src/Set/ValueObject/SetList.php b/src/Set/ValueObject/SetList.php index 209a06a8cc7..5683b300cda 100644 --- a/src/Set/ValueObject/SetList.php +++ b/src/Set/ValueObject/SetList.php @@ -35,38 +35,89 @@ final class SetList */ public const string RECTOR_PRESET = __DIR__ . '/../../../config/set/rector-preset.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_52 = __DIR__ . '/../../../config/set/php52.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_53 = __DIR__ . '/../../../config/set/php53.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_54 = __DIR__ . '/../../../config/set/php54.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_55 = __DIR__ . '/../../../config/set/php55.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_56 = __DIR__ . '/../../../config/set/php56.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_70 = __DIR__ . '/../../../config/set/php70.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_71 = __DIR__ . '/../../../config/set/php71.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_72 = __DIR__ . '/../../../config/set/php72.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_73 = __DIR__ . '/../../../config/set/php73.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_74 = __DIR__ . '/../../../config/set/php74.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_80 = __DIR__ . '/../../../config/set/php80.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_81 = __DIR__ . '/../../../config/set/php81.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_82 = __DIR__ . '/../../../config/set/php82.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_83 = __DIR__ . '/../../../config/set/php83.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_84 = __DIR__ . '/../../../config/set/php84.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_85 = __DIR__ . '/../../../config/set/php85.php'; + /** + * @deprecated Use withPhpSets() or withPhpLevel() instead + */ public const string PHP_86 = __DIR__ . '/../../../config/set/php86.php'; public const string PRIVATIZATION = __DIR__ . '/../../../config/set/privatization.php'; @@ -87,4 +138,9 @@ final class SetList public const string CARBON = __DIR__ . '/../../../config/set/datetime-to-carbon.php'; public const string BEHAT_ANNOTATIONS_TO_ATTRIBUTES = __DIR__ . '/../../../config/set/behat-annotations-to-attributes.php'; + + /** + * All PHP version rules in one set; each rule gates itself by PHP version at runtime + */ + public const string PHP_VERSION_BASED_SET = __DIR__ . '/../../../config/set/php-version-based.php'; } diff --git a/src/VersionBonding/PhpVersionedFilter.php b/src/VersionBonding/PhpVersionedFilter.php index 478812b2037..c387fa16c6f 100644 --- a/src/VersionBonding/PhpVersionedFilter.php +++ b/src/VersionBonding/PhpVersionedFilter.php @@ -50,9 +50,15 @@ public function filter(array $rectors): array continue; } - $maxPhpVersion = $rector instanceof RelatedPolyfillInterface && $ceilingPhpVersion !== null - ? $ceilingPhpVersion - : $minProjectPhpVersion; + // an explicitly picked withPhpSets() version caps the whole set: + // polyfilled rules up to the ceiling, the rest up to the lower of ceiling and project version + if ($ceilingPhpVersion !== null) { + $maxPhpVersion = $rector instanceof RelatedPolyfillInterface + ? $ceilingPhpVersion + : min($ceilingPhpVersion, $minProjectPhpVersion); + } else { + $maxPhpVersion = $minProjectPhpVersion; + } // does satisfy version? → include if ($rector->provideMinPhpVersion() <= $maxPhpVersion) { diff --git a/tests/Bridge/SetRectorsResolverTest.php b/tests/Bridge/SetRectorsResolverTest.php index 32cf9a64420..24fb0508187 100644 --- a/tests/Bridge/SetRectorsResolverTest.php +++ b/tests/Bridge/SetRectorsResolverTest.php @@ -6,11 +6,8 @@ use PHPUnit\Framework\TestCase; use Rector\Bridge\SetRectorsResolver; -use Rector\Configuration\PhpLevelSetResolver; use Rector\Contract\Rector\RectorInterface; -use Rector\Php\PhpVersionResolver\ComposerJsonPhpVersionResolver; use Rector\Set\ValueObject\SetList; -use Rector\ValueObject\PhpVersion; final class SetRectorsResolverTest extends TestCase { @@ -21,31 +18,16 @@ protected function setUp(): void $this->setRectorsResolver = new SetRectorsResolver(); } - public function testResolveFromFilePathForPhpVersion(): void + public function testResolvePhpVersionBasedSet(): void { - $configFilePaths = PhpLevelSetResolver::resolveFromPhpVersion(PhpVersion::PHP_70); - $this->assertCount(6, $configFilePaths); - $this->assertContainsOnlyString($configFilePaths); - - foreach ($configFilePaths as $configFilePath) { - $this->assertFileExists($configFilePath); - } - } - - public function testResolveFromFilePathForPhpLevel(): void - { - $projectPhpVersion = ComposerJsonPhpVersionResolver::resolve(__DIR__ . '/Fixture/some-composer.json'); - - $this->assertIsInt($projectPhpVersion); - $this->assertSame(PhpVersion::PHP_73, $projectPhpVersion); - - $configFilePaths = PhpLevelSetResolver::resolveFromPhpVersion($projectPhpVersion); - $this->assertCount(9, $configFilePaths); + $phpVersionBasedSetFilePath = dirname(__DIR__, 2) . '/config/set/php-version-based.php'; $rectorRulesWithConfiguration = $this->setRectorsResolver->resolveFromFilePathsIncludingConfiguration( - $configFilePaths + [$phpVersionBasedSetFilePath] ); - $this->assertCount(63, $rectorRulesWithConfiguration); + + $this->assertNotEmpty($rectorRulesWithConfiguration); + $this->assertContainsOnlyRules($rectorRulesWithConfiguration); } public function testResolveWithConfiguration(): void @@ -58,6 +40,14 @@ public function testResolveWithConfiguration(): void $this->assertArrayHasKey(0, $rectorRulesWithConfiguration); $this->assertArrayHasKey(8, $rectorRulesWithConfiguration); + $this->assertContainsOnlyRules($rectorRulesWithConfiguration); + } + + /** + * @param array|array, mixed[]>> $rectorRulesWithConfiguration + */ + private function assertContainsOnlyRules(array $rectorRulesWithConfiguration): void + { foreach ($rectorRulesWithConfiguration as $rectorRuleWithConfiguration) { if (is_string($rectorRuleWithConfiguration)) { $this->assertTrue(is_a($rectorRuleWithConfiguration, RectorInterface::class, true)); diff --git a/tests/Configuration/PhpLevelSetResolverTest.php b/tests/Configuration/PhpLevelSetResolverTest.php deleted file mode 100644 index af129f2fe30..00000000000 --- a/tests/Configuration/PhpLevelSetResolverTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertCount(5, $phpSetFiles); - - $this->assertSame([ - SetList::PHP_52, - SetList::PHP_53, - SetList::PHP_54, - SetList::PHP_55, - SetList::PHP_56, - ], $phpSetFiles); - } -}