Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb800f032e | ||
|
|
64b9f9443e | ||
|
|
fd2d3a0e06 | ||
|
|
08d678bdb8 | ||
|
|
418007e572 | ||
|
|
4a1bccdf75 | ||
|
|
00c0b59e9d | ||
|
|
ac82c8ff0b | ||
|
|
f4bef8a090 | ||
|
|
d8a1055bff | ||
|
|
d672ce4d80 | ||
|
|
43ec361d03 | ||
|
|
c3c99c5f76 | ||
|
|
eb1e1421ab | ||
|
|
e8a1d87919 | ||
|
|
921ba6811d | ||
|
|
906dd241b4 |
+36
-30
@@ -15,20 +15,34 @@ return [
|
||||
// Note that the **only** effect of choosing `'5.6'` is to infer
|
||||
// that functions removed in php 7.0 exist.
|
||||
// (See `backward_compatibility_checks` for additional options)
|
||||
"target_php_version" => '7.4',
|
||||
"target_php_version" => '8.0',
|
||||
'backward_compatibility_checks ' => true,
|
||||
'minimum_severity'=>\Phan\Issue::SEVERITY_CRITICAL,
|
||||
'minimum_severity' => \Phan\Issue::SEVERITY_NORMAL,
|
||||
|
||||
'file_list' => [
|
||||
'f_config/config.php',
|
||||
'hwe/_119_b.php',
|
||||
'hwe/_119.php',
|
||||
'hwe/a_bestGeneral.php',
|
||||
'hwe/a_emperior.php',
|
||||
'hwe/_admin1.php',
|
||||
'hwe/_admin1_submit.php',
|
||||
'hwe/_admin2.php',
|
||||
'hwe/_admin2_submit.php',
|
||||
'hwe/_admin4.php',
|
||||
'hwe/_admin4_submit.php',
|
||||
'hwe/_admin5.php',
|
||||
'hwe/_admin5_submit.php',
|
||||
'hwe/_admin7.php',
|
||||
'hwe/_admin8.php',
|
||||
'hwe/_admin_force_rehall.php',
|
||||
'hwe/a_emperior_detail.php',
|
||||
'hwe/a_emperior.php',
|
||||
'hwe/a_genList.php',
|
||||
'hwe/a_hallOfFame.php',
|
||||
'hwe/a_history.php',
|
||||
'hwe/a_kingdomList.php',
|
||||
'hwe/a_npcList.php',
|
||||
'hwe/api.php',
|
||||
'hwe/a_status.php',
|
||||
'hwe/a_traffic.php',
|
||||
'hwe/a_vote.php',
|
||||
@@ -41,13 +55,11 @@ return [
|
||||
'hwe/b_dipcenter.php',
|
||||
'hwe/b_diplomacy.php',
|
||||
'hwe/b_genList.php',
|
||||
'hwe/v_inheritPoint.php',
|
||||
'hwe/b_myBossInfo.php',
|
||||
'hwe/b_myCityInfo.php',
|
||||
'hwe/b_myGenInfo.php',
|
||||
'hwe/b_myKingdomInfo.php',
|
||||
'hwe/b_myPage.php',
|
||||
'hwe/b_npc_control.php',
|
||||
'hwe/b_processing.php',
|
||||
'hwe/b_tournament.php',
|
||||
'hwe/b_troop.php',
|
||||
@@ -55,7 +67,6 @@ return [
|
||||
'hwe/c_dipcenter.php',
|
||||
'hwe/c_tournament.php',
|
||||
'hwe/c_vote.php',
|
||||
'hwe/func.php',
|
||||
'hwe/func_auction.php',
|
||||
'hwe/func_command.php',
|
||||
'hwe/func_converter.php',
|
||||
@@ -64,16 +75,15 @@ return [
|
||||
'hwe/func_legacy.php',
|
||||
'hwe/func_map.php',
|
||||
'hwe/func_message.php',
|
||||
'hwe/func.php',
|
||||
'hwe/func_process.php',
|
||||
'hwe/func_string.php',
|
||||
'hwe/func_template.php',
|
||||
'hwe/func_time_event.php',
|
||||
'hwe/func_tournament.php',
|
||||
'hwe/index.php',
|
||||
'hwe/install.php',
|
||||
'hwe/install_db.php',
|
||||
'hwe/join.php',
|
||||
'hwe/join_post.php',
|
||||
'hwe/install.php',
|
||||
'hwe/j_adjust_icon.php',
|
||||
'hwe/j_autoreset.php',
|
||||
'hwe/j_basic_info.php',
|
||||
@@ -92,8 +102,8 @@ return [
|
||||
'hwe/j_general_log_old.php',
|
||||
'hwe/j_general_set_permission.php',
|
||||
'hwe/j_general_turn.php',
|
||||
'hwe/j_getChiefTurn.php',
|
||||
'hwe/j_get_basic_general_list.php',
|
||||
'hwe/j_getChiefTurn.php',
|
||||
'hwe/j_get_city_list.php',
|
||||
'hwe/j_get_general_list.php',
|
||||
'hwe/j_get_nation_general_list.php',
|
||||
@@ -101,11 +111,12 @@ return [
|
||||
'hwe/j_get_select_npc_token.php',
|
||||
'hwe/j_get_select_pool.php',
|
||||
'hwe/j_image_upload.php',
|
||||
'hwe/j_install.php',
|
||||
'hwe/j_install_db.php',
|
||||
'hwe/j_install.php',
|
||||
'hwe/j_load_scenarios.php',
|
||||
'hwe/j_map.php',
|
||||
'hwe/j_map_history.php',
|
||||
'hwe/j_map.php',
|
||||
'hwe/j_map_recent.php',
|
||||
'hwe/j_msg_contact_list.php',
|
||||
'hwe/j_msg_decide_opt.php',
|
||||
'hwe/j_msg_delete.php',
|
||||
@@ -123,26 +134,20 @@ return [
|
||||
'hwe/j_set_npc_control.php',
|
||||
'hwe/j_simulate_battle.php',
|
||||
'hwe/j_troop.php',
|
||||
'hwe/j_update_picked_general.php',
|
||||
'hwe/j_vacation.php',
|
||||
'hwe/lib.php',
|
||||
'hwe/process_war.php',
|
||||
'hwe/proc.php',
|
||||
'hwe/readme.php',
|
||||
'hwe/recent_map.php',
|
||||
'hwe/select_general_from_pool.php',
|
||||
'hwe/select_npc.php',
|
||||
'hwe/t_board.php',
|
||||
'hwe/t_diplomacy.php',
|
||||
'hwe/_119.php',
|
||||
'hwe/_119_b.php',
|
||||
'hwe/_admin1.php',
|
||||
'hwe/_admin1_submit.php',
|
||||
'hwe/_admin2.php',
|
||||
'hwe/_admin2_submit.php',
|
||||
'hwe/_admin4.php',
|
||||
'hwe/_admin4_submit.php',
|
||||
'hwe/_admin5.php',
|
||||
'hwe/_admin5_submit.php',
|
||||
'hwe/_admin7.php',
|
||||
'hwe/_admin8.php',
|
||||
'hwe/_admin_force_rehall.php',
|
||||
'hwe/v_board.php',
|
||||
'hwe/v_inheritPoint.php',
|
||||
'hwe/v_join.php',
|
||||
'hwe/v_NPCControl.php',
|
||||
],
|
||||
|
||||
// A list of directories that should be parsed for class and
|
||||
@@ -153,9 +158,9 @@ return [
|
||||
// Thus, both first-party and third-party code being used by
|
||||
// your application should be included in this list.
|
||||
'directory_list' => [
|
||||
'hwe/d_setting',
|
||||
'hwe/sammo',
|
||||
'd_setting',
|
||||
'hwe/d_setting',
|
||||
'hwe/sammo',
|
||||
'd_setting',
|
||||
'src/sammo',
|
||||
'src/kakao',
|
||||
'vendor'
|
||||
@@ -179,6 +184,7 @@ return [
|
||||
|
||||
'suppress_issue_types' => [
|
||||
'PhanUnreferencedUseNormal',
|
||||
'PhanTypeArraySuspiciousNullable',
|
||||
],
|
||||
|
||||
// A list of plugin files to execute.
|
||||
@@ -195,4 +201,4 @@ return [
|
||||
'PregRegexCheckerPlugin',
|
||||
'PrintfCheckerPlugin',
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
Generated
+7
-7
@@ -1886,16 +1886,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phan/phan",
|
||||
"version": "5.0.0",
|
||||
"version": "5.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phan/phan.git",
|
||||
"reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90"
|
||||
"reference": "d724bbb8fc3ba89179868bb5220ecdf6a9c1636b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phan/phan/zipball/f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
|
||||
"reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
|
||||
"url": "https://api.github.com/repos/phan/phan/zipball/d724bbb8fc3ba89179868bb5220ecdf6a9c1636b",
|
||||
"reference": "d724bbb8fc3ba89179868bb5220ecdf6a9c1636b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1912,7 +1912,7 @@
|
||||
"symfony/console": "^3.2|^4.0|^5.0",
|
||||
"symfony/polyfill-mbstring": "^1.11.0",
|
||||
"symfony/polyfill-php80": "^1.20.0",
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2"
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.0"
|
||||
@@ -1959,9 +1959,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phan/phan/issues",
|
||||
"source": "https://github.com/phan/phan/tree/5.0.0"
|
||||
"source": "https://github.com/phan/phan/tree/5.2.1"
|
||||
},
|
||||
"time": "2021-08-01T18:17:28+00:00"
|
||||
"time": "2021-09-15T00:08:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-extended/php-system",
|
||||
|
||||
+16
-16
@@ -15,35 +15,35 @@ ini_set("session.gc_maxlifetime", '604800'); // seconds
|
||||
function getFriendlyErrorType($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case E_ERROR: // 1 //
|
||||
case E_ERROR: // 1 //
|
||||
return 'E_ERROR';
|
||||
case E_WARNING: // 2 //
|
||||
case E_WARNING: // 2 //
|
||||
return 'E_WARNING';
|
||||
case E_PARSE: // 4 //
|
||||
case E_PARSE: // 4 //
|
||||
return 'E_PARSE';
|
||||
case E_NOTICE: // 8 //
|
||||
case E_NOTICE: // 8 //
|
||||
return 'E_NOTICE';
|
||||
case E_CORE_ERROR: // 16 //
|
||||
case E_CORE_ERROR: // 16 //
|
||||
return 'E_CORE_ERROR';
|
||||
case E_CORE_WARNING: // 32 //
|
||||
case E_CORE_WARNING: // 32 //
|
||||
return 'E_CORE_WARNING';
|
||||
case E_COMPILE_ERROR: // 64 //
|
||||
case E_COMPILE_ERROR: // 64 //
|
||||
return 'E_COMPILE_ERROR';
|
||||
case E_COMPILE_WARNING: // 128 //
|
||||
case E_COMPILE_WARNING: // 128 //
|
||||
return 'E_COMPILE_WARNING';
|
||||
case E_USER_ERROR: // 256 //
|
||||
case E_USER_ERROR: // 256 //
|
||||
return 'E_USER_ERROR';
|
||||
case E_USER_WARNING: // 512 //
|
||||
case E_USER_WARNING: // 512 //
|
||||
return 'E_USER_WARNING';
|
||||
case E_USER_NOTICE: // 1024 //
|
||||
case E_USER_NOTICE: // 1024 //
|
||||
return 'E_USER_NOTICE';
|
||||
case E_STRICT: // 2048 //
|
||||
case E_STRICT: // 2048 //
|
||||
return 'E_STRICT';
|
||||
case E_RECOVERABLE_ERROR: // 4096 //
|
||||
case E_RECOVERABLE_ERROR: // 4096 //
|
||||
return 'E_RECOVERABLE_ERROR';
|
||||
case E_DEPRECATED: // 8192 //
|
||||
case E_DEPRECATED: // 8192 //
|
||||
return 'E_DEPRECATED';
|
||||
case E_USER_DEPRECATED: // 16384 //
|
||||
case E_USER_DEPRECATED: // 16384 //
|
||||
return 'E_USER_DEPRECATED';
|
||||
}
|
||||
return "{$type}";
|
||||
@@ -134,7 +134,7 @@ function logErrorByCustomHandler(int $errno, string $errstr, string $errfile, in
|
||||
getExceptionTraceAsString($e)
|
||||
);
|
||||
}
|
||||
set_error_handler("\sammo\logErrorByCustomHandler");
|
||||
set_error_handler("\\sammo\\logErrorByCustomHandler");
|
||||
|
||||
|
||||
function logExceptionByCustomHandler(\Throwable $e)
|
||||
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Container\Container;
|
||||
class RootDB
|
||||
{
|
||||
private static $uDB = null;
|
||||
private static ?Capsule $uIlluminate = null;
|
||||
|
||||
private static $host = '_tK_host_';
|
||||
private static $user = '_tK_user_';
|
||||
|
||||
@@ -342,6 +342,7 @@ switch ($btn) {
|
||||
}
|
||||
break;
|
||||
case "랜덤턴":
|
||||
$turnterm = $gameStor->turnterm;
|
||||
foreach ($genlist as $generalID) {
|
||||
$turntime = getRandTurn($turnterm);
|
||||
$db->update('general', [
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<div class="buttonPlate bg1">
|
||||
<div>
|
||||
<a href="a_status.php" target="_blank"><button class="toolbarButton">세력도</button></a>
|
||||
<a href="a_kingdomList.php" target="_blank"><button class="toolbarButton">세력일람</button></a>
|
||||
<a href="a_genList.php" target="_blank"><button class="toolbarButton">장수일람</button></a>
|
||||
<a href="a_bestGeneral.php" target="_blank"><button class="toolbarButton">명장일람</button></a>
|
||||
<a href="a_history.php" target="_blank"><button class="toolbarButton">연감</button></a>
|
||||
<a href="a_hallOfFame.php" target="_blank"><button class="toolbarButton">명예의전당</button></a>
|
||||
<a href="a_emperior.php" target="_blank"><button class="toolbarButton">왕조일람</button></a>
|
||||
<a href="a_traffic.php" target="_blank"><button class="toolbarButton">접속량정보</button></a>
|
||||
<a href="a_status.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">세력도</button></a>
|
||||
<a href="a_kingdomList.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">세력일람</button></a>
|
||||
<a href="a_genList.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">장수일람</button></a>
|
||||
<a href="a_bestGeneral.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">명장일람</button></a>
|
||||
<a href="a_history.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">연감</button></a>
|
||||
<a href="a_hallOfFame.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">명예의전당</button></a>
|
||||
<a href="a_emperior.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">왕조일람</button></a>
|
||||
<a href="a_traffic.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">접속량정보</button></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/bbs/board" target="_blank"><button class="toolbarButton2">삼모게시판</button></a>
|
||||
<a href="/bbs/tip" target="_blank"><button class="toolbarButton">팁/강좌</button></a>
|
||||
<a href="/bbs/news" target="_blank"><button class="toolbarButton">삼국 일보</button></a>
|
||||
<a href="/bbs/history2" target="_blank"><button class="toolbarButton">개인 열전</button></a>
|
||||
<a href="/bbs/history3" target="_blank"><button class="toolbarButton">국가 열전</button></a>
|
||||
<a href="/bbs/patch" target="_blank"><button class="toolbarButton">패치 내역</button></a>
|
||||
<a href="../i_other/help.php" target="_blank"><button class="toolbarButton">튜토리얼</button></a>
|
||||
<a href="<?=$site?>" target="_blank"><button class="toolbarButton"><?=$call?></button></a>
|
||||
<a href="/bbs/board" target="_blank"><button type="button" class="toolbarButton2">삼모게시판</button></a>
|
||||
<a href="/bbs/tip" target="_blank"><button type="button" class="toolbarButton">팁/강좌</button></a>
|
||||
<a href="/bbs/news" target="_blank"><button type="button" class="toolbarButton">삼국 일보</button></a>
|
||||
<a href="/bbs/history2" target="_blank"><button type="button" class="toolbarButton">개인 열전</button></a>
|
||||
<a href="/bbs/history3" target="_blank"><button type="button" class="toolbarButton">국가 열전</button></a>
|
||||
<a href="/bbs/patch" target="_blank"><button type="button" class="toolbarButton">패치 내역</button></a>
|
||||
<a href="battle_simulator.php" target="_blank"><button type="button" class="toolbarButton">전투 시뮬레이터</button></a>
|
||||
<a href="<?=$site?>" target="_blank" class="open-window"><button type="button" class="toolbarButton"><?=$call?></button></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1132,7 +1132,7 @@ function fight($tnmt_type, $tnmt, $phs, $group, $g1, $g2, $type) {
|
||||
$gen1resKey = 'd';
|
||||
$gen2resKey = 'd';
|
||||
break;
|
||||
default: new MustNotBeReachedException();
|
||||
default: throw new MustNotBeReachedException();
|
||||
}
|
||||
|
||||
$db->update('rank_data', [
|
||||
|
||||
+69
-57
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
include "lib.php";
|
||||
@@ -21,18 +22,18 @@ $availableTypes = [
|
||||
'nationPolicy',
|
||||
];
|
||||
|
||||
if(!in_array($type, $availableTypes)){
|
||||
if (!in_array($type, $availableTypes)) {
|
||||
Json::die([
|
||||
'result'=>false,
|
||||
'reason'=>'올바른 타입이 아닙니다.',
|
||||
'result' => false,
|
||||
'reason' => '올바른 타입이 아닙니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
$data = Json::decode($data);
|
||||
if(!$data || !is_array($data)){
|
||||
if (!$data || !is_array($data)) {
|
||||
Json::die([
|
||||
'result'=>false,
|
||||
'reason'=>'올바른 입력이 아닙니다.',
|
||||
'result' => false,
|
||||
'reason' => '올바른 입력이 아닙니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -44,94 +45,95 @@ $me = $db->queryFirstRow('SELECT no, name, npc, nation, city, officer_level, con
|
||||
$nationID = $me['nation'];
|
||||
if (!$nationID) {
|
||||
Json::die([
|
||||
'result'=>false,
|
||||
'reason'=>'국가에 소속되어있지 않습니다.',
|
||||
'result' => false,
|
||||
'reason' => '국가에 소속되어있지 않습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
$permission = checkSecretPermission($me);
|
||||
if ($permission < 3) {
|
||||
Json::die([
|
||||
'result'=>false,
|
||||
'reason'=>'권한이 부족합니다. 군주, 외교권자, 조언자가 아닙니다.'
|
||||
'result' => false,
|
||||
'reason' => '권한이 부족합니다. 군주, 외교권자, 조언자가 아닙니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
function applyNationPolicy($policy, $nationID, $generalName):?string{
|
||||
function applyNationPolicy($policy, $nationID, $generalName): ?string
|
||||
{
|
||||
$db = DB::db();
|
||||
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
|
||||
|
||||
$defaultPolicy = AutorunNationPolicy::$defaultPolicy;
|
||||
$troopCache = null;
|
||||
foreach($db->queryFirstColumn('SELECT troop_leader FROM troop WHERE nation=%i',$nationID) as $troopID){
|
||||
foreach ($db->queryFirstColumn('SELECT troop_leader FROM troop WHERE nation=%i', $nationID) as $troopID) {
|
||||
$troopCache[$troopID] = 'Neutral';
|
||||
}
|
||||
$cityList = CityConst::all();
|
||||
$nationPolicyRoot = $nationStor->npc_nation_policy;
|
||||
$nationPolicy = $nationPolicyRoot['values']??[];
|
||||
foreach($policy as $key=>$val){
|
||||
$nationPolicy = $nationPolicyRoot['values'] ?? [];
|
||||
foreach ($policy as $key => $val) {
|
||||
|
||||
if($key === 'CombatForce'){
|
||||
if(!is_array($val)){
|
||||
return "{$key}는 올바른 정책값이 아닙니다.";
|
||||
if ($key === 'CombatForce') {
|
||||
if (!is_array($val)) {
|
||||
return "{$key}는 올바른 정책값이 아닙니다.";
|
||||
}
|
||||
foreach($val as $troopID=>$troopTarget){
|
||||
if(!key_exists($troopID, $troopCache)){
|
||||
foreach ($val as $troopID => $troopTarget) {
|
||||
if (!key_exists($troopID, $troopCache)) {
|
||||
return "{$troopID}는 국가의 부대가 아닙니다.";
|
||||
}
|
||||
if($troopCache[$troopID] != 'Neutral'){
|
||||
if ($troopCache[$troopID] != 'Neutral') {
|
||||
return "부대({$troopID}는 하나의 역할만 지정할 수 있습니다.";
|
||||
}
|
||||
if(!is_array($troopTarget) || count($troopTarget)!=2){
|
||||
if (!is_array($troopTarget) || count($troopTarget) != 2) {
|
||||
return "{$troopID}의 입력양식이 올바르지 않습니다.";
|
||||
}
|
||||
[$fromCity, $toCity] = $troopCache;
|
||||
if(!key_exists($fromCity, $cityList) || !key_exists($toCity, $cityList)){
|
||||
if (!key_exists($fromCity, $cityList) || !key_exists($toCity, $cityList)) {
|
||||
return "{$troopID}의 도시 {$fromCity}, {$toCity}가 올바른 도시 번호가 아닙니다.";
|
||||
}
|
||||
$troopCache[$troopID]=$key;
|
||||
$troopCache[$troopID] = $key;
|
||||
}
|
||||
$nationPolicy[$key]=$val;
|
||||
$nationPolicy[$key] = $val;
|
||||
continue;
|
||||
}
|
||||
if(in_array($key, ['SupportForce', 'DevelopForce'])){
|
||||
if(!is_array($val)){
|
||||
return "{$key}는 올바른 정책값이 아닙니다.";
|
||||
if (in_array($key, ['SupportForce', 'DevelopForce'])) {
|
||||
if (!is_array($val)) {
|
||||
return "{$key}는 올바른 정책값이 아닙니다.";
|
||||
}
|
||||
foreach($val as $troopID){
|
||||
if(!key_exists($troopID, $troopCache)){
|
||||
foreach ($val as $troopID) {
|
||||
if (!key_exists($troopID, $troopCache)) {
|
||||
return "{$troopID}는 국가의 부대가 아닙니다.";
|
||||
}
|
||||
if($troopCache[$troopID] != 'Neutral'){
|
||||
if ($troopCache[$troopID] != 'Neutral') {
|
||||
return "부대({$troopID}는 하나의 역할만 지정할 수 있습니다.";
|
||||
}
|
||||
$troopCache[$troopID]=$key;
|
||||
$troopCache[$troopID] = $key;
|
||||
}
|
||||
|
||||
$nationPolicy[$key]=array_values($val);
|
||||
$nationPolicy[$key] = array_values($val);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(!key_exists($key, $defaultPolicy)){
|
||||
if (!key_exists($key, $defaultPolicy)) {
|
||||
return "{$key}는 올바른 정책값이 아닙니다.";
|
||||
}
|
||||
$defaultValue = $defaultPolicy[$key];
|
||||
if(is_numeric($defaultValue) != is_numeric($val)){
|
||||
if (is_numeric($defaultValue) != is_numeric($val)) {
|
||||
return "{$key}는 올바른 값이 아닙니다.";
|
||||
}
|
||||
if(is_integer($defaultValue) != is_integer($val)){
|
||||
if (is_integer($defaultValue) != is_integer($val)) {
|
||||
return "{$key}는 올바른 값이 아닙니다.";
|
||||
}
|
||||
if(is_array($defaultValue) != is_array($val)){
|
||||
if (is_array($defaultValue) != is_array($val)) {
|
||||
return "{$key}는 올바른 값이 아닙니다.";
|
||||
}
|
||||
if(is_integer($val) && $val < 0){
|
||||
if (is_integer($val) && $val < 0) {
|
||||
$val = 0;
|
||||
}
|
||||
$nationPolicy[$key] = $val;
|
||||
}
|
||||
|
||||
|
||||
$nationPolicyRoot['values'] = $nationPolicy;
|
||||
$nationPolicyRoot['valueSetter'] = $generalName;
|
||||
$nationPolicyRoot['valueSetTime'] = TimeUtil::now();
|
||||
@@ -139,14 +141,15 @@ function applyNationPolicy($policy, $nationID, $generalName):?string{
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyNationPriority($priority, $nationID, $generalName):?string{
|
||||
function applyNationPriority($priority, $nationID, $generalName): ?string
|
||||
{
|
||||
$db = DB::db();
|
||||
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
|
||||
$nationPolicyRoot = $nationStor->npc_nation_policy;
|
||||
|
||||
$defaultPriority = AutorunNationPolicy::$defaultPriority;
|
||||
foreach($priority as $item){
|
||||
if(!in_array($item, $defaultPriority)){
|
||||
foreach ($priority as $item) {
|
||||
if (!in_array($item, $defaultPriority)) {
|
||||
return "{$item}은 올바른 명령이 아닙니다.";
|
||||
}
|
||||
}
|
||||
@@ -157,17 +160,29 @@ function applyNationPriority($priority, $nationID, $generalName):?string{
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyGeneralPriority($priority, $nationID, $generalName):?string{
|
||||
function applyGeneralPriority($priority, $nationID, $generalName): ?string
|
||||
{
|
||||
$db = DB::db();
|
||||
$nationStor = KVStorage::getStorage($db, $nationID, 'nation_env');
|
||||
$generalPolicyRoot = $nationStor->npc_general_policy;
|
||||
|
||||
$defaultPriority = AutorunGeneralPolicy::$default_priority;
|
||||
foreach($priority as $item){
|
||||
if(!in_array($item, $defaultPriority)){
|
||||
$mustHaveActions = ['출병' => 1];
|
||||
foreach ($priority as $item) {
|
||||
if (key_exists($item, $mustHaveActions)) {
|
||||
$mustHaveActions[$item] = 0;
|
||||
}
|
||||
if (!in_array($item, $defaultPriority)) {
|
||||
return "{$item}은 올바른 명령이 아닙니다.";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($mustHaveActions as $actionKey => $unset) {
|
||||
if ($unset) {
|
||||
return "{$actionKey}은 항상 사용해야 합니다.";
|
||||
}
|
||||
}
|
||||
|
||||
$generalPolicyRoot['priority'] = $priority;
|
||||
$generalPolicyRoot['prioritySetter'] = $generalName;
|
||||
$generalPolicyRoot['prioritySetTime'] = TimeUtil::now();
|
||||
@@ -175,26 +190,23 @@ function applyGeneralPriority($priority, $nationID, $generalName):?string{
|
||||
return null;
|
||||
}
|
||||
|
||||
if($type == 'nationPolicy'){
|
||||
if ($type == 'nationPolicy') {
|
||||
$result = applyNationPolicy($data, $nationID, $me['name']);
|
||||
}
|
||||
else if($type == 'generalPriority'){
|
||||
} else if ($type == 'generalPriority') {
|
||||
$result = applyGeneralPriority($data, $nationID, $me['name']);
|
||||
}
|
||||
else if($type == 'nationPriority'){
|
||||
} else if ($type == 'nationPriority') {
|
||||
$result = applyNationPriority($data, $nationID, $me['name']);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
throw new MustNotBeReachedException();
|
||||
}
|
||||
|
||||
if($result!==null){
|
||||
if ($result !== null) {
|
||||
Json::die([
|
||||
'result'=>false,
|
||||
'reason'=>$result,
|
||||
'result' => false,
|
||||
'reason' => $result,
|
||||
]);
|
||||
}
|
||||
Json::die([
|
||||
'result'=>true,
|
||||
'reason'=>'success',
|
||||
]);
|
||||
'result' => true,
|
||||
'reason' => 'success',
|
||||
]);
|
||||
|
||||
@@ -17,6 +17,7 @@ class BuySpecificUnique extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$availableItems = [];
|
||||
foreach (GameConst::$allItems as $items) {
|
||||
foreach ($items as $itemKey => $amount) {
|
||||
if ($amount == 0) {
|
||||
|
||||
@@ -62,10 +62,10 @@ class ResetTurnTime extends \sammo\BaseAPI
|
||||
|
||||
$userLogger = new UserLogger($userID);
|
||||
if($afterTurn >= 0){
|
||||
$userLogger->push(sprintf("{$reqPoint} 포인트로 턴 시간을 바꾼 결과 %02d:%02d 뒤로 밀림", intdiv($afterTurn, 60), $afterTurn%60), "inheritPoint");
|
||||
$userLogger->push(sprintf("{$reqPoint} 포인트로 턴 시간을 바꾼 결과 %02d:%02d 뒤로 밀림", intdiv(Util::toInt($afterTurn), 60), $afterTurn%60), "inheritPoint");
|
||||
}
|
||||
else{
|
||||
$userLogger->push(sprintf("{$reqPoint} 포인트로 턴 시간을 바꾼 결과 %02d:%02d 앞으로 당김", intdiv(-$afterTurn, 60), (-$afterTurn)%60), "inheritPoint");
|
||||
$userLogger->push(sprintf("{$reqPoint} 포인트로 턴 시간을 바꾼 결과 %02d:%02d 앞으로 당김", intdiv(Util::toInt(-$afterTurn), 60), (-$afterTurn)%60), "inheritPoint");
|
||||
}
|
||||
$userLogger->flush();
|
||||
|
||||
|
||||
@@ -11,25 +11,29 @@ class event_전투특기_무쌍 extends \sammo\BaseItem{
|
||||
protected $id = 61;
|
||||
protected $rawName = '비급';
|
||||
protected $name = '비급(무쌍)';
|
||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대40%)<br>승리 수만큼 피해 0.05%씩 감소(최대30%)';
|
||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대50%)<br>승리 수만큼 피해 0.05%씩 감소(최대20%)';
|
||||
protected $cost = 100;
|
||||
protected $buyable = true;
|
||||
protected $consumable = false;
|
||||
protected $reqSecu = 3000;
|
||||
|
||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
||||
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||
return $value += 0.1;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||
$attackMultiplier = 1.1;
|
||||
$defenceMultiplier = 0.95;
|
||||
$generalWarSpecial = $unit->getGeneral()->getSpecialWar();
|
||||
if($generalWarSpecial !== null && $generalWarSpecial->getName() == '무쌍'){
|
||||
return [1, 1];
|
||||
}
|
||||
$attackMultiplier = 1;
|
||||
$defenceMultiplier = 1;
|
||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.4);
|
||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.3);
|
||||
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||
return [$attackMultiplier, $defenceMultiplier];
|
||||
}
|
||||
}
|
||||
@@ -19,18 +19,22 @@ class che_event_무쌍 extends \sammo\BaseSpecial{
|
||||
];
|
||||
|
||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
||||
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||
return $value += 0.1;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||
$attackMultiplier = 1.1;
|
||||
$generalWarSpecial = $unit->getGeneral()->getSpecialWar();
|
||||
if($generalWarSpecial !== null && $generalWarSpecial->getName() == '무쌍'){
|
||||
return [1, 1];
|
||||
}
|
||||
$attackMultiplier = 1;
|
||||
$defenceMultiplier = 1;
|
||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.4);
|
||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.3);
|
||||
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||
return [$attackMultiplier, $defenceMultiplier];
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class che_무쌍 extends \sammo\BaseSpecial{
|
||||
|
||||
protected $id = 61;
|
||||
protected $name = '무쌍';
|
||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대40%)<br>승리 수만큼 피해 0.05%씩 감소(최대30%)';
|
||||
protected $info = '[전투] 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)';
|
||||
|
||||
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
|
||||
static $selectWeight = 1;
|
||||
@@ -19,18 +19,18 @@ class che_무쌍 extends \sammo\BaseSpecial{
|
||||
];
|
||||
|
||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
||||
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||
return $value += 0.1;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||
$attackMultiplier = 1.1;
|
||||
$defenceMultiplier = 0.95;
|
||||
$attackMultiplier = 1;
|
||||
$defenceMultiplier = 1;
|
||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.4);
|
||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.3);
|
||||
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||
return [$attackMultiplier, $defenceMultiplier];
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ namespace sammo;
|
||||
|
||||
class CityHelper{
|
||||
//Just Helper
|
||||
private static $list = null;
|
||||
private static $listInv = null;
|
||||
private static $listByNation = null;
|
||||
private static ?array $list = null;
|
||||
private static ?array $listInv = null;
|
||||
private static ?array $listByNation = null;
|
||||
|
||||
private function __construct(){
|
||||
|
||||
@@ -25,7 +25,7 @@ class CityHelper{
|
||||
'@phan-var array<int,mixed>|null $list';
|
||||
'@phan-var array<string,mixed>|null $listInv';
|
||||
'@phan-var array<int,mixed>|null $listByNation';
|
||||
|
||||
|
||||
foreach (DB::db()->query('SELECT `city` as `id`, `name`, `level`, `nation` from city') as $city) {
|
||||
$id = $city['id'];
|
||||
$name = $city['name'];
|
||||
@@ -83,5 +83,5 @@ class CityHelper{
|
||||
return $city;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -182,13 +182,13 @@ class che_랜덤임관 extends Command\GeneralCommand
|
||||
$generalsCnt = [];
|
||||
if ($notIn) {
|
||||
$rawGeneralsCnt = $db->query(
|
||||
'SELECT general.nation as nation, nation.gennum, nation.name, npc, count(*) as cnt FROM general JOIN nation ON general.nation = nation.nation WHERE npc != 9 AND nation.gennum < %i AND nation.scout=0 AND nation.nation NOT IN %li GROUP BY general.nation, general.npc',
|
||||
'SELECT general.nation as nation, nation.gennum, nation.name, npc, count(*) as cnt FROM general JOIN nation ON general.nation = nation.nation WHERE (npc < 4 OR npc = 6) AND nation.gennum < %i AND nation.scout=0 AND nation.nation NOT IN %li GROUP BY general.nation, general.npc',
|
||||
$genLimit,
|
||||
$notIn
|
||||
);
|
||||
} else {
|
||||
$rawGeneralsCnt = $db->query(
|
||||
'SELECT general.nation as nation, nation.gennum, nation.name, npc, count(*) as cnt FROM general JOIN nation ON general.nation = nation.nation WHERE npc != 9 AND nation.gennum < %i AND nation.scout=0 GROUP BY general.nation, general.npc',
|
||||
'SELECT general.nation as nation, nation.gennum, nation.name, npc, count(*) as cnt FROM general JOIN nation ON general.nation = nation.nation WHERE (npc < 4 OR npc = 6) AND nation.gennum < %i AND nation.scout=0 GROUP BY general.nation, general.npc',
|
||||
$genLimit
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace sammo\Command\General;
|
||||
|
||||
use \sammo\{
|
||||
DB, Util, JosaUtil,
|
||||
General,
|
||||
General,
|
||||
ActionLogger,
|
||||
GameConst, GameUnitConst,
|
||||
Command
|
||||
@@ -31,18 +31,20 @@ class che_탈취 extends che_화계{
|
||||
$commandName = $this->getName();
|
||||
$db = DB::db();
|
||||
|
||||
// 탈취 최대 800 * 8 * sqrt(1 + (year - startyear) / 5) / 2
|
||||
$yearCoef = sqrt(1 + ($this->env['year'] - $this->env['startyear']) / 5) / 2;
|
||||
$gold = Util::randRangeInt(GameConst::$sabotageDamageMin, GameConst::$sabotageDamageMax) * $destCity['level'] * $yearCoef;
|
||||
$rice = Util::randRangeInt(GameConst::$sabotageDamageMin, GameConst::$sabotageDamageMax) * $destCity['level'] * $yearCoef;
|
||||
// 탈취 최대 800 * 8 * sqrt(1 + (year - startyear) / 4) / 2
|
||||
$yearCoef = sqrt(1 + ($this->env['year'] - $this->env['startyear']) / 4) / 2;
|
||||
$commRatio = $destCity['comm'] / $destCity['comm_max'];
|
||||
$agriRatio = $destCity['agri'] / $destCity['agri_max'];
|
||||
$gold = Util::randRangeInt(GameConst::$sabotageDamageMin, GameConst::$sabotageDamageMax) * $destCity['level'] * $yearCoef * (0.25 + $commRatio / 4);
|
||||
$rice = Util::randRangeInt(GameConst::$sabotageDamageMin, GameConst::$sabotageDamageMax) * $destCity['level'] * $yearCoef * (0.25 + $agriRatio / 4);
|
||||
|
||||
if($destCity['supply']){
|
||||
[$destNationGold, $destNationRice] = $db->queryFirstList('SELECT gold,rice FROM nation WHERE nation=%i', $destNationID);
|
||||
|
||||
|
||||
$destNationGold -= $gold;
|
||||
$destNationRice -= $rice;
|
||||
|
||||
if($destNationGold < GameConst::$minNationalGold) {
|
||||
|
||||
if($destNationGold < GameConst::$minNationalGold) {
|
||||
$gold += $destNationGold - GameConst::$minNationalGold;
|
||||
$destNationGold = GameConst::$minNationalGold;
|
||||
}
|
||||
@@ -50,7 +52,7 @@ class che_탈취 extends che_화계{
|
||||
$rice += $destNationRice - GameConst::$minNationalRice;
|
||||
$destNationRice = GameConst::$minNationalRice;
|
||||
}
|
||||
|
||||
|
||||
$db->update('nation', [
|
||||
'gold'=>$destNationGold,
|
||||
'rice'=>$destNationRice
|
||||
@@ -96,5 +98,5 @@ class che_탈취 extends che_화계{
|
||||
|
||||
$logger->pushGeneralActionLog("금<C>{$goldText}</> 쌀<C>{$riceText}</>을 획득했습니다.", ActionLogger::PLAIN);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -218,7 +218,7 @@ class che_물자원조 extends Command\NationCommand{
|
||||
}
|
||||
|
||||
$josaRoSrc = JosaUtil::pick($nationName, '로');
|
||||
$destNationLogger = new ActionLogger(0, $destChiefID, $year, $month);
|
||||
$destNationLogger = new ActionLogger(0, $destNationID, $year, $month);
|
||||
$destNationLogger->pushNationalHistoryLog("<D><b>{$nationName}</b></>{$josaRoSrc}부터 금<C>{$goldAmountText}</> 쌀<C>{$riceAmountText}</>을 지원 받음");
|
||||
|
||||
$destNationStor = KVStorage::getStorage(DB::db(), $destNationID, 'nation_env');
|
||||
|
||||
@@ -33,6 +33,9 @@ class che_피장파장 extends Command\NationCommand{
|
||||
if(!key_exists('destNationID', $this->arg)){
|
||||
return false;
|
||||
}
|
||||
if(!key_exists('commandType', $this->arg)){
|
||||
return false;
|
||||
}
|
||||
$destNationID = $this->arg['destNationID'];
|
||||
$commandType = $this->arg['commandType'];
|
||||
|
||||
|
||||
@@ -42,6 +42,22 @@ class DummyGeneral extends General{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getInheritancePoint(string $key, &$aux = null, bool $forceCalc = false){
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setInheritancePoint(string $key, $value, $aux = null){
|
||||
return;
|
||||
}
|
||||
|
||||
public function increaseInheritancePoint(string $key, $value, $aux = null){
|
||||
return;
|
||||
}
|
||||
|
||||
public function mergeTotalInheritancePoint(){
|
||||
return;
|
||||
}
|
||||
|
||||
function applyDB($db):bool{
|
||||
if($this->logger){
|
||||
$this->initLogger(1, 1);
|
||||
|
||||
@@ -9,9 +9,11 @@ use function sammo\pickGeneralFromPool;
|
||||
//기존 event_3.php
|
||||
class CreateManyNPC extends \sammo\Event\Action{
|
||||
protected $npcCount;
|
||||
protected $fillCnt;
|
||||
protected $avgGen;
|
||||
public function __construct($npcCount = 10){
|
||||
public function __construct($npcCount = 10, $fillCnt = 0){
|
||||
$this->npcCount = $npcCount;
|
||||
$this->fillCnt = $fillCnt;
|
||||
}
|
||||
|
||||
protected function generateNPC($env, int $cnt){
|
||||
@@ -41,13 +43,23 @@ class CreateManyNPC extends \sammo\Event\Action{
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function run(array $env){
|
||||
if($this->npcCount <= 0){
|
||||
return [__CLASS__, []];
|
||||
if($this->npcCount <= 0 && $this->fillCnt <= 0){
|
||||
return [__CLASS__, []];
|
||||
}
|
||||
$result = $this->generateNPC($env, $this->npcCount);
|
||||
|
||||
|
||||
$moreGenCnt = 0;
|
||||
if($this->fillCnt){
|
||||
$db = DB::db();
|
||||
$nations = $db->queryFirstColumn('SELECT nation FROM general WHERE npc < 2 AND officer_level = 12');
|
||||
$regGens = $db->queryFirstField('SELECT count(*) FROM general WHERE nation IN %li AND npc < 4', $nations);
|
||||
$moreGenCnt = count($nations) * $this->fillCnt - $regGens;
|
||||
}
|
||||
|
||||
$result = $this->generateNPC($env, $this->npcCount + $moreGenCnt);
|
||||
|
||||
$logger = new \sammo\ActionLogger(0, 0, $env['year'], $env['month']);
|
||||
$genCnt = count($result);
|
||||
@@ -62,6 +74,6 @@ class CreateManyNPC extends \sammo\Event\Action{
|
||||
$logger->pushGlobalHistoryLog("장수 <C>{$genCnt}</>명이 <S>등장</>했습니다.", \sammo\ActionLogger::NOTICE_YEAR_MONTH);
|
||||
$logger->flush();
|
||||
|
||||
return [__CLASS__, $result];
|
||||
return [__CLASS__, $result];
|
||||
}
|
||||
}
|
||||
+771
-789
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ class che_도시치료 extends BaseGeneralTrigger{
|
||||
/** @var \sammo\General $general */
|
||||
$general = $this->object;
|
||||
$logger = $general->getLogger();
|
||||
|
||||
|
||||
if($general->getVar('injury') > 0){
|
||||
$general->updateVar('injury', 0);
|
||||
$general->activateSkill('pre.부상경감', 'pre.치료');
|
||||
@@ -25,8 +25,8 @@ class che_도시치료 extends BaseGeneralTrigger{
|
||||
$db = DB::db();
|
||||
|
||||
$patients = $db->queryAllLists(
|
||||
'SELECT no,name,nation FROM general WHERE city=%i AND injury > 10 AND no != %i',
|
||||
$general->getCityID(),
|
||||
'SELECT no,name,nation FROM general WHERE city=%i AND injury > 10 AND no != %i',
|
||||
$general->getCityID(),
|
||||
$general->getID()
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ class che_도시치료 extends BaseGeneralTrigger{
|
||||
return $env;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(count($cureList) == 1){
|
||||
$josaUl = JosaUtil::pick($curedPatientName, "을");
|
||||
$logger->pushGeneralActionLog("<C>의술</>을 펼쳐 도시의 장수 <Y>{$curedPatientName}</>{$josaUl} 치료합니다!", ActionLogger::PLAIN);
|
||||
|
||||
@@ -459,7 +459,7 @@ class GeneralBuilder{
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->dex1 && in_array('dex_t', $avgGen)){
|
||||
if(!$this->dex1 && key_exists('dex_t', $avgGen)){
|
||||
$dexTotal = $avgGen['dex_t'];
|
||||
if ($pickType == '무') {
|
||||
$dexVal = Util::choiceRandom([
|
||||
|
||||
@@ -86,7 +86,7 @@ class SpecialityHelper{
|
||||
if($leadership > GameConst::$chiefStatMin){
|
||||
$myCond |= self::STAT_LEADERSHIP;
|
||||
}
|
||||
|
||||
|
||||
if($strength >= $intel * 0.95 && $strength > GameConst::$chiefStatMin){
|
||||
$myCond |= self::STAT_STRENGTH;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class SpecialityHelper{
|
||||
}
|
||||
if($strength < GameConst::$chiefStatMin){
|
||||
$myCond |= self::STAT_NOT_STRENGTH;
|
||||
}
|
||||
}
|
||||
if($intel < GameConst::$chiefStatMin){
|
||||
$myCond |= self::STAT_NOT_INTEL;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class SpecialityHelper{
|
||||
$myCond |= self::STAT_INTEL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $myCond;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class SpecialityHelper{
|
||||
$specialObj = buildGeneralSpecialWarClass(GameConst::$defaultSpecialWar);
|
||||
$result[GameConst::$defaultSpecialWar] = $specialObj;
|
||||
}
|
||||
|
||||
|
||||
foreach(GameConst::$availableSpecialWar as $specialID){
|
||||
$specialObj = buildGeneralSpecialWarClass($specialID);
|
||||
$result[$specialID] = $specialObj;
|
||||
@@ -220,7 +220,7 @@ class SpecialityHelper{
|
||||
if(in_array($specialID, $prevSpecials)){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if($weightType === static::WEIGHT_PERCENT){
|
||||
$pAbs[$specialID] = $weight;
|
||||
@@ -229,7 +229,7 @@ class SpecialityHelper{
|
||||
$pRel[$specialID] = $weight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($pAbs){
|
||||
if($pRel){
|
||||
$pAbs[0] = max(0, 100 - array_sum($pAbs));
|
||||
@@ -280,7 +280,7 @@ class SpecialityHelper{
|
||||
if(in_array($specialID, $prevSpecials)){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if($cond & self::REQ_DEXTERITY){
|
||||
$reqDex[$specialID] = $weight;
|
||||
@@ -296,7 +296,7 @@ class SpecialityHelper{
|
||||
if($reqDex){
|
||||
return Util::choiceRandomUsingWeight($reqDex);
|
||||
}
|
||||
|
||||
|
||||
if($pAbs){
|
||||
if($pRel){
|
||||
$pAbs[0] = max(0, 100 - array_sum($pAbs));
|
||||
|
||||
@@ -56,6 +56,9 @@ class WarUnitGeneral extends WarUnit
|
||||
$semiTurn = $general->getTurnTime();
|
||||
} else if ($oppose !== null) {
|
||||
$semiTurn = $oppose->getGeneral()->getTurnTime();
|
||||
} else {
|
||||
LogText("WarUnitGeneral::setOppose", "defender인데 oppose가 null {$general->getID()}, {$general->getTurnTime()}");
|
||||
$semiTurn = $general->getTurnTime();
|
||||
}
|
||||
$phase = $this->getRealPhase();
|
||||
$semiTurn = substr($semiTurn, 0, strlen($semiTurn) - 2);
|
||||
@@ -205,7 +208,7 @@ class WarUnitGeneral extends WarUnit
|
||||
if ($this->isAttacker) {
|
||||
if ($officerLevel == 12) {
|
||||
$warPower *= 1.10;
|
||||
} else if ($officerLevel == 11 | $officerLevel == 10 || $officerLevel == 8 || $officerLevel == 6) {
|
||||
} else if ($officerLevel == 11 || $officerLevel == 10 || $officerLevel == 8 || $officerLevel == 6) {
|
||||
$warPower *= 1.05;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ class che_전투치료발동 extends BaseWarUnitTrigger{
|
||||
}
|
||||
$selfEnv['치료발동'] = true;
|
||||
|
||||
$oppose->getLogger()->pushGeneralBattleDetailLog("상대가 <C>치료</>했다!", ActionLogger::PLAIN);
|
||||
$oppose->getLogger()->pushGeneralBattleDetailLog("상대가 <R>치료</>했다!", ActionLogger::PLAIN);
|
||||
$self->getLogger()->pushGeneralBattleDetailLog("<C>치료</>했다!", ActionLogger::PLAIN);
|
||||
|
||||
$oppose->multiplyWarPowerMultiply(0.5);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"events":[
|
||||
[
|
||||
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
|
||||
["CreateManyNPC", 30],
|
||||
["CreateManyNPC", 10, 10],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"events":[
|
||||
[
|
||||
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
|
||||
["CreateManyNPC", 30],
|
||||
["CreateManyNPC", 10, 10],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"events":[
|
||||
[
|
||||
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
|
||||
["CreateManyNPC", 30],
|
||||
["CreateManyNPC", 10, 10],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"iconPath": "쿠키런킹덤",
|
||||
"fiction": 1,
|
||||
"nation":[
|
||||
|
||||
|
||||
],
|
||||
"map":{
|
||||
"mapName":"miniche",
|
||||
@@ -100,7 +100,7 @@
|
||||
"events":[
|
||||
[
|
||||
["or", ["Date", "==", null, 12], ["Date", "==", null, 6]],
|
||||
["CreateManyNPC", 30],
|
||||
["CreateManyNPC", 10, 10],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
@@ -109,5 +109,5 @@
|
||||
["DeleteEvent"]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
}
|
||||
@@ -9,26 +9,26 @@
|
||||
"const": {
|
||||
"joinRuinedNPCProp":0,
|
||||
"availableSpecialWar":[
|
||||
"che_귀병", "che_신산", "che_환술", "che_집중", "che_신중", "che_반계",
|
||||
"che_귀병", "che_신산", "che_환술", "che_집중", "che_신중", "che_반계",
|
||||
"che_보병", "che_궁병", "che_기병", "che_공성",
|
||||
"che_돌격", "che_무쌍", "che_위압",
|
||||
"che_돌격", "che_무쌍", "che_위압",
|
||||
"che_저격", "che_필살", "che_징병", "che_의술", "che_격노", "che_척사"
|
||||
],
|
||||
"allItems":{
|
||||
"horse":{
|
||||
"che_명마_01_노기":0, "che_명마_02_조랑":0, "che_명마_03_노새":0,
|
||||
"che_명마_04_나귀":0, "che_명마_05_갈색마":0, "che_명마_06_흑색마":0,
|
||||
|
||||
"che_명마_07_백마":2, "che_명마_07_기주마":2,
|
||||
"che_명마_04_나귀":0, "che_명마_05_갈색마":0, "che_명마_06_흑색마":0,
|
||||
|
||||
"che_명마_07_백마":2, "che_명마_07_기주마":2,
|
||||
"che_명마_08_양주마":2, "che_명마_09_과하마":2,
|
||||
"che_명마_10_대완마":2, "che_명마_11_서량마":2,
|
||||
"che_명마_10_대완마":2, "che_명마_11_서량마":2,
|
||||
"che_명마_12_사륜거":2, "che_명마_13_절영":1, "che_명마_13_적로":1,
|
||||
"che_명마_14_적란마":1, "che_명마_14_조황비전":1, "che_명마_15_한혈마":1, "che_명마_15_적토마":1
|
||||
},
|
||||
"weapon":{
|
||||
"che_무기_01_단도":0, "che_무기_02_단궁":0, "che_무기_03_단극":0,
|
||||
"che_무기_04_목검":0, "che_무기_05_죽창":0, "che_무기_06_소부":0,
|
||||
|
||||
|
||||
"che_무기_07_동추":1, "che_무기_07_철편":1, "che_무기_07_철쇄":1, "che_무기_07_맥궁":1,
|
||||
"che_무기_08_유성추":1, "che_무기_08_철질여골":1, "che_무기_09_쌍철극":1, "che_무기_09_동호비궁":1,
|
||||
"che_무기_10_삼첨도":1, "che_무기_10_대부":1, "che_무기_11_고정도":1, "che_무기_11_이광궁":1,
|
||||
@@ -37,18 +37,18 @@
|
||||
},
|
||||
"book":{
|
||||
"che_서적_01_효경전":0, "che_서적_02_회남자":0, "che_서적_03_변도론":0,
|
||||
"che_서적_04_건상역주":0, "che_서적_05_여씨춘추":0, "che_서적_06_사민월령":0,
|
||||
|
||||
"che_서적_04_건상역주":0, "che_서적_05_여씨춘추":0, "che_서적_06_사민월령":0,
|
||||
|
||||
"che_서적_07_위료자":1, "che_서적_07_사마법":1, "che_서적_07_한서":1, "che_서적_07_논어":1,
|
||||
"che_서적_08_전론":1, "che_서적_08_사기":1, "che_서적_09_장자":1, "che_서적_09_역경":1,
|
||||
"che_서적_10_시경":1, "che_서적_10_구국론":1, "che_서적_11_상군서":1, "che_서적_11_춘추전":1,
|
||||
"che_서적_12_산해경":1, "che_서적_12_맹덕신서":1, "che_서적_13_관자":1, "che_서적_13_병법24편":1,
|
||||
"che_서적_10_시경":1, "che_서적_10_구국론":1, "che_서적_11_상군서":1, "che_서적_11_춘추전":1,
|
||||
"che_서적_12_산해경":1, "che_서적_12_맹덕신서":1, "che_서적_13_관자":1, "che_서적_13_병법24편":1,
|
||||
"che_서적_14_한비자":1, "che_서적_14_오자병법":1, "che_서적_15_노자":1, "che_서적_15_손자병법":1
|
||||
},
|
||||
"item":{
|
||||
"che_치료_환약":0, "che_저격_수극":0, "che_사기_탁주":0,
|
||||
"che_훈련_청주":0, "che_계략_이추":0, "che_계략_향낭":0,
|
||||
|
||||
|
||||
"event_전투특기_격노":0, "event_전투특기_공성":0, "event_전투특기_궁병":0,
|
||||
"event_전투특기_귀병":0, "event_전투특기_기병":0, "event_전투특기_돌격":0, "event_전투특기_무쌍":0,
|
||||
"event_전투특기_반계":0, "event_전투특기_보병":0, "event_전투특기_신산":0, "event_전투특기_신중":0,
|
||||
@@ -60,7 +60,7 @@
|
||||
"events":[
|
||||
[
|
||||
["Date", "==", null, 12],
|
||||
["CreateManyNPC", 30],
|
||||
["CreateManyNPC", 10, 10],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"events": [
|
||||
[
|
||||
["Date", "==", null, 12],
|
||||
["CreateManyNPC", 100],
|
||||
["CreateManyNPC", 100, 0],
|
||||
["DeleteEvent"]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="buttonPlate bg1">
|
||||
<div>
|
||||
<a href="a_status.php" target="_blank"><button type="button" class="toolbarButton">세력도</button></a>
|
||||
<a href="a_kingdomList.php" target="_blank"><button type="button" class="toolbarButton">세력일람</button></a>
|
||||
<a href="a_genList.php" target="_blank"><button type="button" class="toolbarButton">장수일람</button></a>
|
||||
<a href="a_bestGeneral.php" target="_blank"><button type="button" class="toolbarButton">명장일람</button></a>
|
||||
<a href="a_history.php" target="_blank"><button type="button" class="toolbarButton">연감</button></a>
|
||||
<a href="a_hallOfFame.php" target="_blank"><button type="button" class="toolbarButton">명예의전당</button></a>
|
||||
<a href="a_emperior.php" target="_blank"><button type="button" class="toolbarButton">왕조일람</button></a>
|
||||
<a href="a_traffic.php" target="_blank"><button type="button" class="toolbarButton">접속량정보</button></a>
|
||||
<a href="a_status.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">세력도</button></a>
|
||||
<a href="a_kingdomList.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">세력일람</button></a>
|
||||
<a href="a_genList.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">장수일람</button></a>
|
||||
<a href="a_bestGeneral.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">명장일람</button></a>
|
||||
<a href="a_history.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">연감</button></a>
|
||||
<a href="a_hallOfFame.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">명예의전당</button></a>
|
||||
<a href="a_emperior.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">왕조일람</button></a>
|
||||
<a href="a_traffic.php" target="_blank" class="open-window"><button type="button" class="toolbarButton">접속량정보</button></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/bbs/board" target="_blank"><button type="button" class="toolbarButton2">삼모게시판</button></a>
|
||||
@@ -17,6 +17,6 @@
|
||||
<a href="/bbs/history3" target="_blank"><button type="button" class="toolbarButton">국가 열전</button></a>
|
||||
<a href="/bbs/patch" target="_blank"><button type="button" class="toolbarButton">패치 내역</button></a>
|
||||
<a href="battle_simulator.php" target="_blank"><button type="button" class="toolbarButton">전투 시뮬레이터</button></a>
|
||||
<a href="<?=$site?>" target="_blank"><button type="button" class="toolbarButton"><?=$call?></button></a>
|
||||
<a href="<?=$site?>" target="_blank" class="open-window"><button type="button" class="toolbarButton"><?=$call?></button></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
<a href='b_dipcenter.php'><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>내 무 부</button></a>
|
||||
<a href='b_chiefcenter.php'><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>사 령 부</button></a>
|
||||
<a href='v_NPCControl.php'><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>NPC 정책</button></a>
|
||||
<a href='b_genList.php' target='_blank'><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>암 행 부</button></a>
|
||||
<a href='b_tournament.php' target='_blank'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>토 너 먼 트</button></a>
|
||||
<a href='b_genList.php' target='_blank' class="open-window"><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>암 행 부</button></a>
|
||||
<a href='b_tournament.php' target='_blank' class="open-window"><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>토 너 먼 트</button></a>
|
||||
<a href='b_myKingdomInfo.php'><button type="button" class='commandButton' <?=$meLevel>=1?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>세력 정보</button></a>
|
||||
<a href='b_myCityInfo.php'><button type="button" class='commandButton' <?=($meLevel>=1&&$nationLevel>=1)?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>세력 도시</button></a>
|
||||
<a href='b_myGenInfo.php'><button type="button" class='commandButton' <?=$meLevel>=1?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>세력 장수</button></a>
|
||||
<a href='b_diplomacy.php'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>중원 정보</button></a>
|
||||
<a href='b_currentCity.php'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>현재 도시</button></a>
|
||||
<a href='b_battleCenter.php' target='_blank'><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>감 찰 부</button></a>
|
||||
<a href='b_battleCenter.php' target='_blank' class="open-window"><button type="button" class='commandButton' <?=$showSecret?'':'disabled'?> style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>감 찰 부</button></a>
|
||||
<a href='v_inheritPoint.php'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>유산 관리</button></a>
|
||||
<a href='b_myPage.php'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>내 정보&설정</button></a>
|
||||
<a href='b_auction.php' target='_blank'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>거 래 장</button></a>
|
||||
<a href='b_betting.php' target='_blank'><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>베 팅 장</button></a>
|
||||
<a href='b_auction.php' target='_blank' class="open-window"><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>거 래 장</button></a>
|
||||
<a href='b_betting.php' target='_blank' class="open-window"><button type="button" class='commandButton' style='background-color:<?=$bgColor?>;color:<?=$fgColor?>;'>베 팅 장</button></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -192,6 +192,19 @@ $(function ($) {
|
||||
return false;
|
||||
})
|
||||
|
||||
$('.open-window').on('click', function(e){
|
||||
e.preventDefault();
|
||||
let target = $(e.target as HTMLAnchorElement);
|
||||
while(target.attr('href') === undefined){
|
||||
target = target.parent('a');
|
||||
if(target.length == 0){
|
||||
return;
|
||||
}
|
||||
}
|
||||
const href = target.attr('href');
|
||||
window.open(href);
|
||||
});
|
||||
|
||||
setInterval(myclock, 500);
|
||||
reloadCommandList();
|
||||
activateFlip();
|
||||
|
||||
@@ -71,7 +71,7 @@ class APIHelper
|
||||
[$lastModified, $etag] = $cacheResult;
|
||||
|
||||
if ($lastModified !== null) {
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s", TimeUtil::DateTimeToSeconds($lastModified, true)) . " GMT");
|
||||
header("Last-Modified: " . gmdate("D, d M Y H:i:s", Util::toInt(TimeUtil::DateTimeToSeconds($lastModified, true))) . " GMT");
|
||||
}
|
||||
if ($etag !== null) {
|
||||
header("Etag: $etag");
|
||||
@@ -97,7 +97,7 @@ class APIHelper
|
||||
} catch (\Exception $e) {
|
||||
Json::dieWithReason($e->getMessage());
|
||||
} catch (mixed $e) {
|
||||
Json::dieWithReason($e);
|
||||
Json::dieWithReason(strval($e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class Json
|
||||
return json_decode($value);
|
||||
}
|
||||
|
||||
/** @return never */
|
||||
public static function die($value, $flag = self::NO_CACHE)
|
||||
{
|
||||
//NOTE: REST 형식에 맞게, ok(), fail()로 쪼개는게 낫지 않을까 생각해봄.
|
||||
|
||||
+26
-26
@@ -156,7 +156,7 @@ class Util extends \utilphp\util
|
||||
else{
|
||||
$text = str_replace("_tK_{$key}_", $value, $text);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
file_put_contents($destFilePath, $text);
|
||||
|
||||
@@ -182,7 +182,7 @@ class Util extends \utilphp\util
|
||||
else{
|
||||
$head[] = "class $newClassName extends $srcClassName";
|
||||
}
|
||||
|
||||
|
||||
$head[] = '{';
|
||||
$head[] = '';
|
||||
$head = join("\n", $head);
|
||||
@@ -203,7 +203,7 @@ class Util extends \utilphp\util
|
||||
}
|
||||
$result = file_put_contents($destFilePath, $head.join("\n", $body).$tail, LOCK_EX);
|
||||
assert($result);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -281,12 +281,12 @@ class Util extends \utilphp\util
|
||||
public static function convertArrayToDict($arr, $keyName)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
|
||||
foreach ($arr as $obj) {
|
||||
$key = $obj[$keyName];
|
||||
$result[$key] = $obj;
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ class Util extends \utilphp\util
|
||||
public static function convertDictToArray($dict, bool $withKey=true)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
|
||||
foreach($dict as $key=>$value){
|
||||
if($withKey){
|
||||
$result[] = [$key, $value];
|
||||
@@ -318,7 +318,7 @@ class Util extends \utilphp\util
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ class Util extends \utilphp\util
|
||||
foreach($dict as $dictKey=>$value){
|
||||
$result[$dictKey] = $value[$key];
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ class Util extends \utilphp\util
|
||||
}
|
||||
$idx = $key + 1;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -368,20 +368,20 @@ class Util extends \utilphp\util
|
||||
if ($dict === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (is_array($dict) && empty($dict)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if ($depth <= 0) {
|
||||
return $dict;
|
||||
}
|
||||
|
||||
|
||||
foreach ($dict as $key=>$value) {
|
||||
if ($value === null) {
|
||||
unset($dict[$key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!Util::isDict($value)) {
|
||||
continue;
|
||||
}
|
||||
@@ -393,7 +393,7 @@ class Util extends \utilphp\util
|
||||
$dict[$key] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $dict;
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ class Util extends \utilphp\util
|
||||
|
||||
return (float)$matches[1] / 100;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* $min과 $max 사이의 값으로 교정
|
||||
@@ -552,7 +552,7 @@ class Util extends \utilphp\util
|
||||
|
||||
/**
|
||||
* 2중 배열에서 특정 키의 합
|
||||
*
|
||||
*
|
||||
* @param array $array 배열. 1차원 배열 또는 2차원 배열
|
||||
* @param int|string|null $key 2차원 배열에서 참조할 키.
|
||||
* @return int|float 합계
|
||||
@@ -570,14 +570,14 @@ class Util extends \utilphp\util
|
||||
|
||||
/**
|
||||
* 특정 키를 가진 값으로 묶음
|
||||
*
|
||||
*
|
||||
* @param array $array 배열. 1차원 배열 또는 2차원 배열
|
||||
* @param int|string|null $key 2차원 배열에서 참조할 키.
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayGroupBy(array $array, $key, bool $preserveRowKey=false) {
|
||||
$result = array();
|
||||
|
||||
|
||||
if($preserveRowKey){
|
||||
foreach($array as $rowKey=>$val) {
|
||||
if(key_exists($key, $val)){
|
||||
@@ -596,7 +596,7 @@ class Util extends \utilphp\util
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -618,7 +618,7 @@ class Util extends \utilphp\util
|
||||
*
|
||||
* @param array $items 선택하고자 하는 배열
|
||||
*
|
||||
* @return int|float|string|object 선택된 value값.
|
||||
* @return int|float|string|array|object 선택된 value값.
|
||||
*/
|
||||
public static function choiceRandom(array $items)
|
||||
{
|
||||
@@ -700,7 +700,7 @@ class Util extends \utilphp\util
|
||||
else{
|
||||
throw new \InvalidArgumentException('$lhs is not Traversable');
|
||||
}
|
||||
|
||||
|
||||
if($rhs instanceof \Traversable){
|
||||
$rhsIter = new \IteratorIterator($rhs);
|
||||
}
|
||||
@@ -778,11 +778,11 @@ class Util extends \utilphp\util
|
||||
|
||||
/**
|
||||
* Python 3의 range와 동일
|
||||
* @param int $from
|
||||
* @param null|int $to
|
||||
* @param null|int $step
|
||||
* @param int $from
|
||||
* @param null|int $to
|
||||
* @param null|int $step
|
||||
* @return \Traversable
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function range(int $from, ?int $to=null, ?int $step=null):\Traversable{
|
||||
if($to === null){
|
||||
@@ -809,5 +809,5 @@ class Util extends \utilphp\util
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
+5
-5
@@ -29,7 +29,7 @@ private static $installed = array (
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '40a20a55d3aa08f0ab58a50b0572b17dce33de80',
|
||||
'reference' => 'cf3f40b5787b2a1aed9255775143108ef5b1f278',
|
||||
'name' => 'sammo-hid/sammo',
|
||||
),
|
||||
'versions' =>
|
||||
@@ -315,12 +315,12 @@ private static $installed = array (
|
||||
),
|
||||
'phan/phan' =>
|
||||
array (
|
||||
'pretty_version' => '5.0.0',
|
||||
'version' => '5.0.0.0',
|
||||
'pretty_version' => '5.2.1',
|
||||
'version' => '5.2.1.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'f36b6b9a2f4143a25f35ce94d712ceb0527e9d90',
|
||||
'reference' => 'd724bbb8fc3ba89179868bb5220ecdf6a9c1636b',
|
||||
),
|
||||
'php-extended/php-system' =>
|
||||
array (
|
||||
@@ -450,7 +450,7 @@ private static $installed = array (
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '40a20a55d3aa08f0ab58a50b0572b17dce33de80',
|
||||
'reference' => 'cf3f40b5787b2a1aed9255775143108ef5b1f278',
|
||||
),
|
||||
'scssphp/scssphp' =>
|
||||
array (
|
||||
|
||||
Vendored
+1
-1
@@ -28,7 +28,7 @@ return array(
|
||||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'6714be961f4a45ae8b9a99d5d55c5d07' => $vendorDir . '/tysonandre/var_representation_polyfill/src/var_representation.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => $vendorDir . '/pguardiario/phpuri/phpuri.php',
|
||||
'ef65a1626449d89d0811cf9befce46f0' => $vendorDir . '/illuminate/events/functions.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => $vendorDir . '/pguardiario/phpuri/phpuri.php',
|
||||
'870dc64919afa8b0f700701bb2c6a783' => $baseDir . '/f_config/config.php',
|
||||
);
|
||||
|
||||
Vendored
+1
-1
@@ -29,8 +29,8 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'6714be961f4a45ae8b9a99d5d55c5d07' => __DIR__ . '/..' . '/tysonandre/var_representation_polyfill/src/var_representation.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => __DIR__ . '/..' . '/pguardiario/phpuri/phpuri.php',
|
||||
'ef65a1626449d89d0811cf9befce46f0' => __DIR__ . '/..' . '/illuminate/events/functions.php',
|
||||
'5b154887902198b16314243c6e0e3e19' => __DIR__ . '/..' . '/pguardiario/phpuri/phpuri.php',
|
||||
'870dc64919afa8b0f700701bb2c6a783' => __DIR__ . '/../..' . '/f_config/config.php',
|
||||
);
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -1948,17 +1948,17 @@
|
||||
},
|
||||
{
|
||||
"name": "phan/phan",
|
||||
"version": "5.0.0",
|
||||
"version_normalized": "5.0.0.0",
|
||||
"version": "5.2.1",
|
||||
"version_normalized": "5.2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phan/phan.git",
|
||||
"reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90"
|
||||
"reference": "d724bbb8fc3ba89179868bb5220ecdf6a9c1636b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phan/phan/zipball/f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
|
||||
"reference": "f36b6b9a2f4143a25f35ce94d712ceb0527e9d90",
|
||||
"url": "https://api.github.com/repos/phan/phan/zipball/d724bbb8fc3ba89179868bb5220ecdf6a9c1636b",
|
||||
"reference": "d724bbb8fc3ba89179868bb5220ecdf6a9c1636b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1975,7 +1975,7 @@
|
||||
"symfony/console": "^3.2|^4.0|^5.0",
|
||||
"symfony/polyfill-mbstring": "^1.11.0",
|
||||
"symfony/polyfill-php80": "^1.20.0",
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2"
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.0"
|
||||
@@ -1988,7 +1988,7 @@
|
||||
"ext-tokenizer": "Needed for fallback/polyfill parser support and file/line-based suppressions.",
|
||||
"ext-var_representation": "Suggested for converting values to strings in issue messages"
|
||||
},
|
||||
"time": "2021-08-01T18:17:28+00:00",
|
||||
"time": "2021-09-15T00:08:40+00:00",
|
||||
"bin": [
|
||||
"phan",
|
||||
"phan_client",
|
||||
@@ -2024,7 +2024,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phan/phan/issues",
|
||||
"source": "https://github.com/phan/phan/tree/5.0.0"
|
||||
"source": "https://github.com/phan/phan/tree/5.2.1"
|
||||
},
|
||||
"install-path": "../phan/phan"
|
||||
},
|
||||
|
||||
Vendored
+5
-5
@@ -6,7 +6,7 @@
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '40a20a55d3aa08f0ab58a50b0572b17dce33de80',
|
||||
'reference' => 'cf3f40b5787b2a1aed9255775143108ef5b1f278',
|
||||
'name' => 'sammo-hid/sammo',
|
||||
),
|
||||
'versions' =>
|
||||
@@ -292,12 +292,12 @@
|
||||
),
|
||||
'phan/phan' =>
|
||||
array (
|
||||
'pretty_version' => '5.0.0',
|
||||
'version' => '5.0.0.0',
|
||||
'pretty_version' => '5.2.1',
|
||||
'version' => '5.2.1.0',
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => 'f36b6b9a2f4143a25f35ce94d712ceb0527e9d90',
|
||||
'reference' => 'd724bbb8fc3ba89179868bb5220ecdf6a9c1636b',
|
||||
),
|
||||
'php-extended/php-system' =>
|
||||
array (
|
||||
@@ -427,7 +427,7 @@
|
||||
'aliases' =>
|
||||
array (
|
||||
),
|
||||
'reference' => '40a20a55d3aa08f0ab58a50b0572b17dce33de80',
|
||||
'reference' => 'cf3f40b5787b2a1aed9255775143108ef5b1f278',
|
||||
),
|
||||
'scssphp/scssphp' =>
|
||||
array (
|
||||
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
.git/
|
||||
internal/PHP_CodeSniffer
|
||||
tests/docker
|
||||
tests/run_all_tests_dockerized
|
||||
.git
|
||||
vendor
|
||||
.phan/data
|
||||
composer.phar
|
||||
\#*#
|
||||
build
|
||||
report
|
||||
samples
|
||||
all_output.*
|
||||
.vscode/
|
||||
*.joblog
|
||||
*.swo
|
||||
*.swp
|
||||
*.zip
|
||||
tags
|
||||
.phan/config.local.php
|
||||
nohup.out
|
||||
*.md.new
|
||||
.idea/
|
||||
src_copy
|
||||
out/
|
||||
phpspy.*
|
||||
.phpunit.result.cache
|
||||
errors.txt
|
||||
Vendored
+5
@@ -393,12 +393,15 @@ return [
|
||||
'tool/phan_repl_helpers.php',
|
||||
'internal/dump_fallback_ast.php',
|
||||
'internal/dump_html_styles.php',
|
||||
'internal/emit_signature_map_for_php_version.php',
|
||||
'internal/extract_arg_info.php',
|
||||
'internal/flatten_signature_map.php',
|
||||
'internal/internalsignatures.php',
|
||||
'internal/line_deleter.php',
|
||||
'internal/package.php',
|
||||
'internal/reflection_completeness_check.php',
|
||||
'internal/sanitycheck.php',
|
||||
'internal/sort_signature_map.php',
|
||||
'vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
|
||||
'vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
|
||||
'vendor/phpdocumentor/reflection-docblock/src/DocBlock.php',
|
||||
@@ -651,6 +654,8 @@ return [
|
||||
'RemoveDebugStatementPlugin',
|
||||
'UnsafeCodePlugin',
|
||||
'DeprecateAliasPlugin',
|
||||
// Suggest '@return never'
|
||||
'.phan/plugins/AddNeverReturnTypePlugin.php',
|
||||
// Still have false positives to suppress
|
||||
// '.phan/plugins/StaticVariableMisusePlugin.php',
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use ast\Node;
|
||||
use Phan\Analysis\BlockExitStatusChecker;
|
||||
use Phan\CodeBase;
|
||||
use Phan\Language\Element\Func;
|
||||
use Phan\Language\Element\Method;
|
||||
use Phan\Language\Type\NeverType;
|
||||
use Phan\PluginV3;
|
||||
use Phan\PluginV3\AnalyzeFunctionCapability;
|
||||
use Phan\PluginV3\AnalyzeMethodCapability;
|
||||
|
||||
/**
|
||||
* This plugin checks if a function or method will not return (and has no overrides).
|
||||
* If the function doesn't have a return type of never.
|
||||
* then this plugin will emit an issue.
|
||||
*
|
||||
* It hooks into two events:
|
||||
*
|
||||
* - analyzeMethod
|
||||
* Once all methods are parsed, this method will be called
|
||||
* on every method in the code base
|
||||
*
|
||||
* - analyzeFunction
|
||||
* Once all functions have been parsed, this method will
|
||||
* be called on every function in the code base.
|
||||
*
|
||||
* A plugin file must
|
||||
*
|
||||
* - Contain a class that inherits from \Phan\PluginV3
|
||||
*
|
||||
* - End by returning an instance of that class.
|
||||
*
|
||||
* It is assumed without being checked that plugins aren't
|
||||
* mangling state within the passed code base or context.
|
||||
*
|
||||
* Note: When adding new plugins,
|
||||
* add them to the corresponding section of README.md
|
||||
*/
|
||||
final class NeverReturnPlugin extends PluginV3 implements
|
||||
AnalyzeFunctionCapability,
|
||||
AnalyzeMethodCapability
|
||||
{
|
||||
|
||||
/**
|
||||
* @param CodeBase $code_base
|
||||
* The code base in which the method exists
|
||||
*
|
||||
* @param Method $method
|
||||
* A method being analyzed
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public function analyzeMethod(
|
||||
CodeBase $code_base,
|
||||
Method $method
|
||||
): void {
|
||||
$stmts_list = self::getStatementListToAnalyze($method);
|
||||
if ($stmts_list === null) {
|
||||
// check for abstract methods, generators, etc.
|
||||
return;
|
||||
}
|
||||
if ($method->getFQSEN() !== $method->getDefiningFQSEN()) {
|
||||
// Check if this was inherited by a descendant class.
|
||||
return;
|
||||
}
|
||||
|
||||
if ($method->getUnionType()->hasType(NeverType::instance(false))) {
|
||||
return;
|
||||
}
|
||||
if ($method->isOverriddenByAnother()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This modifies the nodes in place, check this last
|
||||
if (!BlockExitStatusChecker::willUnconditionallyNeverReturn($stmts_list)) {
|
||||
return;
|
||||
}
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
$method->getContext(),
|
||||
'PhanPluginNeverReturnMethod',
|
||||
"Method {METHOD} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead",
|
||||
[$method->getRepresentationForIssue(), $method->getUnionType(), 'never']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CodeBase $code_base
|
||||
* The code base in which the function exists
|
||||
*
|
||||
* @param Func $function
|
||||
* A function or closure being analyzed
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public function analyzeFunction(
|
||||
CodeBase $code_base,
|
||||
Func $function
|
||||
): void {
|
||||
$stmts_list = self::getStatementListToAnalyze($function);
|
||||
if ($stmts_list === null) {
|
||||
// check for abstract methods, generators, etc.
|
||||
return;
|
||||
}
|
||||
|
||||
if ($function->getUnionType()->hasType(NeverType::instance(false))) {
|
||||
return;
|
||||
}
|
||||
// This modifies the nodes in place, check this last
|
||||
if (!BlockExitStatusChecker::willUnconditionallyNeverReturn($stmts_list)) {
|
||||
return;
|
||||
}
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
$function->getContext(),
|
||||
'PhanPluginNeverReturnFunction',
|
||||
"Function {FUNCTION} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead",
|
||||
[$function->getRepresentationForIssue(), $function->getUnionType(), 'never']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Func|Method $func
|
||||
* @return ?Node - returns null if there's no statement list to analyze
|
||||
*/
|
||||
private static function getStatementListToAnalyze($func): ?Node
|
||||
{
|
||||
if (!$func->hasNode()) {
|
||||
return null;
|
||||
} elseif ($func->hasYield()) {
|
||||
// generators always return Generator.
|
||||
return null;
|
||||
}
|
||||
$node = $func->getNode();
|
||||
if (!$node) {
|
||||
return null;
|
||||
}
|
||||
return $node->children['stmts'];
|
||||
}
|
||||
}
|
||||
|
||||
// Every plugin needs to return an instance of itself at the
|
||||
// end of the file in which it's defined.
|
||||
return new NeverReturnPlugin();
|
||||
@@ -116,7 +116,7 @@ class AvoidableGetterVisitor extends PluginAwarePostAnalysisVisitor
|
||||
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->lineno),
|
||||
$issue_name,
|
||||
"Can replace {METHOD} with {PROPERTY}",
|
||||
[ASTReverter::toShortString($node), '$this->' . $property_name]
|
||||
|
||||
@@ -95,7 +95,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
|
||||
$normalized_case_cond = is_object($case_cond) ? ASTReverter::toShortString($case_cond) : self::normalizeSwitchKey($case_cond);
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($case_node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($case_node->lineno),
|
||||
'PhanPluginDuplicateSwitchCase',
|
||||
"Duplicate/Equivalent switch case({STRING_LITERAL}) detected in switch statement - the later entry will be ignored in favor of case {CODE} at line {LINE}.",
|
||||
[$normalized_case_cond, ASTReverter::toShortString($case_constant_set[$cond_key]->children['cond']), $case_constant_set[$cond_key]->lineno],
|
||||
@@ -171,7 +171,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if ($old_index !== null) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($children[$i]->lineno),
|
||||
(clone $this->context)->withLineNumberStart($children[$i]->lineno),
|
||||
'PhanPluginDuplicateSwitchCaseLooseEquality',
|
||||
"Switch case({STRING_LITERAL}) is loosely equivalent (==) to an earlier case ({STRING_LITERAL}) in switch statement - the earlier entry may be chosen instead.",
|
||||
[self::normalizeSwitchKey($values_to_check[$i]), self::normalizeSwitchKey($values_to_check[$old_index])],
|
||||
@@ -227,7 +227,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
|
||||
$normalized_arm_expr_cond = ASTReverter::toShortString($arm_expr_cond);
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($lineno),
|
||||
(clone $this->context)->withLineNumberStart($lineno),
|
||||
'PhanPluginDuplicateMatchArmExpression',
|
||||
"Duplicate match arm expression({STRING_LITERAL}) detected in match expression - the later entry will be ignored in favor of expression {CODE} at line {LINE}.",
|
||||
[$normalized_arm_expr_cond, ASTReverter::toShortString($arm_expr_constant_set[$cond_key][0]), $arm_expr_constant_set[$cond_key][1]],
|
||||
@@ -302,7 +302,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if (is_string($key) && strncmp($key, self::HASH_PREFIX, strlen(self::HASH_PREFIX)) === 0) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($entry->lineno),
|
||||
(clone $this->context)->withLineNumberStart($entry->lineno),
|
||||
'PhanPluginDuplicateArrayKeyExpression',
|
||||
"Duplicate dynamic array key expression ({CODE}) detected in array - the earlier entry at line {LINE} will be ignored if the expression had the same value.",
|
||||
[ASTReverter::toShortString($entry->children['key']), $old_entry->lineno],
|
||||
@@ -315,7 +315,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
|
||||
$normalized_key = self::normalizeKey($key);
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($entry->lineno),
|
||||
(clone $this->context)->withLineNumberStart($entry->lineno),
|
||||
'PhanPluginDuplicateArrayKey',
|
||||
"Duplicate/Equivalent array key value({STRING_LITERAL}) detected in array - the earlier entry {CODE} at line {LINE} will be ignored.",
|
||||
[$normalized_key, ASTReverter::toShortString($old_entry->children['key']), $old_entry->lineno],
|
||||
|
||||
@@ -351,7 +351,7 @@ class RedundantNodePostAnalysisVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if ($hash === $prev_hash) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($child->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($child->lineno ?? $node->lineno),
|
||||
'PhanPluginDuplicateAdjacentStatement',
|
||||
"Statement {CODE} is a duplicate of the statement on the above line. Suppress this issue instance if there's a good reason for this.",
|
||||
[ASTReverter::toShortString($child)]
|
||||
@@ -476,7 +476,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
|
||||
if (isset($condition_set[$cond_hash])) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($cond->lineno ?? $children[$i]->lineno),
|
||||
(clone $this->context)->withLineNumberStart($cond->lineno ?? $children[$i]->lineno),
|
||||
'PhanPluginDuplicateIfCondition',
|
||||
'Saw the same condition {CODE} in an earlier if/elseif statement',
|
||||
[ASTReverter::toShortString($cond)]
|
||||
@@ -490,7 +490,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
|
||||
if (($stmts->children ?? null) && ASTHasher::hash($stmts) === ASTHasher::hash($children[$N - 2]->children['stmts'])) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($children[$N - 1]->lineno),
|
||||
(clone $this->context)->withLineNumberStart($children[$N - 1]->lineno),
|
||||
'PhanPluginDuplicateIfStatements',
|
||||
'The statements of the else duplicate the statements of the previous if/elseif statement with condition {CODE}',
|
||||
[ASTReverter::toShortString($children[$N - 2]->children['cond'])]
|
||||
@@ -522,7 +522,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
|
||||
if ($prev_hash === $cur_hash) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($catches[$i]->lineno),
|
||||
(clone $this->context)->withLineNumberStart($catches[$i]->lineno),
|
||||
'PhanPluginDuplicateCatchStatementBody',
|
||||
'The implementation of catch({CODE}) and catch({CODE}) are identical, and can be combined if the application only needs to supports php 7.1 and newer',
|
||||
[
|
||||
|
||||
@@ -111,7 +111,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($last_if_elem->children['stmts']->lineno ?? $last_if_elem->lineno),
|
||||
(clone $this->context)->withLineNumberStart($last_if_elem->children['stmts']->lineno ?? $last_if_elem->lineno),
|
||||
'PhanPluginEmptyStatementIf',
|
||||
'Empty statement list statement detected for the last if/elseif statement',
|
||||
[]
|
||||
@@ -188,7 +188,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
|
||||
'PhanPluginEmptyStatementForLoop',
|
||||
'Empty statement list statement detected for the for loop',
|
||||
[]
|
||||
@@ -216,7 +216,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
|
||||
'PhanPluginEmptyStatementWhileLoop',
|
||||
'Empty statement list statement detected for the while loop',
|
||||
[]
|
||||
@@ -244,7 +244,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($stmts_node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($stmts_node->lineno),
|
||||
'PhanPluginEmptyStatementDoWhileLoop',
|
||||
'Empty statement list statement detected for the do-while loop',
|
||||
[]
|
||||
@@ -273,7 +273,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($stmts_node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($stmts_node->lineno),
|
||||
'PhanPluginEmptyStatementForeachLoop',
|
||||
'Empty statement list statement detected for the foreach loop',
|
||||
[]
|
||||
@@ -292,7 +292,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if (!$this->hasTODOComment($try_node->lineno, $node, $node->children['catches']->children[0]->lineno ?? $finally_node->lineno ?? null)) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($try_node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($try_node->lineno),
|
||||
'PhanPluginEmptyStatementTryBody',
|
||||
'Empty statement list statement detected for the try statement\'s body',
|
||||
[]
|
||||
@@ -303,7 +303,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if (!$this->hasTODOComment($finally_node->lineno, $node)) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($finally_node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($finally_node->lineno),
|
||||
'PhanPluginEmptyStatementTryFinally',
|
||||
'Empty statement list statement detected for the try\'s finally body',
|
||||
[]
|
||||
@@ -348,7 +348,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->lineno),
|
||||
'PhanPluginEmptyStatementSwitch',
|
||||
'No side effects seen for any cases of this switch statement',
|
||||
[]
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ class InlineHTMLPlugin extends PluginV3 implements
|
||||
}
|
||||
$this->emitIssue(
|
||||
$code_base,
|
||||
clone($context)->withLineNumberStart($token[2]),
|
||||
(clone $context)->withLineNumberStart($token[2]),
|
||||
$issue,
|
||||
$message,
|
||||
[StringUtil::jsonEncode(self::truncate($token[1]))]
|
||||
|
||||
@@ -144,7 +144,7 @@ class InvokePHPNativeSyntaxCheckPlugin extends PluginV3 implements
|
||||
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
clone($context)->withLineNumberStart($lineno),
|
||||
(clone $context)->withLineNumberStart($lineno),
|
||||
'PhanNativePHPSyntaxCheckPlugin',
|
||||
'Saw error or notice for {FILE} --syntax-check: {DETAILS}',
|
||||
[
|
||||
@@ -182,6 +182,9 @@ class InvokeExecutionPromise
|
||||
/** @var string the raw bytes from stdout with serialized data */
|
||||
private $raw_stdout = '';
|
||||
|
||||
/** @var string */
|
||||
private $fallback_error = '';
|
||||
|
||||
/** @var Context has the file name being analyzed */
|
||||
private $context;
|
||||
|
||||
@@ -190,7 +193,7 @@ class InvokeExecutionPromise
|
||||
|
||||
public function __construct(string $binary, string $file_contents, Context $context)
|
||||
{
|
||||
$this->context = clone($context);
|
||||
$this->context = clone $context;
|
||||
$new_file_contents = Parser::removeShebang($file_contents);
|
||||
// TODO: Use symfony process
|
||||
// Note: We might have invalid utf-8, ensure that the streams are opened in binary mode.
|
||||
@@ -252,7 +255,10 @@ class InvokeExecutionPromise
|
||||
}
|
||||
$this->process = $process;
|
||||
|
||||
self::streamPutContents($pipes[0], $new_file_contents);
|
||||
error_clear_last();
|
||||
if (!self::streamPutContents($pipes[0], $new_file_contents)) {
|
||||
$this->fallback_error = \error_get_last()['message'] ?? '';
|
||||
}
|
||||
}
|
||||
$this->pipes = $pipes;
|
||||
|
||||
@@ -290,17 +296,18 @@ class InvokeExecutionPromise
|
||||
/**
|
||||
* @param resource $stream stream to write $file_contents to before fclose()
|
||||
* @param string $file_contents
|
||||
* @return void
|
||||
* See https://bugs.php.net/bug.php?id=39598
|
||||
*/
|
||||
private static function streamPutContents($stream, string $file_contents): void
|
||||
private static function streamPutContents($stream, string $file_contents): bool
|
||||
{
|
||||
try {
|
||||
while (strlen($file_contents) > 0) {
|
||||
$bytes_written = fwrite($stream, $file_contents);
|
||||
$bytes_written = with_disabled_phan_error_handler(/** @return int|false */ static function () use ($stream, $file_contents) {
|
||||
return @fwrite($stream, $file_contents);
|
||||
});
|
||||
if ($bytes_written === false) {
|
||||
error_log('failed to write in ' . __METHOD__);
|
||||
return;
|
||||
CLI::printWarningToStderr('failed to write in ' . __METHOD__);
|
||||
return false;
|
||||
}
|
||||
if ($bytes_written === 0) {
|
||||
$read_streams = [];
|
||||
@@ -316,8 +323,8 @@ class InvokeExecutionPromise
|
||||
// $stream is ready to be written to?
|
||||
$bytes_written = fwrite($stream, $file_contents);
|
||||
if (!$bytes_written) {
|
||||
error_log('failed to write in ' . __METHOD__ . ' but the stream should be ready');
|
||||
return;
|
||||
CLI::printToStderr('failed to write in ' . __METHOD__ . ' but the stream should be ready');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($bytes_written > 0) {
|
||||
@@ -327,6 +334,7 @@ class InvokeExecutionPromise
|
||||
} finally {
|
||||
fclose($stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,6 +398,14 @@ class InvokeExecutionPromise
|
||||
if (!$this->done) {
|
||||
throw new RangeException("Called " . __METHOD__ . " too early");
|
||||
}
|
||||
if ($this->error === '') {
|
||||
// There was an error running the process, but no output to stdout.
|
||||
$result = "No output was detected. Is " . var_representation($this->binary) . " a relative or absolute path to an executable PHP binary?";
|
||||
if ($this->fallback_error !== '') {
|
||||
$result .= ' Error sending file contents to syntax check: ' . $this->fallback_error;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
@@ -409,6 +425,9 @@ class InvokeExecutionPromise
|
||||
return $this->binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->tmp_path = null;
|
||||
|
||||
@@ -111,6 +111,9 @@ class MoreSpecificElementTypePlugin extends PluginV3 implements
|
||||
if ($declared_return_type->isStrictSubtypeOf($code_base, $actual_type)) {
|
||||
return false;
|
||||
}
|
||||
if (!$actual_type->isStrictSubtypeOf($code_base, $declared_return_type)) {
|
||||
return false;
|
||||
}
|
||||
if (!$actual_type->canCastToUnionType($declared_return_type, $code_base)) {
|
||||
// Don't warn here about type mismatches such as int->string or object->array, but do warn about SubClass->BaseClass.
|
||||
// Phan should warn elsewhere about those mismatches
|
||||
|
||||
@@ -134,7 +134,7 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($expression->lineno),
|
||||
(clone $this->context)->withLineNumberStart($expression->lineno),
|
||||
$issue_type,
|
||||
$issue_msg,
|
||||
[$function_name, $this->context->getNamespace()]
|
||||
@@ -167,7 +167,9 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$constant_name_lower = strtolower($constant_name);
|
||||
if ($constant_name_lower === 'true' || $constant_name_lower === 'false' || $constant_name_lower === 'null') {
|
||||
// These are keywords and are the same in any namespace
|
||||
// These are treated similarly to keywords and are either
|
||||
// 1. the same in any namespace
|
||||
// 2. `use somethingelse\true [as false];`
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +185,7 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
|
||||
{
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($expression->lineno),
|
||||
(clone $this->context)->withLineNumberStart($expression->lineno),
|
||||
self::NotFullyQualifiedGlobalConstant,
|
||||
'Expected usage of {CONST} to be fully qualified or have a use statement but none were found in namespace {NAMESPACE}',
|
||||
[$constant_name, $this->context->getNamespace()]
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
use Microsoft\PhpParser\Node\Expression\CallExpression;
|
||||
use Microsoft\PhpParser\Node\QualifiedName;
|
||||
use Microsoft\PhpParser\Node\ReservedWord;
|
||||
use Microsoft\PhpParser\Token;
|
||||
use Phan\AST\TolerantASTConverter\NodeUtils;
|
||||
use Phan\CodeBase;
|
||||
use Phan\IssueInstance;
|
||||
@@ -29,15 +31,26 @@ call_user_func(static function (): void {
|
||||
$expected_name = $instance->getTemplateParameters()[0];
|
||||
$edits = [];
|
||||
foreach ($contents->getNodesAtLine($line) as $node) {
|
||||
if (!$node instanceof QualifiedName) {
|
||||
if ($node instanceof QualifiedName) {
|
||||
if ($node->globalSpecifier || $node->relativeSpecifier) {
|
||||
IssueFixer::debug("skip already globally or relatively specified\n");
|
||||
// This is already qualified
|
||||
continue;
|
||||
}
|
||||
$actual_name = (new NodeUtils($contents->getContents()))->phpParserNameToString($node);
|
||||
} elseif ($node instanceof ReservedWord) {
|
||||
// A reserved word in other contexts such as 'float'
|
||||
$token = $node->children;
|
||||
if (!$token instanceof Token) {
|
||||
continue;
|
||||
}
|
||||
$actual_name = (new NodeUtils($contents->getContents()))->tokenToString($token);
|
||||
} else {
|
||||
IssueFixer::debug("skip wrong node kind " . get_class($node) . "\n");
|
||||
continue;
|
||||
}
|
||||
if ($node->globalSpecifier || $node->relativeSpecifier) {
|
||||
// This is already qualified
|
||||
continue;
|
||||
}
|
||||
$actual_name = (new NodeUtils($contents->getContents()))->phpParserNameToString($node);
|
||||
if ($actual_name !== $expected_name) {
|
||||
IssueFixer::debug("skip '$actual_name' !== '$expected_name'\n");
|
||||
continue;
|
||||
}
|
||||
$is_actual_call = $node->parent instanceof CallExpression;
|
||||
@@ -58,7 +71,8 @@ call_user_func(static function (): void {
|
||||
} else {
|
||||
// Don't do this if the global function this refers to doesn't exist.
|
||||
// TODO: Support namespaced functions
|
||||
if (!$code_base->hasGlobalConstantWithFQSEN(FullyQualifiedGlobalConstantName::fromFullyQualifiedString($actual_name))) {
|
||||
if (!$code_base->hasGlobalConstantWithFQSEN(FullyQualifiedGlobalConstantName::fromFullyQualifiedString($actual_name)) &&
|
||||
!in_array(strtolower($actual_name), ['null', 'true', 'false'], true)) {
|
||||
IssueFixer::debug("skip attempt to fix $actual_name because the constant was not found in the global scope\n");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class PHPDocInWrongCommentPlugin extends PluginV3 implements
|
||||
if ($comment_string[0] === '#' && substr($comment_string, 1, 1) !== '[') {
|
||||
$this->emitIssue(
|
||||
$code_base,
|
||||
(clone($context))->withLineNumberStart($token[2]),
|
||||
(clone $context)->withLineNumberStart($token[2]),
|
||||
'PhanPluginPHPDocHashComment',
|
||||
'Saw comment starting with {COMMENT} in {COMMENT} - consider using {COMMENT} instead to avoid confusion with php 8.0 {COMMENT} attributes',
|
||||
['#', StringUtil::jsonEncode(self::truncate(trim($comment_string))), '//', '#[']
|
||||
@@ -76,7 +76,7 @@ class PHPDocInWrongCommentPlugin extends PluginV3 implements
|
||||
}
|
||||
$this->emitIssue(
|
||||
$code_base,
|
||||
(clone($context))->withLineNumberStart($token[2]),
|
||||
(clone $context)->withLineNumberStart($token[2]),
|
||||
'PhanPluginPHPDocInWrongComment',
|
||||
'Saw possible phpdoc annotation in ordinary block comment {COMMENT}. PHPDoc comments should start with "/**" (followed by whitespace), not "/*"',
|
||||
[StringUtil::jsonEncode(self::truncate($comment_string))]
|
||||
|
||||
+3
-3
@@ -60,7 +60,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
|
||||
) use (
|
||||
$fmt_index,
|
||||
$arg_index
|
||||
): void {
|
||||
): void {
|
||||
if (\count($args) <= $fmt_index) {
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
|
||||
) use (
|
||||
$type_index,
|
||||
$arg_index
|
||||
): void {
|
||||
): void {
|
||||
if (\count($args) <= $type_index) {
|
||||
return;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
|
||||
) use (
|
||||
$type_index,
|
||||
$arg_index
|
||||
): void {
|
||||
): void {
|
||||
if (\count($args) <= $type_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-2
@@ -706,8 +706,8 @@ class PrintfCheckerPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
|
||||
* @param CodeBase $code_base
|
||||
* @param Context $context
|
||||
* @param string $fmt_str
|
||||
* @param ConversionSpec[][] $types_of_arg contains array of ConversionSpec for
|
||||
* each position in the untranslated format string.
|
||||
* @param associative-array<int,array<mixed,ConversionSpec|true>> $types_of_arg contains array of ConversionSpec for
|
||||
* each position in the untranslated format string.
|
||||
*/
|
||||
protected static function validateTranslations(CodeBase $code_base, Context $context, string $fmt_str, array $types_of_arg): void
|
||||
{
|
||||
|
||||
+10
@@ -596,6 +596,16 @@ This is only useful in applications or libraries that print output in only a few
|
||||
|
||||
Suppression comments can use the issue name `PhanPluginRemoveDebugAny` to suppress all issue types emitted by this plugin.
|
||||
|
||||
#### AddNeverReturnTypePlugin.php
|
||||
|
||||
This plugin checks if a function or method will not return (and has no overrides).
|
||||
If the function doesn't have a return type of never.
|
||||
then this plugin will emit an issue.
|
||||
Closures and short error functions are currently not checked
|
||||
|
||||
- **PhanPluginNeverReturnMethod**: `Method {METHOD} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead`
|
||||
- **PhanPluginNeverReturnFunction**: `Function {FUNCTION} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead`
|
||||
|
||||
### 4. Demo plugins:
|
||||
|
||||
These files demonstrate plugins for Phan.
|
||||
|
||||
@@ -121,7 +121,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
|
||||
$issue_name = 'PhanPluginRedundantAssignment';
|
||||
}
|
||||
if ($this->context->isInLoop()) {
|
||||
$this->context->deferCheckToOutermostLoop(function (Context $context_after_loop) use ($issue_name, $var_name, $variable_type): void {
|
||||
$this->context->deferCheckToOutermostLoop(function (Context $context_after_loop) use ($issue_name, $var_name, $variable_type, $var): void {
|
||||
$new_variable = $context_after_loop->getScope()->getVariableByNameOrNull($var_name);
|
||||
if (!$new_variable) {
|
||||
return;
|
||||
@@ -135,7 +135,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
$this->context,
|
||||
(clone $this->context)->withLineNumberStart($var->lineno),
|
||||
$issue_name,
|
||||
'Assigning {TYPE} to variable ${VARIABLE} which already has that value',
|
||||
[$variable_type, $var_name]
|
||||
@@ -145,7 +145,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
$this->context,
|
||||
(clone $this->context)->withLineNumberStart($var->lineno),
|
||||
$issue_name,
|
||||
'Assigning {TYPE} to variable ${VARIABLE} which already has that value',
|
||||
[$expr_type, $var_name]
|
||||
|
||||
@@ -38,7 +38,10 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
*/
|
||||
public function getAnalyzeFunctionCallClosures(CodeBase $code_base): array
|
||||
{
|
||||
$warn_remove_debug_call = static function (CodeBase $code_base, Context $context, FunctionInterface $function): void {
|
||||
$warn_remove_debug_call = static function (CodeBase $code_base, Context $context, FunctionInterface $function, ?Node $node): void {
|
||||
if ($node) {
|
||||
$context = (clone $context)->withLineNumberStart($node->lineno);
|
||||
}
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
$context,
|
||||
@@ -55,12 +58,12 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
Context $context,
|
||||
Func $function,
|
||||
array $unused_args,
|
||||
?Node $unused_node = null
|
||||
?Node $node = null
|
||||
) use ($warn_remove_debug_call): void {
|
||||
if (self::shouldSuppressDebugIssues($code_base, $context)) {
|
||||
return;
|
||||
}
|
||||
$warn_remove_debug_call($code_base, $context, $function);
|
||||
$warn_remove_debug_call($code_base, $context, $function, $node);
|
||||
};
|
||||
/**
|
||||
* @param list<Node|string|int|float> $args the nodes for the arguments to the invocation
|
||||
@@ -71,7 +74,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
Context $context,
|
||||
Func $function,
|
||||
array $args,
|
||||
?Node $unused_node = null
|
||||
?Node $node = null
|
||||
) use ($warn_remove_debug_call): void {
|
||||
if (self::shouldSuppressDebugIssues($code_base, $context)) {
|
||||
return;
|
||||
@@ -84,7 +87,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
return;
|
||||
}
|
||||
}
|
||||
$warn_remove_debug_call($code_base, $context, $function);
|
||||
$warn_remove_debug_call($code_base, $context, $function, $node);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -95,7 +98,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
Context $context,
|
||||
Func $function,
|
||||
array $args,
|
||||
?Node $unused_node = null
|
||||
?Node $node = null
|
||||
) use ($warn_remove_debug_call): void {
|
||||
$file = $args[0] ?? null;
|
||||
if (!$file instanceof Node || $file->kind !== ast\AST_CONST || !in_array($file->children['name']->children['name'] ?? null, ['STDOUT', 'STDERR'], true)) {
|
||||
@@ -106,7 +109,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
|
||||
return;
|
||||
}
|
||||
|
||||
$warn_remove_debug_call($code_base, $context, $function);
|
||||
$warn_remove_debug_call($code_base, $context, $function, $node);
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ class SleepCheckerVisitor extends PluginAwarePostAnalysisVisitor
|
||||
*/
|
||||
private function analyzeReturnValue($expr_node, int $lineno, array &$sleep_properties): void
|
||||
{
|
||||
$context = clone($this->context)->withLineNumberStart($lineno);
|
||||
$context = (clone $this->context)->withLineNumberStart($lineno);
|
||||
if (!($expr_node instanceof Node)) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
|
||||
@@ -60,7 +60,7 @@ class StrictComparisonPlugin extends PluginV3 implements
|
||||
$index,
|
||||
$index_name,
|
||||
$min_args
|
||||
): void {
|
||||
): void {
|
||||
if (count($args) < $min_args) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if ($function->isPHPInternal()) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->lineno),
|
||||
self::SuspiciousParamOrderInternal,
|
||||
'Suspicious order for arguments named {DETAILS} - These are being passed to parameters {DETAILS} of {FUNCTION}',
|
||||
[
|
||||
@@ -213,7 +213,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
|
||||
} else {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->lineno),
|
||||
self::SuspiciousParamOrder,
|
||||
'Suspicious order for arguments named {DETAILS} - These are being passed to parameters {DETAILS} of {FUNCTION} defined at {FILE}:{LINE}',
|
||||
[
|
||||
@@ -280,7 +280,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
|
||||
if ($function->isPHPInternal()) {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
|
||||
self::SuspiciousParamPositionInternal,
|
||||
'Suspicious order for argument {DETAILS} - This is getting passed to parameter {DETAILS} of {FUNCTION}',
|
||||
[
|
||||
@@ -292,7 +292,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
|
||||
} else {
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
clone($this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
|
||||
self::SuspiciousParamPosition,
|
||||
'Suspicious order for argument {DETAILS} - This is getting passed to parameter {DETAILS} of {FUNCTION} defined at {FILE}:{LINE}',
|
||||
[
|
||||
|
||||
@@ -132,7 +132,8 @@ class UnknownElementTypePlugin extends PluginV3 implements
|
||||
$inferred_types[$i] = $combined_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
@@ -350,7 +351,8 @@ class UnknownElementTypePlugin extends PluginV3 implements
|
||||
$inferred_types[$i] = $combined_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ final class UnreachableCodeVisitor extends PluginAwarePostAnalysisVisitor
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$context = clone($this->context)->withLineNumberStart($next_node->lineno);
|
||||
if ($this->context->isInFunctionLikeScope()) {
|
||||
if ($this->context->getFunctionLikeInScope($this->code_base)->checkHasSuppressIssueAndIncrementCount('PhanPluginUnreachableCode')) {
|
||||
// don't emit the below issue.
|
||||
@@ -102,7 +101,7 @@ final class UnreachableCodeVisitor extends PluginAwarePostAnalysisVisitor
|
||||
}
|
||||
$this->emitPluginIssue(
|
||||
$this->code_base,
|
||||
$context,
|
||||
(clone $this->context)->withLineNumberStart($next_node->lineno),
|
||||
'PhanPluginUnreachableCode',
|
||||
'Unreachable statement detected',
|
||||
[]
|
||||
|
||||
+3
-3
@@ -54,7 +54,7 @@ class WhitespacePlugin extends PluginV3 implements
|
||||
if ($newline_position !== false) {
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $newline_position)),
|
||||
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $newline_position)),
|
||||
self::CarriageReturn,
|
||||
'The first occurrence of a carriage return ("\r") was seen here. Running "dos2unix" can fix that.'
|
||||
);
|
||||
@@ -63,7 +63,7 @@ class WhitespacePlugin extends PluginV3 implements
|
||||
if ($tab_position !== false) {
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $tab_position)),
|
||||
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $tab_position)),
|
||||
self::Tab,
|
||||
'The first occurrence of a tab was seen here. Running "expand" can fix that.'
|
||||
);
|
||||
@@ -71,7 +71,7 @@ class WhitespacePlugin extends PluginV3 implements
|
||||
if (preg_match('/[ \t]\r?$/mS', $file_contents, $match, PREG_OFFSET_CAPTURE)) {
|
||||
self::emitIssue(
|
||||
$code_base,
|
||||
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $match[0][1])),
|
||||
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $match[0][1])),
|
||||
self::WhitespaceTrailing,
|
||||
'The first occurrence of trailing whitespace was seen here.'
|
||||
);
|
||||
|
||||
Vendored
+60
@@ -1,5 +1,65 @@
|
||||
Phan NEWS
|
||||
|
||||
Sep 14 2021, Phan 5.2.1
|
||||
-----------------------
|
||||
|
||||
New Features:
|
||||
- Improve analysis of conditions detecting the empty/non-empty array. (#4523)
|
||||
E.g. support `if ($x === []) {...} else {...}`, `if (count($x) > 0) {...} else {...}`, etc.
|
||||
- Raise severity of `PhanTypeNonVarPassByRef` to critical. It throws an Error in php 8.0+. (#3830)
|
||||
- Infer from conditions such as `in_array($var, $array, true)` that $array is a non-empty array and that $var is of a type found in the elements of $array. (#2511)
|
||||
|
||||
Plugins:
|
||||
- Emit a proper warning when `InvokePHPNativeSyntaxCheckPlugin` is passed a path to a php binary that is missing or invalid (or if the syntax check crashed). (#4116)
|
||||
Previously, Phan would crash with an error such as `fwrite(): write of 8196 bytes failed with errno=32 Broken pipe`
|
||||
- Fix false positive `PhanPluginMoreSpecificActualReturnType` for phpdoc array shape return type and returned generic array. (#4531)
|
||||
|
||||
Bug fixes:
|
||||
- Fix type inference logic that was looking for array specializations rather than array or any array subtype (#4512)
|
||||
- Fix false positive `PhanUnreferencedClosure`/`PhanUnreferencedFunction` seen when a closure/function name was passed to a function such as `uasort` that already had a plugin analyzing calls of the closure. (#4090, #4519)
|
||||
- Fix false positive/negative `PhanTypeMissingReturn*` instances. (#4537)
|
||||
|
||||
The check was wrong and should have been checking for a statement list that throws/exits.
|
||||
Return statements can be omitted if a function unconditionally exits.
|
||||
|
||||
Also, check for the real `never` return type when emitting issues
|
||||
- Fix false positive `PhanPossiblyUndefinedGlobalVariable*` instance when `global $var` is used within a conditional. (#4539)
|
||||
- Fix false positive `PhanPluginRedundantAssignmentInLoop` instance when a variable is modified in a catch statement with a break/continue. (#4542)
|
||||
- Fix some incorrect line numbers in some plugin issues.
|
||||
- Fix crash seen when parsing intersection types containing union types such as `non-empty-array&array<'a'|'b'>` (#4544)
|
||||
|
||||
Maintenance:
|
||||
- Fix old return type signature for `get_headers` (#3273)
|
||||
- Print instructions on how to upgrade php-ast to 1.0.11+ if an outdated version is installed. (#4532)
|
||||
|
||||
Aug 26 2021, Phan 5.2.0
|
||||
-----------------------
|
||||
|
||||
Plugins:
|
||||
- Add `AddNeverReturnTypePlugin`` which will suggest adding a phpdoc return type of `@return never`. (#4468)
|
||||
|
||||
Bug fixes:
|
||||
- When using the polyfill parser, properly parse nullable class property declarations as nullable. (#4492)
|
||||
- Don't emit PhanIncompatibleRealPropertyType for private base property. (#4426)
|
||||
- Fix false positive where a method overriding an existing method could be treated as having overrides. (#4502)
|
||||
- Consistently support `numeric-string` in all phpdoc
|
||||
- Fix false positive `PhanTypeMismatchPropertyDefaultReal` warning for literal integer and `float` typed property. (#4507)
|
||||
- Fix false positive warnings such as `PhanImpossibleTypeComparison` about string subtypes not casting to other string subtypes (#4514)
|
||||
|
||||
Maintenance:
|
||||
- Change internal representation of FunctionSignatureMap delta files.
|
||||
- Add a new exit status bit flag to `BlockExitStatusChecker` to indicate that a function will exit or infinitely loop (`STATUS_NORETURN`) (#4468)
|
||||
- Internally represent the base function map using php 8.0 signatures instead of php 7.3 - applying deltas backwards has the same result (#4478)
|
||||
|
||||
Aug 07 2021, Phan 5.1.0
|
||||
-----------------------
|
||||
|
||||
New Features (Analysis):
|
||||
- Support running Phan 5 with AST version 80 instead of 85 but warn about php-ast being outdated.
|
||||
|
||||
Documentation:
|
||||
- Update documentation of `--target-php-version` and `--minimum-target-php-version`
|
||||
|
||||
Aug 01 2021, Phan 5.0.0
|
||||
-----------------------
|
||||
|
||||
|
||||
Vendored
+2
-3
@@ -5,13 +5,12 @@ information is available or can be deduced. Phan has a good (but not comprehensi
|
||||
and can track values in a few use cases (e.g. arrays, integers, and strings).
|
||||
|
||||
[](https://dev.azure.com/tysonandre775/phan/_build/latest?definitionId=3&branchName=v5)
|
||||
[](https://github.com/phan/phan/actions/workflows/main.yml?query=branch%3Av5)
|
||||
[](https://ci.appveyor.com/project/TysonAndre/phan/branch/v5)
|
||||
[](https://gitter.im/phan/phan?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://packagist.org/packages/phan/phan)
|
||||
[](https://github.com/phan/phan/blob/v5/LICENSE)
|
||||
|
||||
This is the unstable branch for an upcoming Phan 5 release. The branch for the older stable Phan 4 release line is [here](https://github.com/phan/phan/tree/v4).
|
||||
|
||||
# Getting Started
|
||||
|
||||
The easiest way to use Phan is via Composer.
|
||||
@@ -23,7 +22,7 @@ composer require phan/phan
|
||||
With Phan installed, you'll want to [create a `.phan/config.php` file](https://github.com/phan/phan/wiki/Getting-Started#creating-a-config-file) in
|
||||
your project to tell Phan how to analyze your source code. Once configured, you can run it via `./vendor/bin/phan`.
|
||||
|
||||
Phan depends on PHP 7.2+ with the [php-ast](https://github.com/nikic/php-ast) extension (1.0.14+ is preferred) and supports analyzing PHP version 7.0-8.1 syntax.
|
||||
Phan 5 depends on PHP 7.2+ with the [php-ast](https://github.com/nikic/php-ast) extension (1.0.14+ is preferred) and supports analyzing PHP version 7.0-8.1 syntax.
|
||||
Installation instructions for php-ast can be found [here](https://github.com/nikic/php-ast#installation).
|
||||
(Phan can be used without php-ast by using the CLI option `--allow-polyfill-parser`, but there are slight differences in the parsing of doc comments)
|
||||
|
||||
|
||||
Vendored
-5
@@ -6,11 +6,6 @@ trigger:
|
||||
- v5
|
||||
|
||||
jobs:
|
||||
- template: .azure/job.yml
|
||||
parameters:
|
||||
configurationName: PHP_72_NTS
|
||||
phpVersion: 7.2
|
||||
vmImage: 'ubuntu-16.04'
|
||||
- template: .azure/job.yml
|
||||
parameters:
|
||||
configurationName: PHP_73_NTS
|
||||
|
||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
||||
"symfony/console": "^3.2|^4.0|^5.0",
|
||||
"symfony/polyfill-mbstring": "^1.11.0",
|
||||
"symfony/polyfill-php80": "^1.20.0",
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2"
|
||||
"tysonandre/var_representation_polyfill": "^0.0.2|^0.1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ast": "Needed for parsing ASTs (unless --use-fallback-parser is used). 1.0.1+ is needed, 1.0.14+ is recommended.",
|
||||
|
||||
+38
-35
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cc0af707fc392b3959172556c6e575f4",
|
||||
"content-hash": "9b0f8da866a879ae2b1c4847a19ea935",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/semver",
|
||||
@@ -616,16 +616,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v5.3.6",
|
||||
"version": "v5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2"
|
||||
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
|
||||
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a",
|
||||
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -695,7 +695,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v5.3.6"
|
||||
"source": "https://github.com/symfony/console/tree/v5.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -711,7 +711,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-27T19:10:22+00:00"
|
||||
"time": "2021-08-25T20:02:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1347,16 +1347,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v5.3.3",
|
||||
"version": "v5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1"
|
||||
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
|
||||
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5",
|
||||
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1410,7 +1410,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v5.3.3"
|
||||
"source": "https://github.com/symfony/string/tree/v5.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1426,20 +1426,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-06-27T11:44:38+00:00"
|
||||
"time": "2021-08-26T08:00:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tysonandre/var_representation_polyfill",
|
||||
"version": "0.0.2",
|
||||
"version": "0.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TysonAndre/var_representation_polyfill.git",
|
||||
"reference": "3f17999ee1f257319ddc6721dd26ebbc5d175f33"
|
||||
"reference": "0a942e74e18af5514749895507bc6ca7ab96399a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/3f17999ee1f257319ddc6721dd26ebbc5d175f33",
|
||||
"reference": "3f17999ee1f257319ddc6721dd26ebbc5d175f33",
|
||||
"url": "https://api.github.com/repos/TysonAndre/var_representation_polyfill/zipball/0a942e74e18af5514749895507bc6ca7ab96399a",
|
||||
"reference": "0a942e74e18af5514749895507bc6ca7ab96399a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1450,6 +1450,9 @@
|
||||
"phan/phan": "^4.0",
|
||||
"phpunit/phpunit": "^8.5.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-var_representation": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -1468,16 +1471,16 @@
|
||||
"name": "Tyson Andre"
|
||||
}
|
||||
],
|
||||
"description": "Polyfill for var_representation",
|
||||
"description": "Polyfill for var_representation: convert a variable to a string in a way that fixes the shortcomings of var_export",
|
||||
"keywords": [
|
||||
"var_export",
|
||||
"var_representation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/TysonAndre/var_representation_polyfill/issues",
|
||||
"source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.0.2"
|
||||
"source": "https://github.com/TysonAndre/var_representation_polyfill/tree/0.1.1"
|
||||
},
|
||||
"time": "2021-06-26T18:55:02+00:00"
|
||||
"time": "2021-08-16T00:12:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
@@ -1779,33 +1782,33 @@
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
|
||||
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
|
||||
"reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
|
||||
"reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.2",
|
||||
"php": "^7.2 || ~8.0, <8.1",
|
||||
"php": "^7.2 || ~8.0, <8.2",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"sebastian/comparator": "^3.0 || ^4.0",
|
||||
"sebastian/recursion-context": "^3.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpspec/phpspec": "^6.0",
|
||||
"phpspec/phpspec": "^6.0 || ^7.0",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.11.x-dev"
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1840,9 +1843,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpspec/prophecy/issues",
|
||||
"source": "https://github.com/phpspec/prophecy/tree/1.13.0"
|
||||
"source": "https://github.com/phpspec/prophecy/tree/1.14.0"
|
||||
},
|
||||
"time": "2021-03-17T13:42:18+00:00"
|
||||
"time": "2021-09-10T09:02:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -2143,16 +2146,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "8.5.19",
|
||||
"version": "8.5.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "496281b64ec781856ed0a583483b5923b4033722"
|
||||
"reference": "9deefba183198398a09b927a6ac6bc1feb0b7b70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/496281b64ec781856ed0a583483b5923b4033722",
|
||||
"reference": "496281b64ec781856ed0a583483b5923b4033722",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9deefba183198398a09b927a6ac6bc1feb0b7b70",
|
||||
"reference": "9deefba183198398a09b927a6ac6bc1feb0b7b70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2224,7 +2227,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.19"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.20"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2236,7 +2239,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-31T15:15:06+00:00"
|
||||
"time": "2021-08-31T06:44:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/code-unit-reverse-lookup",
|
||||
|
||||
Vendored
+2
-8
@@ -395,7 +395,7 @@ class PhanPHPLinterOpts
|
||||
/**
|
||||
* @param string $msg - optional message
|
||||
* @param int $exit_code - process exit code.
|
||||
* @return void - exits with $exit_code
|
||||
* @return never - exits with $exit_code
|
||||
*/
|
||||
public function usage($msg = '', $exit_code = 0)
|
||||
{
|
||||
@@ -537,7 +537,6 @@ EOB;
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
$this->usage('--flycheck-file should be passed a string value', 1);
|
||||
break; // unreachable
|
||||
}
|
||||
$this->temporary_file_map = [$this->file_list[0] => $value];
|
||||
break;
|
||||
@@ -549,7 +548,6 @@ EOB;
|
||||
$mapping = json_decode($value, true);
|
||||
if (!\is_array($mapping)) {
|
||||
$this->usage('--temporary-file-map should be a JSON encoded map from source file to temporary file to analyze instead', 1);
|
||||
break; // unreachable
|
||||
}
|
||||
$this->temporary_file_map = $mapping;
|
||||
|
||||
@@ -574,19 +572,17 @@ EOB;
|
||||
if (!is_string($path)) {
|
||||
$this->print_usage_on_error = $print_usage_on_error;
|
||||
$this->usage(sprintf("Error: asked to analyze path %s which is not a string", json_encode($path) ?: 'invalid'), 1);
|
||||
exit(1);
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
$this->print_usage_on_error = $print_usage_on_error;
|
||||
$this->usage(sprintf("Error: asked to analyze file %s which does not exist", json_encode($path) ?: 'invalid'), 1);
|
||||
exit(1);
|
||||
}
|
||||
$this->file_list[] = $path;
|
||||
break;
|
||||
case 'h':
|
||||
case 'help':
|
||||
// never returns
|
||||
$this->usage();
|
||||
break;
|
||||
case 'd':
|
||||
case 'disable-usage-on-error':
|
||||
$print_usage_on_error = false;
|
||||
@@ -604,7 +600,6 @@ EOB;
|
||||
case 'output-mode':
|
||||
if (!is_string($value) || !in_array($value, ['text', 'json', 'csv', 'codeclimate', 'checkstyle', 'pylint', 'phan_client'], true)) {
|
||||
$this->usage("Expected --output-mode {text,json,csv,codeclimate,checkstyle,pylint}, but got " . json_encode($value), 1);
|
||||
break; // unreachable
|
||||
}
|
||||
if ($value === 'phan_client') {
|
||||
// We're requesting the default
|
||||
@@ -614,7 +609,6 @@ EOB;
|
||||
break;
|
||||
default:
|
||||
$this->usage("Unknown option '-$key'", 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
+13
-14
@@ -579,8 +579,6 @@ class ContextNode
|
||||
return [];
|
||||
}
|
||||
|
||||
// TODO: Should this check that count($class_list) > 0 instead? Or just always check?
|
||||
// TODO: Improve for intersection types
|
||||
if (\count($class_list) === 0) {
|
||||
if (!$union_type->hasTypeMatchingCallback(function (Type $type) use ($expected_type_categories): bool {
|
||||
if ($this->node instanceof Node) {
|
||||
@@ -1778,17 +1776,6 @@ class ContextNode
|
||||
}
|
||||
|
||||
$code_base = $this->code_base;
|
||||
|
||||
$constant_name_lower = \strtolower($constant_name);
|
||||
if ($constant_name_lower === 'true' || $constant_name_lower === 'false' || $constant_name_lower === 'null') {
|
||||
return $code_base->getGlobalConstantByFQSEN(
|
||||
// @phan-suppress-next-line PhanThrowTypeMismatchForCall
|
||||
FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
|
||||
$constant_name_lower
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$context = $this->context;
|
||||
$flags = $node->children['name']->flags ?? 0;
|
||||
try {
|
||||
@@ -1834,6 +1821,18 @@ class ContextNode
|
||||
} catch (FQSENException $e) {
|
||||
throw new AssertionError("Impossible FQSENException: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Handle true/false/null, even for edge cases such as `use const null as somethingelse;`
|
||||
$fqsen_lower = \strtolower($fqsen->__toString());
|
||||
if (\in_array($fqsen_lower, ['\true', '\false', '\null'], true)) {
|
||||
return $code_base->getGlobalConstantByFQSEN(
|
||||
// @phan-suppress-next-line PhanThrowTypeMismatchForCall
|
||||
FullyQualifiedGlobalConstantName::fromFullyQualifiedString(
|
||||
$fqsen_lower
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// This is either a fully qualified constant,
|
||||
// or a relative constant for which nothing was found in the namespace
|
||||
|
||||
@@ -1884,7 +1883,7 @@ class ContextNode
|
||||
|
||||
/**
|
||||
* @throws IssueException
|
||||
* @return no-return
|
||||
* @return never
|
||||
*/
|
||||
private function throwUndeclaredGlobalConstantIssueException(CodeBase $code_base, Context $context, FullyQualifiedGlobalConstantName $fqsen): void
|
||||
{
|
||||
|
||||
@@ -57,6 +57,10 @@ class InferPureSnippetVisitor extends InferPureVisitor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitReturn(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
@@ -79,17 +83,29 @@ class InferPureSnippetVisitor extends InferPureVisitor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitYield(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitYieldFrom(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
// TODO(optional) track actual goto labels
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitGoto(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
@@ -97,6 +113,10 @@ class InferPureSnippetVisitor extends InferPureVisitor
|
||||
|
||||
// NOTE: Checks of assignment, increment or decrement are deferred to --unused-variable-detection
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitUnset(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
|
||||
+5
-1
@@ -105,6 +105,10 @@ class InferPureVisitor extends AnalysisVisitor
|
||||
// visitEcho
|
||||
// visitPrint
|
||||
// visitIncludeOrExec
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visit(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
@@ -681,7 +685,7 @@ class InferPureVisitor extends AnalysisVisitor
|
||||
public function visitClosure(Node $node): void
|
||||
{
|
||||
$closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext(
|
||||
(clone($this->context))->withLineNumberStart($node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->lineno),
|
||||
$node
|
||||
);
|
||||
if (!$this->code_base->hasFunctionWithFQSEN($closure_fqsen)) {
|
||||
|
||||
@@ -67,32 +67,55 @@ class ScopeImpactCheckingVisitor extends InferPureVisitor
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitContinue(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitBreak(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitPreInc(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitPreDec(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitPostInc(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitPostDec(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
@@ -110,25 +133,37 @@ class ScopeImpactCheckingVisitor extends InferPureVisitor
|
||||
$this->visitVar($var);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitGoto(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitAssignOp(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitAssign(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
/**
|
||||
* @override
|
||||
* @return never
|
||||
*/
|
||||
public function visitReturn(Node $node): void
|
||||
{
|
||||
throw new NodeException($node);
|
||||
|
||||
+50
-22
@@ -65,7 +65,18 @@ Shim::load();
|
||||
* each time they are invoked,
|
||||
* so it's possible to have multiple callers use this without affecting each other.
|
||||
*
|
||||
* Compatibility: PHP 7.0-7.4
|
||||
* Compatibility: PHP 7.0-8.1
|
||||
*
|
||||
* XXX: This aims to match the line numbers that php-ast would generate (for compatibility) where reasonable,
|
||||
* even when counterintuitive. See https://github.com/phan/phan/issues/4520
|
||||
*
|
||||
* - The way php (and as a result php-ast) is getting the line number for anything
|
||||
* that has 1 or more children is to use the line number of the first non-null child.
|
||||
* (skipping most tokens such as `[`, `return`, etc, and having a line number for literal
|
||||
* values (php-ast does not wrap the AST_ZVAL type php uses internally in a Node)
|
||||
* - If there are no non-null child nodes, then php uses the current line number of the lexer.
|
||||
*
|
||||
* which are the line numbers php uses in
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
@@ -114,7 +125,7 @@ class TolerantASTConverter
|
||||
public const AST_VERSION = 85;
|
||||
|
||||
// The versions that this supports
|
||||
public const SUPPORTED_AST_VERSIONS = [self::AST_VERSION];
|
||||
public const SUPPORTED_AST_VERSIONS = [80, self::AST_VERSION];
|
||||
|
||||
private const _IGNORED_STRING_TOKEN_KIND_SET = [
|
||||
TokenKind::OpenBraceDollarToken => true,
|
||||
@@ -449,7 +460,7 @@ class TolerantASTConverter
|
||||
}
|
||||
if (!\is_int($lineno)) {
|
||||
foreach ($parser_nodes as $parser_node) {
|
||||
$child_node_line = static::getEndLine($parser_node);
|
||||
$child_node_line = static::getStartLine($parser_node);
|
||||
if ($child_node_line > 0) {
|
||||
$lineno = $child_node_line;
|
||||
break;
|
||||
@@ -871,12 +882,14 @@ class TolerantASTConverter
|
||||
return $str;
|
||||
},
|
||||
/**
|
||||
* @return never
|
||||
* @throws InvalidNodeException
|
||||
*/
|
||||
'Microsoft\PhpParser\MissingToken' => static function (PhpParser\MissingToken $unused_node, int $_): void {
|
||||
throw new InvalidNodeException();
|
||||
},
|
||||
/**
|
||||
* @return never
|
||||
* @throws InvalidNodeException
|
||||
*/
|
||||
'Microsoft\PhpParser\SkippedToken' => static function (PhpParser\SkippedToken $unused_node, int $_): void {
|
||||
@@ -1003,11 +1016,12 @@ class TolerantASTConverter
|
||||
return new ast\Node($type, 0, ['var' => static::phpParserNodeToAstNode($n->operand)], $start_line);
|
||||
},
|
||||
'Microsoft\PhpParser\Node\Expression\PrintIntrinsicExpression' => static function (PhpParser\Node\Expression\PrintIntrinsicExpression $n, int $start_line): ast\Node {
|
||||
$expr_node = static::phpParserNodeToAstNode($n->expression);
|
||||
return new ast\Node(
|
||||
ast\AST_PRINT,
|
||||
0,
|
||||
['expr' => static::phpParserNodeToAstNode($n->expression)],
|
||||
$start_line
|
||||
['expr' => $expr_node],
|
||||
$expr_node->lineno ?? (self::getStartLine($n->expression) ?: $start_line)
|
||||
);
|
||||
},
|
||||
/** @return ?ast\Node */
|
||||
@@ -1076,14 +1090,17 @@ class TolerantASTConverter
|
||||
$ast_expr = ($element_value !== null && !($element_value instanceof MissingToken)) ? static::phpParserNodeToAstNode($array_element->elementValue) : null;
|
||||
if ($kind === \ast\AST_YIELD) {
|
||||
$element_key = $array_element->elementKey ?? null;
|
||||
$key = $element_key !== null ? static::phpParserNodeToAstNode($element_key) : null;
|
||||
$children = [
|
||||
'value' => $ast_expr,
|
||||
'key' => $element_key !== null ? static::phpParserNodeToAstNode($element_key) : null,
|
||||
'key' => $key,
|
||||
];
|
||||
$start_line = $key->lineno ?? $ast_expr->lineno ?? $start_line;
|
||||
} else {
|
||||
$children = [
|
||||
'expr' => $ast_expr,
|
||||
];
|
||||
$start_line = $ast_expr->lineno ?? $start_line;
|
||||
}
|
||||
return new ast\Node(
|
||||
$kind,
|
||||
@@ -1233,6 +1250,7 @@ class TolerantASTConverter
|
||||
$kind = $n->breakOrContinueKeyword->kind === TokenKind::ContinueKeyword ? ast\AST_CONTINUE : ast\AST_BREAK;
|
||||
$breakout_level = $n->breakoutLevel;
|
||||
if ($breakout_level !== null) {
|
||||
$start_line = self::getStartLine($breakout_level);
|
||||
$breakout_level = static::phpParserNodeToAstNode($breakout_level);
|
||||
if (!\is_int($breakout_level)) {
|
||||
$breakout_level = null;
|
||||
@@ -1254,8 +1272,8 @@ class TolerantASTConverter
|
||||
$catch_list_node,
|
||||
$variableName !== null ? static::variableTokenToString($variableName) : null,
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable return_null_on_empty is false.
|
||||
static::phpParserStmtlistToAstNode($n->compoundStatement, $start_line, false),
|
||||
$start_line
|
||||
static::phpParserStmtlistToAstNode($n->compoundStatement, self::getStartLine($n->compoundStatement) ?: $start_line, false),
|
||||
$variableName !== null ? self::getStartLine($variableName) : $start_line
|
||||
);
|
||||
},
|
||||
'Microsoft\PhpParser\Node\Statement\InterfaceDeclaration' => static function (PhpParser\Node\Statement\InterfaceDeclaration $n, int $start_line): ast\Node {
|
||||
@@ -1421,10 +1439,12 @@ class TolerantASTConverter
|
||||
if ($expr instanceof Token && $expr->kind === TokenKind::CommaToken) {
|
||||
continue;
|
||||
}
|
||||
$expr_node = static::phpParserNodeToAstNode($expr);
|
||||
$start_line = ($expr_node->lineno ?? self::getStartLine($expr)) ?: $start_line;
|
||||
$ast_echos[] = new ast\Node(
|
||||
ast\AST_ECHO,
|
||||
0,
|
||||
['expr' => static::phpParserNodeToAstNode($expr)],
|
||||
['expr' => $expr_node],
|
||||
$start_line
|
||||
);
|
||||
}
|
||||
@@ -1525,7 +1545,7 @@ class TolerantASTConverter
|
||||
ast\AST_ECHO,
|
||||
0,
|
||||
['expr' => static::tokenToRawString($n->text)],
|
||||
$start_line
|
||||
self::getStartLine($n->text) ?: $start_line
|
||||
);
|
||||
},
|
||||
/** @suppress PhanTypeMismatchArgument TODO: Make ForStatement have more accurate docs? */
|
||||
@@ -1586,7 +1606,7 @@ class TolerantASTConverter
|
||||
'Microsoft\PhpParser\Node\Statement\ReturnStatement' => static function (PhpParser\Node\Statement\ReturnStatement $n, int $start_line): ast\Node {
|
||||
$e = $n->expression;
|
||||
$expr_node = $e !== null ? static::phpParserNodeToAstNode($e) : null;
|
||||
return new ast\Node(ast\AST_RETURN, 0, ['expr' => $expr_node], $start_line);
|
||||
return new ast\Node(ast\AST_RETURN, 0, ['expr' => $expr_node], $expr_node->lineno ?? $start_line);
|
||||
},
|
||||
/** @return ast\Node|ast\Node[] */
|
||||
'Microsoft\PhpParser\Node\Statement\FunctionStaticDeclaration' => static function (PhpParser\Node\Statement\FunctionStaticDeclaration $n, int $start_line) {
|
||||
@@ -1712,8 +1732,8 @@ class TolerantASTConverter
|
||||
return static::astNodeTry(
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable return_null_on_empty is false.
|
||||
static::phpParserStmtlistToAstNode($n->compoundStatement, $start_line, false), // $n->try
|
||||
static::phpParserCatchlistToAstCatchlist($n->catchClauses ?? [], $start_line),
|
||||
$finally_clause !== null ? static::phpParserStmtlistToAstNode($finally_clause->compoundStatement, self::getStartLine($finally_clause), false) : null,
|
||||
static::phpParserCatchlistToAstCatchlist($n->catchClauses ?? [], self::getEndLine($n->compoundStatement) ?: $start_line),
|
||||
$finally_clause !== null ? static::phpParserStmtlistToAstNode($finally_clause->compoundStatement, self::getStartLine($finally_clause->compoundStatement), false) : null,
|
||||
$start_line
|
||||
);
|
||||
},
|
||||
@@ -1824,7 +1844,7 @@ class TolerantASTConverter
|
||||
foreach ($catches as $parser_catch) {
|
||||
$children[] = static::phpParserNonValueNodeToAstNode($parser_catch);
|
||||
}
|
||||
return new ast\Node(ast\AST_CATCH_LIST, 0, $children, $children[0]->lineno ?? $lineno);
|
||||
return new ast\Node(ast\AST_CATCH_LIST, 0, $children, $lineno);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2405,11 +2425,12 @@ class TolerantASTConverter
|
||||
if (!$expression) {
|
||||
throw new InvalidNodeException();
|
||||
}
|
||||
$expr_node = static::phpParserNodeToAstNode($expression);
|
||||
return new ast\Node(
|
||||
ast\AST_THROW,
|
||||
0,
|
||||
['expr' => static::phpParserNodeToAstNode($expression)],
|
||||
$start_line
|
||||
['expr' => $expr_node],
|
||||
$expr_node->lineno ?? $start_line
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2630,21 +2651,23 @@ class TolerantASTConverter
|
||||
|
||||
private static function phpParserIfStmtToAstIfStmt(PhpParser\Node\Statement\IfStatementNode $node, int $start_line): ast\Node
|
||||
{
|
||||
$if_elem_expr = static::phpParserNodeToAstNode($node->expression);
|
||||
$if_elem = static::astIfElem(
|
||||
static::phpParserNodeToAstNode($node->expression),
|
||||
$if_elem_expr,
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable return_null_on_empty is false.
|
||||
static::phpParserStmtlistToAstNode(
|
||||
$node->statements,
|
||||
self::getStartLineOfStatementOrStatements($node->statements) ?: $start_line,
|
||||
false
|
||||
),
|
||||
$start_line
|
||||
$if_elem_expr->lineno ?? (self::getStartLine($node->expression) ?: $start_line)
|
||||
);
|
||||
$if_elems = [$if_elem];
|
||||
foreach ($node->elseIfClauses as $else_if) {
|
||||
$if_elem_line = self::getStartLine($else_if);
|
||||
$else_if_node = static::phpParserNodeToAstNode($else_if->expression);
|
||||
$if_elem_line = $else_if_node->lineno ?? (self::getStartLine($else_if->expression) ?: $start_line);
|
||||
$if_elem = static::astIfElem(
|
||||
static::phpParserNodeToAstNode($else_if->expression),
|
||||
$else_if_node,
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable return_null_on_empty is false.
|
||||
static::phpParserStmtlistToAstNode(
|
||||
$else_if->statements,
|
||||
@@ -2664,7 +2687,7 @@ class TolerantASTConverter
|
||||
$parser_else_line
|
||||
);
|
||||
}
|
||||
return new ast\Node(ast\AST_IF, 0, $if_elems, $start_line);
|
||||
return new ast\Node(ast\AST_IF, 0, $if_elems, $if_elems[0]->lineno);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2843,8 +2866,13 @@ class TolerantASTConverter
|
||||
$line = $prop_elems[0]->lineno ?? (self::getStartLine($n) ?: $start_line);
|
||||
$prop_decl = new ast\Node(ast\AST_PROP_DECL, 0, $prop_elems, $line);
|
||||
$type_line = static::getEndLine($n->typeDeclarationList) ?: $start_line;
|
||||
|
||||
$type = static::phpParserUnionTypeToAstNode($n->typeDeclarationList, $type_line);
|
||||
if ($n->questionToken !== null && $type !== null) {
|
||||
$type = new ast\Node(ast\AST_NULLABLE_TYPE, 0, ['type' => $type], $start_line);
|
||||
}
|
||||
return new ast\Node(ast\AST_PROP_GROUP, $flags, [
|
||||
'type' => static::phpParserUnionTypeToAstNode($n->typeDeclarationList, $type_line),
|
||||
'type' => $type,
|
||||
'props' => $prop_decl,
|
||||
'attributes' => static::phpParserAttributeGroupsToAstAttributeList($n->attributes),
|
||||
], $line);
|
||||
|
||||
+35
-30
@@ -412,42 +412,47 @@ class Analysis
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($plugin_set->getAnalyzeFunctionCallClosures($code_base) as $fqsen_string => $closure) {
|
||||
try {
|
||||
if (\strpos($fqsen_string, '::') !== false) {
|
||||
// This is an override of a method.
|
||||
[$class, $method_name] = \explode('::', $fqsen_string, 2);
|
||||
$class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class);
|
||||
if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
|
||||
continue;
|
||||
}
|
||||
$class = $code_base->getClassByFQSEN($class_fqsen);
|
||||
// Note: This is used because it will create methods such as __construct if they do not exist.
|
||||
if ($class->hasMethodWithName($code_base, $method_name, false)) {
|
||||
$method = $class->getMethodByName($code_base, $method_name);
|
||||
$method->addFunctionCallAnalyzer($closure);
|
||||
|
||||
$methods_by_defining_fqsen = $methods_by_defining_fqsen ?? $code_base->getMethodsMapGroupedByDefiningFQSEN();
|
||||
$fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
|
||||
if (!$methods_by_defining_fqsen->offsetExists($fqsen)) {
|
||||
// To fix issues such as https://github.com/phan/phan/issues/4090, this (1) always calls addFunctionCallAnalyzer,
|
||||
// and (2) automatically loads definitions for functions and methods by calling hasFunctionWithFQSEN (etc.), so that internal function definitions used by previous plugins can be iterated over by the next plugin being called (e.g. if getFunctionSet is called).
|
||||
// @phan-suppress-next-line PhanAccessMethodInternal
|
||||
foreach ($plugin_set->getAnalyzeFunctionCallPluginSet() as $plugin) {
|
||||
foreach ($plugin->getAnalyzeFunctionCallClosures($code_base) as $fqsen_string => $closure) {
|
||||
try {
|
||||
if (\strpos($fqsen_string, '::') !== false) {
|
||||
// This is an override of a method.
|
||||
[$class, $method_name] = \explode('::', $fqsen_string, 2);
|
||||
$class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class);
|
||||
if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
|
||||
continue;
|
||||
}
|
||||
$class = $code_base->getClassByFQSEN($class_fqsen);
|
||||
// Note: This is used because it will create methods such as __construct if they do not exist.
|
||||
if ($class->hasMethodWithName($code_base, $method_name, false)) {
|
||||
$method = $class->getMethodByName($code_base, $method_name);
|
||||
$method->addFunctionCallAnalyzer($closure, $plugin);
|
||||
|
||||
foreach ($methods_by_defining_fqsen->offsetGet($fqsen) as $child_method) {
|
||||
$child_method->addFunctionCallAnalyzer($closure);
|
||||
$methods_by_defining_fqsen = $methods_by_defining_fqsen ?? $code_base->getMethodsMapGroupedByDefiningFQSEN();
|
||||
$fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
|
||||
if (!$methods_by_defining_fqsen->offsetExists($fqsen)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($methods_by_defining_fqsen->offsetGet($fqsen) as $child_method) {
|
||||
$child_method->addFunctionCallAnalyzer($closure, $plugin);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a function.
|
||||
$fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
|
||||
if ($code_base->hasFunctionWithFQSEN($fqsen)) {
|
||||
$function = $code_base->getFunctionByFQSEN($fqsen);
|
||||
$function->addFunctionCallAnalyzer($closure, $plugin);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is an override of a function.
|
||||
$fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
|
||||
if ($code_base->hasFunctionWithFQSEN($fqsen)) {
|
||||
$function = $code_base->getFunctionByFQSEN($fqsen);
|
||||
$function->setFunctionCallAnalyzer($closure);
|
||||
}
|
||||
} catch (FQSENException | InvalidArgumentException $e) {
|
||||
// @phan-suppress-next-line PhanPluginRemoveDebugCall
|
||||
\fprintf(STDERR, "getAnalyzeFunctionCallClosures returned an invalid FQSEN %s: %s\n", $fqsen_string, $e->getMessage());
|
||||
}
|
||||
} catch (FQSENException | InvalidArgumentException $e) {
|
||||
// @phan-suppress-next-line PhanPluginRemoveDebugCall
|
||||
\fprintf(STDERR, "getAnalyzeFunctionCallClosures returned an invalid FQSEN %s: %s\n", $fqsen_string, $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ class AssignOperatorAnalysisVisitor extends FlagVisitorImplementation
|
||||
|
||||
// If both left and right are arrays, then this is array
|
||||
// concatenation.
|
||||
if ($left->isGenericArray() && $right->isGenericArray()) {
|
||||
if ($left->isArray() && $right->isArray()) {
|
||||
BinaryOperatorFlagVisitor::checkInvalidArrayShapeCombination($code_base, $context, $node, $left, $right);
|
||||
if ($left->isEqualTo($right)) {
|
||||
return $left;
|
||||
|
||||
@@ -283,7 +283,7 @@ class AssignOperatorFlagVisitor extends FlagVisitorImplementation
|
||||
|
||||
// If both left and right are arrays, then this is array
|
||||
// concatenation.
|
||||
if ($left->isGenericArray() && $right->isGenericArray()) {
|
||||
if ($left->isArray() && $right->isArray()) {
|
||||
if ($left->isEqualTo($right)) {
|
||||
return $left;
|
||||
}
|
||||
|
||||
@@ -89,10 +89,11 @@ final class BinaryOperatorFlagVisitor extends FlagVisitorImplementation
|
||||
/**
|
||||
* @throws AssertionError
|
||||
* @suppress PhanUnreferencedPrivateMethod this is referenced by __invoke
|
||||
* @return never
|
||||
*/
|
||||
private function handleMissing(Node $node): void
|
||||
{
|
||||
throw new AssertionError("All flags must match. Found kind=" . Debug::nodeName($node) . ', flags=' . Element::flagDescription($node) . ' raw flags=' . $node->flags . ' at ' . $this->context->withLineNumberStart((int)$node->lineno));
|
||||
throw new AssertionError("All flags must match. Found kind=" . Debug::nodeName($node) . ', flags=' . Element::flagDescription($node) . ' raw flags=' . $node->flags . ' at ' . (clone $this->context)->withLineNumberStart((int)$node->lineno));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +752,7 @@ final class BinaryOperatorFlagVisitor extends FlagVisitorImplementation
|
||||
|
||||
// If both left and right union types are arrays, then this is array
|
||||
// concatenation. (`$left + $right`)
|
||||
if ($left->isGenericArray() && $right->isGenericArray()) {
|
||||
if ($left->isArray() && $right->isArray()) {
|
||||
self::checkInvalidArrayShapeCombination($code_base, $context, $node, $left, $right);
|
||||
if ($left->isEqualTo($right)) {
|
||||
return $left;
|
||||
|
||||
@@ -44,11 +44,17 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
public const STATUS_CONTINUE = (1 << 22); // At least one branch leads to a continue statement
|
||||
public const STATUS_BREAK = (1 << 23); // At least one branch leads to a break statement
|
||||
public const STATUS_THROW = (1 << 24); // At least one branch leads to a throw statement
|
||||
public const STATUS_RETURN = (1 << 25); // At least one branch leads to a return/exit() statement (or an infinite loop)
|
||||
public const STATUS_RETURN = (1 << 25); // At least one branch leads to a return statement
|
||||
public const STATUS_NORETURN = (1 << 26); // At least one branch leads to a exit() statement (or an infinite loop)
|
||||
|
||||
public const STATUS_THROW_OR_RETURN_BITMASK =
|
||||
self::STATUS_THROW |
|
||||
self::STATUS_RETURN;
|
||||
self::STATUS_RETURN |
|
||||
self::STATUS_NORETURN;
|
||||
|
||||
public const STATUS_NOT_RETURN_BITMASK =
|
||||
self::STATUS_THROW |
|
||||
self::STATUS_NORETURN;
|
||||
|
||||
// Any status which doesn't lead to proceeding.
|
||||
public const STATUS_NOT_PROCEED_BITMASK =
|
||||
@@ -56,7 +62,8 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
self::STATUS_CONTINUE |
|
||||
self::STATUS_BREAK |
|
||||
self::STATUS_THROW |
|
||||
self::STATUS_RETURN;
|
||||
self::STATUS_RETURN |
|
||||
self::STATUS_NORETURN;
|
||||
|
||||
public const STATUS_BITMASK =
|
||||
self::STATUS_PROCEED |
|
||||
@@ -475,7 +482,7 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
{
|
||||
$status = $inner_status & ~self::UNEXITABLE_LOOP_INNER_STATUS;
|
||||
if ($status === 0) {
|
||||
return self::STATUS_RETURN; // this is an infinite loop, it didn't contain break/throw/return statements?
|
||||
return self::STATUS_NORETURN; // this is an infinite loop, it didn't contain break/throw/return statements?
|
||||
}
|
||||
if (($status & self::STATUS_BREAK) !== 0) {
|
||||
// if the inside of "while (true) {} contains a break statement,
|
||||
@@ -500,7 +507,7 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
*/
|
||||
public function visitExit(Node $node): int
|
||||
{
|
||||
return self::STATUS_RETURN;
|
||||
return self::STATUS_NORETURN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -628,10 +635,14 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
return self::STATUS_PROCEED;
|
||||
}
|
||||
// The returned code for exit() is 'return', e.g. E_USER_ERROR makes trigger_error emit an error then abort execution.
|
||||
if (\in_array($name, ['E_ERROR', 'E_PARSE', 'E_CORE_ERROR', 'E_COMPILE_ERROR', 'E_USER_ERROR'], true)) {
|
||||
return self::STATUS_RETURN;
|
||||
// NOTE: Native errors either emit a notice about being invalid error types to pass to this function (e.g. E_ERROR).
|
||||
// must be one of E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE, or E_USER_DEPRECATED
|
||||
if ($name === 'E_USER_ERROR') {
|
||||
// Fatal error
|
||||
return self::STATUS_NORETURN;
|
||||
}
|
||||
if ($name === 'E_RECOVERABLE_ERROR') {
|
||||
if (!\in_array($name, ['E_USER_WARNING', 'E_USER_NOTICE', 'E_USER_DEPRECATED'], true)) {
|
||||
// Newer php versions throw a ValueError for invalid types.
|
||||
return self::STATUS_THROW;
|
||||
}
|
||||
|
||||
@@ -804,6 +815,14 @@ final class BlockExitStatusChecker extends KindVisitorImplementation
|
||||
return ((new self())->__invoke($node) & ~self::STATUS_THROW_OR_RETURN_BITMASK) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will the node $node unconditionally throw or exit (or infinitely loop)
|
||||
*/
|
||||
public static function willUnconditionallyNeverReturn(Node $node): bool
|
||||
{
|
||||
return ((new self())->__invoke($node) & ~self::STATUS_NOT_RETURN_BITMASK) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will the node $node unconditionally proceed (no break/continue, throw, or goto)
|
||||
*/
|
||||
|
||||
@@ -87,7 +87,8 @@ class CompositionAnalyzer
|
||||
} catch (IssueException $_) {
|
||||
$inherited_property_union_type = UnionType::empty();
|
||||
}
|
||||
if (!$property->isDynamicOrFromPHPDoc()) {
|
||||
// Don't complain about incompatible types if the base property is private, #4426
|
||||
if (!$property->isDynamicOrFromPHPDoc() && !$inherited_property->isPrivate()) {
|
||||
$real_property_type = $property->getRealUnionType()->asNormalizedTypes();
|
||||
$real_inherited_property_type = $inherited_property->getRealUnionType()->asNormalizedTypes();
|
||||
if (!$real_property_type->isEqualTo($real_inherited_property_type)) {
|
||||
|
||||
+51
-1
@@ -879,7 +879,7 @@ class ConditionVisitor extends KindVisitorImplementation implements ConditionVis
|
||||
$method = new ReflectionMethod(UnionType::class, $extract_types);
|
||||
/**
|
||||
* @param list<Node|mixed> $args
|
||||
* @suppress PhanPluginUnknownObjectMethodCall can't analye ReflectionMethod
|
||||
* @suppress PhanPluginUnknownObjectMethodCall can't analyze ReflectionMethod
|
||||
*/
|
||||
return static function (CodeBase $code_base, Context $context, Variable $variable, array $args) use ($method, $default_if_empty, $allow_undefined): void {
|
||||
// Change the type to match the is_a relationship
|
||||
@@ -1052,6 +1052,9 @@ class ConditionVisitor extends KindVisitorImplementation implements ConditionVis
|
||||
case 'array_key_exists':
|
||||
// @phan-suppress-next-line PhanPartialTypeMismatchArgument
|
||||
return $this->analyzeArrayKeyExists($args);
|
||||
case 'in_array':
|
||||
// @phan-suppress-next-line PhanPartialTypeMismatchArgument
|
||||
return $this->analyzeInArray($args);
|
||||
case 'defined':
|
||||
// @phan-suppress-next-line PhanPartialTypeMismatchArgument
|
||||
return $this->analyzeDefined($args);
|
||||
@@ -1148,6 +1151,53 @@ class ConditionVisitor extends KindVisitorImplementation implements ConditionVis
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Node|string|int|float> $args
|
||||
*/
|
||||
private function analyzeInArray(array $args): Context
|
||||
{
|
||||
$context = $this->context;
|
||||
if (\count($args) < 2) {
|
||||
return $context;
|
||||
}
|
||||
// in_array($var, $array, [bool]) asserts that $var is a type found in the elements of $array
|
||||
// It also asserts $array is not empty
|
||||
$array_node = $args[1];
|
||||
if (!$array_node instanceof Node) {
|
||||
return $context;
|
||||
}
|
||||
if (\in_array($array_node->kind, [ast\AST_VAR, ast\AST_PROP, ast\AST_DIM], true)) {
|
||||
// in_array implies the array has at least one element, implying it is not falsey.
|
||||
$context = $this->updateVariableWithConditionalFilter(
|
||||
$array_node,
|
||||
$context,
|
||||
static function (UnionType $_): bool {
|
||||
return true;
|
||||
},
|
||||
static function (UnionType $type): UnionType {
|
||||
return $type->arrayTypesStrictCast()->nonFalseyClone();
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
$var_node = $args[0];
|
||||
if (!($var_node instanceof Node)) {
|
||||
return $context;
|
||||
}
|
||||
$array_value = UnionTypeVisitor::unionTypeFromNode($this->code_base, $this->context, $array_node)->arrayTypesStrictCastAllowEmpty();
|
||||
if ($array_value->isEmptyOrMixed()) {
|
||||
return $context;
|
||||
}
|
||||
$element_type = $array_value->genericArrayElementTypes(true, $this->code_base);
|
||||
if ($element_type->isEmptyOrMixed()) {
|
||||
return $context;
|
||||
}
|
||||
$is_strict = isset($args[2]) && UnionTypeVisitor::checkCondUnconditionalTruthiness($args[2]) === true;
|
||||
return $this->updateVariableWithNewType($var_node, $context, $element_type, true, $is_strict);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Node|string|int|float> $args
|
||||
*/
|
||||
|
||||
@@ -44,4 +44,9 @@ interface BinaryCondition
|
||||
* @param Node|string|int|float $expr a node with a constant value
|
||||
*/
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr): ?Context;
|
||||
|
||||
/**
|
||||
* @return static the same operation but to analyze flipped operand orders
|
||||
*/
|
||||
public function withFlippedOperands(): self;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Phan\Analysis\ConditionVisitor;
|
||||
|
||||
use ast;
|
||||
use ast\Node;
|
||||
use Phan\Analysis\ConditionVisitorInterface;
|
||||
use Phan\Analysis\ConditionVisitor;
|
||||
use Phan\Analysis\NegatedConditionVisitor;
|
||||
use Phan\AST\UnionTypeVisitor;
|
||||
use Phan\Language\Context;
|
||||
|
||||
/**
|
||||
@@ -50,9 +54,49 @@ class ComparisonCondition implements BinaryCondition
|
||||
*/
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr): ?Context
|
||||
{
|
||||
$function_name = ConditionVisitor::getFunctionName($call_node);
|
||||
if (\is_string($function_name) && \strcasecmp($function_name, 'count') === 0) {
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$value = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr)->asSingleScalarValueOrNullOrSelf();
|
||||
if (\is_object($value) || $value < 0) {
|
||||
return null;
|
||||
}
|
||||
if ($this->assertsPositiveNumber($value)) {
|
||||
// e.g. `if (is_string($x) === true)`
|
||||
return (new ConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
} elseif ($this->assertsZeroOrLess($value)) {
|
||||
return (new NegatedConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|int|float|string|null $value
|
||||
*/
|
||||
private function assertsPositiveNumber($value): bool {
|
||||
if ($this->flags === ast\flags\BINARY_IS_GREATER) {
|
||||
return $value > 0;
|
||||
} elseif ($this->flags === ast\flags\BINARY_IS_GREATER_OR_EQUAL) {
|
||||
return $value >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|int|float|string|null $value
|
||||
*/
|
||||
private function assertsZeroOrLess($value): bool {
|
||||
if ($this->flags === ast\flags\BINARY_IS_SMALLER) {
|
||||
return $value > 0 && $value <= 1;
|
||||
} elseif ($this->flags === ast\flags\BINARY_IS_SMALLER_OR_EQUAL) {
|
||||
// @phan-suppress-next-line PhanPluginComparisonNotStrictForScalar, PhanSuspiciousTruthyString
|
||||
return $value == 0 && $value <= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @suppress PhanUnusedPublicMethodParameter
|
||||
*/
|
||||
@@ -60,4 +104,20 @@ class ComparisonCondition implements BinaryCondition
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private const FLIP_OPERANDS = [
|
||||
ast\flags\BINARY_IS_GREATER => ast\flags\BINARY_IS_SMALLER,
|
||||
ast\flags\BINARY_IS_GREATER_OR_EQUAL => ast\flags\BINARY_IS_SMALLER_OR_EQUAL,
|
||||
ast\flags\BINARY_IS_SMALLER => ast\flags\BINARY_IS_GREATER,
|
||||
ast\flags\BINARY_IS_SMALLER_OR_EQUAL => ast\flags\BINARY_IS_GREATER_OR_EQUAL,
|
||||
];
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
$flipped = self::FLIP_OPERANDS[$this->flags] ?? null;
|
||||
return isset($flipped) ? new self($flipped) : $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class EqualsCondition implements BinaryCondition
|
||||
return $visitor->analyzeClassAssertion($object, $expr) ?? $visitor->getContext();
|
||||
}
|
||||
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr): ?Context
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr, bool $negate = false): ?Context
|
||||
{
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
@@ -48,20 +48,27 @@ class EqualsCondition implements BinaryCondition
|
||||
return null;
|
||||
}
|
||||
// Skip check for `if is_bool`, allow weaker comparisons such as `is_string($x) == 1`
|
||||
if (!$expr_type->isExclusivelyBoolTypes() && !UnionTypeVisitor::unionTypeFromNode($code_base, $context, $call_node)->isExclusivelyBoolTypes()) {
|
||||
$function_name = ConditionVisitor::getFunctionName($call_node);
|
||||
if (\is_string($function_name) && \strcasecmp($function_name, 'count') === 0) {
|
||||
if ($negate && $expr_type->containsTruthy()) {
|
||||
// Currently can't infer anything from `if (count($x) != 2)`
|
||||
return null;
|
||||
}
|
||||
// Fall through
|
||||
} elseif (!$expr_type->isExclusivelyBoolTypes() && !UnionTypeVisitor::unionTypeFromNode($code_base, $context, $call_node)->isExclusivelyBoolTypes()) {
|
||||
return null;
|
||||
}
|
||||
if (!$expr_type->containsFalsey()) {
|
||||
// e.g. `if (is_string($x) === true)`
|
||||
if ($negate ? !$expr_type->containsTruthy() : !$expr_type->containsFalsey()) {
|
||||
// e.g. `if (is_string($x) == true)`, or negated equals check such as `if (is_string($x) != false)`
|
||||
return (new ConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
} elseif (!$expr_type->containsTruthy()) {
|
||||
// e.g. `if (is_string($x) === false)`
|
||||
} elseif ($negate ? !$expr_type->containsFalsey() : !$expr_type->containsTruthy()) {
|
||||
// e.g. `if (is_string($x) == false)`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr): ?Context
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr, bool $negate = false): ?Context
|
||||
{
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
@@ -69,13 +76,19 @@ class EqualsCondition implements BinaryCondition
|
||||
if (!$expr_type->isExclusivelyBoolTypes() && !UnionTypeVisitor::unionTypeFromNode($code_base, $context, $complex_node)->isExclusivelyBoolTypes()) {
|
||||
return null;
|
||||
}
|
||||
if (!$expr_type->containsFalsey()) {
|
||||
if ($negate ? !$expr_type->containsTruthy() : !$expr_type->containsFalsey()) {
|
||||
// e.g. `if (($x instanceof Xyz) == true)`
|
||||
return (new ConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
} elseif (!$expr_type->containsTruthy()) {
|
||||
} elseif ($negate ? !$expr_type->containsFalsey() : !$expr_type->containsTruthy()) {
|
||||
// e.g. `if (($x instanceof Xyz) == false)`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,4 +87,10 @@ class HasTypeCondition implements BinaryCondition
|
||||
// TODO: Could analyze get_class($array['field']) === stdClass::class (e.g. with AssignmentVisitor)
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-9
@@ -10,6 +10,7 @@ use Phan\Analysis\ConditionVisitorInterface;
|
||||
use Phan\Analysis\NegatedConditionVisitor;
|
||||
use Phan\AST\UnionTypeVisitor;
|
||||
use Phan\Language\Context;
|
||||
use Phan\Language\UnionType;
|
||||
|
||||
/**
|
||||
* This represents an identical assertion implementation acting on two sides of a condition (===)
|
||||
@@ -39,19 +40,27 @@ class IdenticalCondition implements BinaryCondition
|
||||
return $visitor->analyzeClassAssertion($object, $expr) ?? $visitor->getContext();
|
||||
}
|
||||
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr): ?Context
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr, bool $negate = false): ?Context
|
||||
{
|
||||
if (!$expr instanceof Node) {
|
||||
// Cannot be false/true.
|
||||
return null;
|
||||
}
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$value = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr)->asSingleScalarValueOrNullOrSelf();
|
||||
if (!\is_bool($value)) {
|
||||
if ($value instanceof UnionType) {
|
||||
return null;
|
||||
}
|
||||
if ($value) {
|
||||
$function_name = ConditionVisitor::getFunctionName($call_node);
|
||||
if (\is_string($function_name) && \strcasecmp($function_name, 'count') === 0) {
|
||||
if (!\is_int($value)) {
|
||||
return null;
|
||||
}
|
||||
if ($negate && $value !== 0) {
|
||||
// Phan currently can't infer anything from `count($x) !== 2`.
|
||||
return null;
|
||||
}
|
||||
} elseif (!\is_bool($value)) {
|
||||
return null;
|
||||
}
|
||||
if ($value xor $negate) { // logical xor
|
||||
// e.g. `if (is_string($x) === true)`
|
||||
return (new ConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
} else {
|
||||
@@ -60,7 +69,7 @@ class IdenticalCondition implements BinaryCondition
|
||||
}
|
||||
}
|
||||
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr): ?Context
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr, bool $negate = false): ?Context
|
||||
{
|
||||
if (!$expr instanceof Node) {
|
||||
return null;
|
||||
@@ -71,7 +80,7 @@ class IdenticalCondition implements BinaryCondition
|
||||
if (!\is_bool($value)) {
|
||||
return null;
|
||||
}
|
||||
if ($value) {
|
||||
if ($value xor $negate) { // logical xor
|
||||
// e.g. `if (is_string($x) === true)`
|
||||
return (new ConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
} else {
|
||||
@@ -79,4 +88,10 @@ class IdenticalCondition implements BinaryCondition
|
||||
return (new NegatedConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-31
@@ -5,10 +5,7 @@ declare(strict_types=1);
|
||||
namespace Phan\Analysis\ConditionVisitor;
|
||||
|
||||
use ast\Node;
|
||||
use Phan\Analysis\ConditionVisitor;
|
||||
use Phan\Analysis\ConditionVisitorInterface;
|
||||
use Phan\Analysis\NegatedConditionVisitor;
|
||||
use Phan\AST\UnionTypeVisitor;
|
||||
use Phan\Language\Context;
|
||||
|
||||
/**
|
||||
@@ -43,37 +40,17 @@ class NotEqualsCondition implements BinaryCondition
|
||||
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr): ?Context
|
||||
{
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$expr_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr);
|
||||
if (!$expr_type->isExclusivelyBoolTypes() && !UnionTypeVisitor::unionTypeFromNode($code_base, $context, $call_node)->isExclusivelyBoolTypes()) {
|
||||
return null;
|
||||
}
|
||||
if (!$expr_type->containsFalsey()) {
|
||||
// e.g. `if (is_string($x) != true)`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
} elseif (!$expr_type->containsTruthy()) {
|
||||
// e.g. `if (is_string($x) != false)`
|
||||
return (new ConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
}
|
||||
return null;
|
||||
return (new EqualsCondition())->analyzeCall($visitor, $call_node, $expr, true);
|
||||
}
|
||||
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr): ?Context
|
||||
{
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$expr_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr);
|
||||
if (!$expr_type->isExclusivelyBoolTypes() && !UnionTypeVisitor::unionTypeFromNode($code_base, $context, $complex_node)->isExclusivelyBoolTypes()) {
|
||||
return null;
|
||||
}
|
||||
if (!$expr_type->containsFalsey()) {
|
||||
// e.g. `if (($x instanceof Xyz) != true)`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
} elseif (!$expr_type->containsTruthy()) {
|
||||
// e.g. `if (($x instanceof Xyz) != false)`
|
||||
return (new ConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
}
|
||||
return null;
|
||||
return (new EqualsCondition())->analyzeComplexCondition($visitor, $complex_node, $expr, true);
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,4 +89,10 @@ class NotHasTypeCondition implements BinaryCondition
|
||||
// TODO: Could analyze get_class($array['field']) === stdClass::class (e.g. with AssignmentVisitor)
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-35
@@ -5,10 +5,7 @@ declare(strict_types=1);
|
||||
namespace Phan\Analysis\ConditionVisitor;
|
||||
|
||||
use ast\Node;
|
||||
use Phan\Analysis\ConditionVisitor;
|
||||
use Phan\Analysis\ConditionVisitorInterface;
|
||||
use Phan\Analysis\NegatedConditionVisitor;
|
||||
use Phan\AST\UnionTypeVisitor;
|
||||
use Phan\Language\Context;
|
||||
|
||||
/**
|
||||
@@ -42,41 +39,17 @@ class NotIdenticalCondition implements BinaryCondition
|
||||
|
||||
public function analyzeCall(ConditionVisitorInterface $visitor, Node $call_node, $expr): ?Context
|
||||
{
|
||||
if (!$expr instanceof Node) {
|
||||
return null;
|
||||
}
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$value = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr)->asSingleScalarValueOrNullOrSelf();
|
||||
if (!\is_bool($value)) {
|
||||
return null;
|
||||
}
|
||||
if ($value) {
|
||||
// e.g. `if (!(is_string($x) === true))`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
} else {
|
||||
// e.g. `if (!(is_string($x) === false))`
|
||||
return (new ConditionVisitor($code_base, $context))->visitCall($call_node);
|
||||
}
|
||||
return (new IdenticalCondition())->analyzeCall($visitor, $call_node, $expr, true);
|
||||
}
|
||||
|
||||
public function analyzeComplexCondition(ConditionVisitorInterface $visitor, Node $complex_node, $expr): ?Context
|
||||
{
|
||||
if (!$expr instanceof Node) {
|
||||
return null;
|
||||
}
|
||||
$code_base = $visitor->getCodeBase();
|
||||
$context = $visitor->getContext();
|
||||
$value = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $expr)->asSingleScalarValueOrNullOrSelf();
|
||||
if (!\is_bool($value)) {
|
||||
return null;
|
||||
}
|
||||
if ($value) {
|
||||
// e.g. `if (($x instanceof Xyz) !== true)`
|
||||
return (new NegatedConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
} else {
|
||||
// e.g. `if (($x instanceof Xyz) !== false)`
|
||||
return (new ConditionVisitor($code_base, $context))->__invoke($complex_node);
|
||||
}
|
||||
return (new IdenticalCondition())->analyzeComplexCondition($visitor, $complex_node, $expr, true);
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function withFlippedOperands(): BinaryCondition
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+26
-1
@@ -30,6 +30,7 @@ use Phan\Language\Element\Variable;
|
||||
use Phan\Language\FQSEN\FullyQualifiedClassName;
|
||||
use Phan\Language\Type;
|
||||
use Phan\Language\Type\ArrayShapeType;
|
||||
use Phan\Language\Type\ArrayType;
|
||||
use Phan\Language\Type\FalseType;
|
||||
use Phan\Language\Type\IntType;
|
||||
use Phan\Language\Type\IterableType;
|
||||
@@ -392,6 +393,24 @@ trait ConditionVisitorUtil
|
||||
);
|
||||
}
|
||||
|
||||
final protected function removeEmptyArrayFromVariable(Node $var_node, Context $context): Context
|
||||
{
|
||||
return $this->updateVariableWithConditionalFilter(
|
||||
$var_node,
|
||||
$context,
|
||||
static function (UnionType $type): bool {
|
||||
return $type->hasArray();
|
||||
},
|
||||
static function (UnionType $type): UnionType {
|
||||
return $type->asMappedUnionType(static function (Type $type): Type {
|
||||
return $type instanceof ArrayType ? $type->asNonFalseyType() : $type;
|
||||
});
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type after removing all types that are empty or don't support property or array access
|
||||
* @param 1|2|3|4 $access_type ConditionVisitor::ACCESS_IS_*
|
||||
@@ -1034,6 +1053,8 @@ trait ConditionVisitorUtil
|
||||
return $this->removeTrueFromVariable($var_node, $context);
|
||||
} elseif ($value === null) {
|
||||
return $this->removeNullFromVariable($var_node, $context, false);
|
||||
} elseif ($value === []) {
|
||||
return $this->removeEmptyArrayFromVariable($var_node, $context);
|
||||
}
|
||||
} else {
|
||||
return $this->removeLiteralScalarFromVariable($var_node, $context, $expr, true);
|
||||
@@ -1070,6 +1091,8 @@ trait ConditionVisitorUtil
|
||||
return $this->removeFalseyFromVariable($var_node, $context, false);
|
||||
} elseif ($expr === true) {
|
||||
return $this->removeTrueFromVariable($var_node, $context);
|
||||
} elseif ($expr === []) {
|
||||
return $this->removeEmptyArrayFromVariable($var_node, $context);
|
||||
}
|
||||
}
|
||||
// Remove all of the types which are loosely equal
|
||||
@@ -1149,7 +1172,7 @@ trait ConditionVisitorUtil
|
||||
}
|
||||
}
|
||||
if ($right instanceof Node) {
|
||||
$result = $this->analyzeBinaryConditionSide($right, $left, $condition);
|
||||
$result = $this->analyzeBinaryConditionSide($right, $left, $condition->withFlippedOperands());
|
||||
if ($result !== null) {
|
||||
return $result;
|
||||
}
|
||||
@@ -1398,6 +1421,8 @@ trait ConditionVisitorUtil
|
||||
|
||||
/**
|
||||
* Fetches the function name. Does not check for function uses or namespaces.
|
||||
*
|
||||
* TODO: Check for function uses
|
||||
* @param Node $node a node of kind ast\AST_CALL
|
||||
* @return ?string (null if function name could not be found)
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ use Phan\Language\Type;
|
||||
use Phan\Language\Type\ArrayType;
|
||||
use Phan\Language\Type\FloatType;
|
||||
use Phan\Language\Type\GenericArrayType;
|
||||
use Phan\Language\Type\IntersectionType;
|
||||
use Phan\Language\Type\IntType;
|
||||
use Phan\Language\Type\IterableType;
|
||||
use Phan\Language\Type\MixedType;
|
||||
@@ -633,10 +634,12 @@ class NegatedConditionVisitor extends KindVisitorImplementation implements Condi
|
||||
})->asNormalizedTypes());
|
||||
};
|
||||
/** @param list<Node|mixed> $unused_args */
|
||||
$zero_count_callback = static function (CodeBase $unused_code_base, Context $unused_context, Variable $variable, array $unused_args): void {
|
||||
$variable->setUnionType($variable->getUnionType()->asMappedListUnionType(/** @return list<Type> */ static function (Type $type): array {
|
||||
$zero_count_callback = static function (CodeBase $code_base, Context $context, Variable $variable, array $unused_args): void {
|
||||
$variable->setUnionType($variable->getUnionType()->asMappedListUnionType(/** @return list<Type> */ static function (Type $type) use ($code_base, $context): array {
|
||||
if ($type->isPossiblyObject()) {
|
||||
// TODO: Could cast iterable to Traversable|array{}
|
||||
if ($type->isObject()) {
|
||||
return [IntersectionType::createFromTypes([$type, Type::countableInstance()], $code_base, $context)];
|
||||
}
|
||||
return [$type];
|
||||
}
|
||||
if (!$type->isPossiblyFalsey()) {
|
||||
|
||||
+27
-30
@@ -40,7 +40,6 @@ use Phan\Language\Element\Variable;
|
||||
use Phan\Language\FQSEN\FullyQualifiedClassName;
|
||||
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
|
||||
use Phan\Language\Type;
|
||||
use Phan\Language\Type\ArrayType;
|
||||
use Phan\Language\Type\FalseType;
|
||||
use Phan\Language\Type\GenericArrayType;
|
||||
use Phan\Language\Type\IntType;
|
||||
@@ -298,7 +297,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
}
|
||||
return $type;
|
||||
});
|
||||
$variable = clone($variable);
|
||||
$variable = clone $variable;
|
||||
$context->addScopeVariable($variable);
|
||||
$variable->setUnionType($union_type);
|
||||
/*
|
||||
@@ -308,7 +307,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
$union_type = $union_type->withoutType($type)->withType(
|
||||
GenericArrayType::fromElementType($type->genericArrayElementType(), false, $type->getKeyType())
|
||||
);
|
||||
$variable = clone($variable);
|
||||
$variable = clone $variable;
|
||||
$context->addScopeVariable($variable);
|
||||
$variable->setUnionType($union_type);
|
||||
}
|
||||
@@ -509,10 +508,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
);
|
||||
|
||||
if (!$type->hasPrintableScalar()) {
|
||||
if ($type->isType(ArrayType::instance(false))
|
||||
|| $type->isType(ArrayType::instance(true))
|
||||
|| $type->isGenericArray()
|
||||
) {
|
||||
if ($type->isArray()) {
|
||||
$this->emitIssue(
|
||||
Issue::TypeConversionFromArray,
|
||||
$expr_node->lineno,
|
||||
@@ -615,12 +611,15 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
} else {
|
||||
$scope = $this->context->getScope();
|
||||
if (!$scope->hasGlobalVariableWithName($variable_name)) {
|
||||
$this->context->addGlobalScopeVariable(clone $variable);
|
||||
$actual_global_variable = clone $variable;
|
||||
$this->context->addGlobalScopeVariable($actual_global_variable);
|
||||
} else {
|
||||
// TODO: Support @global?
|
||||
$actual_global_variable = $scope->getGlobalVariableByName($variable_name);
|
||||
}
|
||||
// TODO: Support @global?
|
||||
$actual_global_variable = $scope->getGlobalVariableByName($variable_name);
|
||||
$scope_global_variable = $actual_global_variable instanceof GlobalVariable ? clone($actual_global_variable) : new GlobalVariable($actual_global_variable);
|
||||
$scope_global_variable->setUnionType($actual_global_variable->getUnionType()->eraseRealTypeSetRecursively());
|
||||
$scope_global_variable = $actual_global_variable instanceof GlobalVariable ? (clone $actual_global_variable) : new GlobalVariable($actual_global_variable);
|
||||
// Importing an undefined global by reference will make an undefined value a reference to null.
|
||||
$scope_global_variable->setUnionType($actual_global_variable->getUnionType()->eraseRealTypeSetRecursively()->withIsPossiblyUndefined(false));
|
||||
}
|
||||
|
||||
// Note that we're not creating a new scope, just
|
||||
@@ -660,6 +659,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
}
|
||||
|
||||
// NOTE: Phan can't be sure that the type the static type starts with is the same as what it has later. Avoid false positive PhanRedundantCondition.
|
||||
// This should never be undefined with current limits on expressions found in static variables.
|
||||
$variable->setUnionType($default_type->eraseRealTypeSetRecursively());
|
||||
// TODO: Probably not true in a loop?
|
||||
// TODO: Expand this to assigning to variables? (would need to make references invalidate that, and skip this in the global scope)
|
||||
@@ -706,10 +706,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
);
|
||||
|
||||
if (!$type->hasPrintableScalar()) {
|
||||
if ($type->isType(ArrayType::instance(false))
|
||||
|| $type->isType(ArrayType::instance(true))
|
||||
|| $type->isGenericArray()
|
||||
) {
|
||||
if ($type->isArray()) {
|
||||
$this->emitIssue(
|
||||
Issue::TypeConversionFromArray,
|
||||
$expr_node->lineno ?? $node->lineno,
|
||||
@@ -1250,7 +1247,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
} catch (IssueException | NodeException $_) {
|
||||
return $this->context;
|
||||
}
|
||||
$variable = clone($variable);
|
||||
$variable = clone $variable;
|
||||
$variable->setUnionType($new_type);
|
||||
$this->context->addScopeVariable($variable);
|
||||
return $this->context;
|
||||
@@ -1505,10 +1502,9 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
|
||||
if (!$return_type->isEmpty()
|
||||
&& !$func->hasReturn()
|
||||
&& !self::declOnlyThrows($node)
|
||||
&& !$return_type->hasType(VoidType::instance(false))
|
||||
&& !$return_type->hasType(NullType::instance(false))
|
||||
&& !$return_type->hasType(NeverType::instance(false))
|
||||
&& !self::declNeverReturns($node)
|
||||
&& !$return_type->isNull()
|
||||
&& !$return_type->isNeverType()
|
||||
) {
|
||||
$this->warnTypeMissingReturn($func, $node);
|
||||
}
|
||||
@@ -2001,7 +1997,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
RedundantCondition::emitInstance(
|
||||
$expr,
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($expr->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($expr->lineno ?? $node->lineno),
|
||||
Issue::EmptyYieldFrom,
|
||||
[(string)$yield_from_type],
|
||||
Closure::fromCallable([BlockAnalysisVisitor::class, 'isEmptyIterable'])
|
||||
@@ -3029,9 +3025,10 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
&& !$has_interface_class
|
||||
&& !$return_type->isEmpty()
|
||||
&& !$method->hasReturn()
|
||||
&& !self::declOnlyThrows($node)
|
||||
&& !self::declNeverReturns($node)
|
||||
&& !$return_type->hasType(VoidType::instance(false))
|
||||
&& !$return_type->hasType(NullType::instance(false))
|
||||
&& !$return_type->hasType(NeverType::instance(false))
|
||||
) {
|
||||
$this->warnTypeMissingReturn($method, $node);
|
||||
}
|
||||
@@ -3091,7 +3088,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
|
||||
if (!$return_type->isEmpty()
|
||||
&& !$function->hasReturn()
|
||||
&& !self::declOnlyThrows($node)
|
||||
&& !self::declNeverReturns($node)
|
||||
&& !$return_type->hasType(VoidType::instance(false))
|
||||
&& !$return_type->hasType(NullType::instance(false))
|
||||
) {
|
||||
@@ -4204,7 +4201,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
if ($variable) {
|
||||
$set_variable_type = static function (UnionType $new_type) use ($code_base, $context, $variable, $argument): void {
|
||||
if ($variable instanceof Variable) {
|
||||
$variable = clone($variable);
|
||||
$variable = clone $variable;
|
||||
AssignmentVisitor::analyzeSetUnionTypeInContext($code_base, $context, $variable, $new_type, $argument);
|
||||
$context->addScopeVariable($variable);
|
||||
} else {
|
||||
@@ -4349,12 +4346,12 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
}
|
||||
|
||||
$original_method_scope = $method->getInternalScope();
|
||||
$method->setInternalScope(clone($original_method_scope));
|
||||
$method->setInternalScope(clone $original_method_scope);
|
||||
try {
|
||||
// Even though we don't modify the parameter list, we still need to know the types
|
||||
// -- as an optimization, we don't run quick mode again if the types didn't change?
|
||||
$parameter_list = \array_map(static function (Parameter $parameter): Parameter {
|
||||
return clone($parameter);
|
||||
return clone $parameter;
|
||||
}, $method->getParameterList());
|
||||
|
||||
foreach ($parameter_list as $i => $parameter_clone) {
|
||||
@@ -4436,7 +4433,7 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
): void {
|
||||
$method = $this->findDefiningMethod($method);
|
||||
$original_method_scope = $method->getInternalScope();
|
||||
$method->setInternalScope(clone($original_method_scope));
|
||||
$method->setInternalScope(clone $original_method_scope);
|
||||
$method_context = $method->getContext();
|
||||
|
||||
try {
|
||||
@@ -5046,12 +5043,12 @@ class PostOrderAnalysisVisitor extends AnalysisVisitor
|
||||
* @return bool
|
||||
* True when the decl can only throw an exception or return or exit()
|
||||
*/
|
||||
private static function declOnlyThrows(Node $node): bool
|
||||
private static function declNeverReturns(Node $node): bool
|
||||
{
|
||||
// Work around fallback parser generating methods without statements list.
|
||||
// Otherwise, 'stmts' would always be a Node due to preconditions.
|
||||
$stmts_node = $node->children['stmts'];
|
||||
return $stmts_node instanceof Node && BlockExitStatusChecker::willUnconditionallyThrowOrReturn($stmts_node);
|
||||
return $stmts_node instanceof Node && BlockExitStatusChecker::willUnconditionallyNeverReturn($stmts_node);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-4
@@ -1177,7 +1177,7 @@ class BlockAnalysisVisitor extends AnalysisVisitor
|
||||
RedundantCondition::emitInstance(
|
||||
$node->children['expr'],
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($node->children['expr']->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->children['expr']->lineno ?? $node->lineno),
|
||||
Issue::EmptyForeach,
|
||||
[(string)$union_type],
|
||||
Closure::fromCallable([self::class, 'isEmptyIterable'])
|
||||
@@ -1188,7 +1188,7 @@ class BlockAnalysisVisitor extends AnalysisVisitor
|
||||
RedundantCondition::emitInstance(
|
||||
$node->children['expr'],
|
||||
$this->code_base,
|
||||
(clone($this->context))->withLineNumberStart($node->children['expr']->lineno ?? $node->lineno),
|
||||
(clone $this->context)->withLineNumberStart($node->children['expr']->lineno ?? $node->lineno),
|
||||
Issue::EmptyForeachBody,
|
||||
[(string)$union_type],
|
||||
Closure::fromCallable([self::class, 'isDefinitelyNotObject'])
|
||||
@@ -2535,6 +2535,13 @@ class BlockAnalysisVisitor extends AnalysisVisitor
|
||||
// Step into each catch node and get an
|
||||
// updated context for the node
|
||||
$catch_context = $this->analyzeAndGetUpdatedContext($catch_context, $node, $catch_node);
|
||||
$catch_stmts_node = $catch_node->children['stmts'];
|
||||
if ($catch_stmts_node instanceof Node && BlockExitStatusChecker::willUnconditionallySkipRemainingStatements($catch_stmts_node)) {
|
||||
// e.g. "} catch (Exception $e) { break; }"
|
||||
if (!BlockExitStatusChecker::willUnconditionallyThrowOrReturn($catch_stmts_node)) {
|
||||
$this->recordLoopContextForBreakOrContinue($catch_context);
|
||||
}
|
||||
}
|
||||
// NOTE: We let ContextMergeVisitor->mergeCatchContext decide if the block exit status is valid.
|
||||
$catch_context_list[] = $catch_context;
|
||||
}
|
||||
@@ -2891,7 +2898,7 @@ class BlockAnalysisVisitor extends AnalysisVisitor
|
||||
RedundantCondition::emitInstance(
|
||||
$left_node,
|
||||
$this->code_base,
|
||||
(clone($context))->withLineNumberStart($node->lineno),
|
||||
(clone $context)->withLineNumberStart($node->lineno),
|
||||
Issue::CoalescingNeverNull,
|
||||
[
|
||||
ASTReverter::toShortString($left_node),
|
||||
@@ -2907,7 +2914,7 @@ class BlockAnalysisVisitor extends AnalysisVisitor
|
||||
RedundantCondition::emitInstance(
|
||||
$left_node,
|
||||
$this->code_base,
|
||||
(clone($context))->withLineNumberStart($node->lineno),
|
||||
(clone $context)->withLineNumberStart($node->lineno),
|
||||
Issue::CoalescingAlwaysNull,
|
||||
[
|
||||
ASTReverter::toShortString($left_node),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user