diff --git a/resources/php_compat_info.json b/resources/php_compat_info.json new file mode 100644 index 00000000..88441d06 --- /dev/null +++ b/resources/php_compat_info.json @@ -0,0 +1 @@ +{"@generated":true,"params":{"is_a":{"2":"5.3.9"},"nl2br":{"1":"5.3.0"},"getopt":{"1":"5.3.0"},"copy":{"2":"5.3.0"},"opendir":{"1":"5.3.0"},"clearstatcache":["5.3.0","5.3.0"],"scandir":{"2":"5.3.0"},"json_decode":{"2":"5.3.0","3":"5.4.0"}},"functions":{"ldap_control_paged_result":"5.4.0","ldap_control_paged_result_response":"5.4.0","date_add":"5.3.0","date_create_from_format":"5.3.0","date_diff":"5.3.0","date_get_last_errors":"5.3.0","date_interval_create_from_date_string":"5.3.0","date_interval_format":"5.3.0","date_parse_from_format":"5.3.0","date_sub":"5.3.0","date_timestamp_get":"5.3.0","date_timestamp_set":"5.3.0","timezone_location_get":"5.3.0","timezone_version_get":"5.3.0","msg_queue_exists":"5.3.0","openssl_cipher_iv_length":"5.3.3","openssl_decrypt":"5.3.0","openssl_dh_compute_key":"5.3.0","openssl_digest":"5.3.0","openssl_encrypt":"5.3.0","openssl_get_cipher_methods":"5.3.0","openssl_get_md_methods":"5.3.0","openssl_random_pseudo_bytes":"5.3.0","socket_import_stream":"5.4.0","class_alias":"5.3.0","gc_collect_cycles":"5.3.0","gc_enabled":"5.3.0","gc_enable":"5.3.0","gc_disable":"5.3.0","get_called_class":"5.3.0","get_declared_traits":"5.4.0","trait_exists":"5.4.0","array_replace":"5.3.0","array_replace_recursive":"5.3.0","forward_static_call":"5.3.0","forward_static_call_array":"5.3.0","gethostname":"5.3.0","getimagesizefromstring":"5.4.0","header_register_callback":"5.4.0","header_remove":"5.3.0","hex2bin":"5.4.0","http_response_code":"5.4.0","lcfirst":"5.3.0","parse_ini_string":"5.3.0","php_ini_loaded_file":"5.2.4","quoted_printable_encode":"5.3.0","realpath_cache_get":"5.3.2","realpath_cache_size":"5.3.2","str_getcsv":"5.3.0","stream_context_get_params":"5.3.0","stream_context_set_default":"5.3.0","stream_set_read_buffer":"5.3.3","stream_is_local":"5.2.4","stream_resolve_include_path":"5.3.2","stream_set_chunk_size":"5.4.0","stream_supports_lock":"5.3.0","pg_escape_literal":"5.4.4","pg_escape_identifier":"5.4.4","gmp_testbit":"5.3.0","preg_filter":"5.3.0","hash_copy":"5.3.0","shm_has_var":"5.3.0","gzdecode":"5.4.0","zlib_decode":"5.4.0","zlib_encode":"5.4.0","pcntl_signal_dispatch":"5.3.0","pcntl_sigprocmask":"5.3.0","pcntl_sigtimedwait":"5.3.0","pcntl_sigwaitinfo":"5.3.0","pcntl_get_last_error":"5.3.4","pcntl_errno":"5.3.4","pcntl_strerror":"5.3.4","enchant_broker_describe":"5.3.0","enchant_broker_dict_exists":"5.3.0","enchant_broker_free_dict":"5.3.0","enchant_broker_free":"5.3.0","enchant_broker_get_error":"5.3.0","enchant_broker_init":"5.3.0","enchant_broker_request_dict":"5.3.0","enchant_broker_request_pwl_dict":"5.3.0","enchant_broker_set_ordering":"5.3.0","enchant_dict_add_to_personal":"5.3.0","enchant_dict_add_to_session":"5.3.0","enchant_dict_check":"5.3.0","enchant_dict_describe":"5.3.0","enchant_dict_get_error":"5.3.0","enchant_dict_is_in_session":"5.3.0","enchant_dict_store_replacement":"5.3.0","enchant_dict_suggest":"5.3.0","enchant_dict_quick_check":"5.3.0","enchant_broker_list_dicts":"5.3.0","enchant_broker_set_dict_path":"5.3.1","enchant_broker_get_dict_path":"5.3.1","imap_fetchmime":"5.3.6","imap_gc":"5.3.0","imap_utf8_to_mutf7":"5.3.0","imap_mutf7_to_utf8":"5.3.0","class_uses":"5.4.0","libxml_disable_entity_loader":"5.2.11","libxml_set_external_entity_loader":"5.4.0","json_last_error":"5.3.0","mb_encoding_aliases":"5.3.0","mb_ereg_replace_callback":"5.4.1","collator_asort":"5.2.4","collator_compare":"5.2.4","collator_create":"5.2.4","collator_get_attribute":"5.2.4","collator_get_error_code":"5.2.4","collator_get_error_message":"5.2.4","collator_get_locale":"5.2.4","collator_get_sort_key":"5.2.4","collator_get_strength":"5.2.4","collator_set_attribute":"5.2.4","collator_set_strength":"5.2.4","collator_sort":"5.2.4","collator_sort_with_sort_keys":"5.2.4","datefmt_create":"5.2.4","datefmt_format":"5.2.4","datefmt_get_calendar":"5.2.4","datefmt_get_datetype":"5.2.4","datefmt_get_error_code":"5.2.4","datefmt_get_error_message":"5.2.4","datefmt_get_locale":"5.2.4","datefmt_get_pattern":"5.2.4","datefmt_get_timetype":"5.2.4","datefmt_get_timezone_id":"5.2.4","datefmt_is_lenient":"5.2.4","datefmt_localtime":"5.2.4","datefmt_parse":"5.2.4","datefmt_set_calendar":"5.2.4","datefmt_set_lenient":"5.2.4","datefmt_set_pattern":"5.2.4","datefmt_set_timezone_id":"5.2.4","grapheme_extract":"5.2.4","grapheme_stripos":"5.2.4","grapheme_stristr":"5.2.4","grapheme_strlen":"5.2.4","grapheme_strpos":"5.2.4","grapheme_strripos":"5.2.4","grapheme_strrpos":"5.2.4","grapheme_strstr":"5.2.4","grapheme_substr":"5.2.4","intl_error_name":"5.2.4","intl_get_error_code":"5.2.4","intl_get_error_message":"5.2.4","intl_is_failure":"5.2.4","locale_accept_from_http":"5.2.4","locale_canonicalize":"5.2.4","locale_compose":"5.2.4","locale_filter_matches":"5.2.4","locale_get_all_variants":"5.2.4","locale_get_default":"5.2.4","locale_get_display_language":"5.2.4","locale_get_display_name":"5.2.4","locale_get_display_region":"5.2.4","locale_get_display_script":"5.2.4","locale_get_display_variant":"5.2.4","locale_get_keywords":"5.2.4","locale_get_primary_language":"5.2.4","locale_get_region":"5.2.4","locale_get_script":"5.2.4","locale_lookup":"5.2.4","locale_parse":"5.2.4","locale_set_default":"5.2.4","msgfmt_create":"5.2.4","msgfmt_format":"5.2.4","msgfmt_format_message":"5.2.4","msgfmt_get_error_code":"5.2.4","msgfmt_get_error_message":"5.2.4","msgfmt_get_locale":"5.2.4","msgfmt_get_pattern":"5.2.4","msgfmt_parse":"5.2.4","msgfmt_parse_message":"5.2.4","msgfmt_set_pattern":"5.2.4","normalizer_is_normalized":"5.2.4","normalizer_normalize":"5.2.4","numfmt_create":"5.2.4","numfmt_format":"5.2.4","numfmt_format_currency":"5.2.4","numfmt_get_attribute":"5.2.4","numfmt_get_error_code":"5.2.4","numfmt_get_error_message":"5.2.4","numfmt_get_locale":"5.2.4","numfmt_get_pattern":"5.2.4","numfmt_get_symbol":"5.2.4","numfmt_get_text_attribute":"5.2.4","numfmt_parse":"5.2.4","numfmt_parse_currency":"5.2.4","numfmt_set_attribute":"5.2.4","numfmt_set_pattern":"5.2.4","numfmt_set_symbol":"5.2.4","numfmt_set_text_attribute":"5.2.4","resourcebundle_count":"5.2.4","resourcebundle_create":"5.2.4","resourcebundle_get":"5.2.4","resourcebundle_get_error_code":"5.2.4","resourcebundle_get_error_message":"5.2.4","resourcebundle_locales":"5.2.4","transliterator_create":"5.4.0","transliterator_create_from_rules":"5.4.0","transliterator_create_inverse":"5.4.0","transliterator_get_error_code":"5.4.0","transliterator_get_error_message":"5.4.0","transliterator_list_ids":"5.4.0","transliterator_transliterate":"5.4.0","idn_to_ascii":"5.2.4","idn_to_utf8":"5.2.4","mysqli_error_list":"5.4.0","mysqli_refresh":"5.3.0","mysqli_stmt_error_list":"5.4.0","session_register_shutdown":"5.4.0","session_status":"5.4.0"},"classes":{"dateinterval":"5.3.0","dateperiod":"5.3.0","snmp":"5.4.0","closure":"5.3.0","reflectionzendextension":"5.4.0","sqlite3":"5.3.0","sqlite3stmt":"5.3.0","sqlite3result":"5.3.0","callbackfilteriterator":"5.4.0","recursivecallbackfilteriterator":"5.4.0","recursivetreeiterator":"5.3.0","filesystemiterator":"5.3.0","globiterator":"5.3.0","spldoublylinkedlist":"5.3.0","splqueue":"5.3.0","splstack":"5.3.0","splheap":"5.3.0","splminheap":"5.3.0","splmaxheap":"5.3.0","splpriorityqueue":"5.3.0","splfixedarray":"5.3.0","multipleiterator":"5.3.0","collator":"5.2.4","numberformatter":"5.2.4","locale":"5.2.4","normalizer":"5.2.4","messageformatter":"5.2.4","intldateformatter":"5.2.4","resourcebundle":"5.2.4","transliterator":"5.4.0","spoofchecker":"5.4.0","sessionhandler":"5.4.0"},"interfaces":{"jsonserializable":"5.4.0","sessionhandlerinterface":"5.4.0"}} \ No newline at end of file diff --git a/scripts/update_compat_info.php b/scripts/update_compat_info.php new file mode 100755 index 00000000..1006a66f --- /dev/null +++ b/scripts/update_compat_info.php @@ -0,0 +1,63 @@ +#!/usr/bin/env php +getAll(); + +$output = array(); +$output['@'.'generated'] = true; +$output['params'] = array(); + +foreach (array('functions', 'classes', 'interfaces') as $type) { + $output[$type] = array(); + foreach ($reference[$type] as $name => $versions) { + $name = strtolower($name); + $versions = reset($versions); + list($min, $max) = $versions; + if (version_compare($min, $required) > 0) { + $output[$type][$name] = $min; + } + if ($type == 'functions' && isset($versions[2])) { + $params = explode(', ', $versions[2]); + foreach ($params as $i => $version) { + if (version_compare($version, $required) > 0) { + $output['params'][$name][$i] = $version; + } + } + } + } +} + +file_put_contents( + dirname(__FILE__).'/../'.$target, + json_encode($output)); + +echo "Done.\n"; diff --git a/src/lint/linter/ArcanistXHPASTLinter.php b/src/lint/linter/ArcanistXHPASTLinter.php index a865477c..e34a7f68 100644 --- a/src/lint/linter/ArcanistXHPASTLinter.php +++ b/src/lint/linter/ArcanistXHPASTLinter.php @@ -302,6 +302,53 @@ final class ArcanistXHPASTLinter extends ArcanistLinter { } } + $this->lintPHP53Functions($root); + } + + private function lintPHP53Functions($root) { + $target = dirname(__FILE__).'/../../../resources/php_compat_info.json'; + $compat_info = json_decode(file_get_contents($target), true); + + $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); + foreach ($calls as $call) { + $node = $call->getChildByIndex(0); + $name = strtolower($node->getConcreteString()); + $version = idx($compat_info['functions'], $name); + if ($version) { + $this->raiseLintAtNode( + $node, + self::LINT_PHP_53_FEATURES, + "This codebase targets PHP 5.2.3, but `{$name}()` was not ". + "introduced until PHP {$version}."); + } else if (array_key_exists($name, $compat_info['params'])) { + $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); + foreach (array_values($params->getChildren()) as $i => $param) { + $version = idx($compat_info['params'][$name], $i); + if ($version) { + $this->raiseLintAtNode( + $param, + self::LINT_PHP_53_FEATURES, + "This codebase targets PHP 5.2.3, but parameter ".($i + 1)." ". + "of `{$name}()` was not introduced until PHP {$version}."); + } + } + } + } + + $classes = $root->selectDescendantsOfType('n_CLASS_NAME'); + foreach ($classes as $node) { + $name = strtolower($node->getConcreteString()); + $version = idx($compat_info['interfaces'], $name); + $version = idx($compat_info['classes'], $name, $version); + if ($version) { + $this->raiseLintAtNode( + $node, + self::LINT_PHP_53_FEATURES, + "This codebase targets PHP 5.2.3, but `{$name}` was not ". + "introduced until PHP {$version}."); + } + } + } private function lintImplicitFallthrough($root) {