12
12
13
13
error_reporting (E_ALL );
14
14
15
- function processDirectory (string $ dir , Context $ context ) {
15
+ /**
16
+ * @return FileInfo[]
17
+ */
18
+ function processDirectory (string $ dir , Context $ context ): array {
19
+ $ fileInfos = [];
20
+
16
21
$ it = new RecursiveIteratorIterator (
17
22
new RecursiveDirectoryIterator ($ dir ),
18
23
RecursiveIteratorIterator::LEAVES_ONLY
19
24
);
20
25
foreach ($ it as $ file ) {
21
26
$ pathName = $ file ->getPathName ();
22
27
if (preg_match ('/\.stub\.php$/ ' , $ pathName )) {
23
- processStubFile ($ pathName , $ context );
28
+ $ fileInfo = processStubFile ($ pathName , $ context );
29
+ if ($ fileInfo ) {
30
+ $ fileInfos [] = $ fileInfo ;
31
+ }
24
32
}
25
33
}
34
+
35
+ return $ fileInfos ;
26
36
}
27
37
28
- function processStubFile (string $ stubFile , Context $ context ) {
38
+ function processStubFile (string $ stubFile , Context $ context ): ? FileInfo {
29
39
try {
30
40
if (!file_exists ($ stubFile )) {
31
41
throw new Exception ("File $ stubFile does not exist " );
@@ -37,15 +47,15 @@ function processStubFile(string $stubFile, Context $context) {
37
47
$ stubCode = file_get_contents ($ stubFile );
38
48
$ stubHash = computeStubHash ($ stubCode );
39
49
$ oldStubHash = extractStubHash ($ arginfoFile );
40
- if ($ stubHash === $ oldStubHash && $ context ->forceRegeneration === false ) {
50
+ if ($ stubHash === $ oldStubHash && ! $ context ->forceParse ) {
41
51
/* Stub file did not change, do not regenerate. */
42
- return ;
52
+ return null ;
43
53
}
44
54
45
55
initPhpParser ();
46
56
$ fileInfo = parseStubFile ($ stubCode );
47
57
$ arginfoCode = generateArgInfoCode ($ fileInfo , $ stubHash );
48
- if (file_put_contents ($ arginfoFile , $ arginfoCode )) {
58
+ if (( $ context -> forceRegeneration || $ stubHash !== $ oldStubHash ) && file_put_contents ($ arginfoFile , $ arginfoCode )) {
49
59
echo "Saved $ arginfoFile \n" ;
50
60
}
51
61
@@ -54,20 +64,12 @@ function processStubFile(string $stubFile, Context $context) {
54
64
$ funcInfo ->discardInfoForOldPhpVersions ();
55
65
}
56
66
$ arginfoCode = generateArgInfoCode ($ fileInfo , $ stubHash );
57
- if (file_put_contents ($ legacyFile , $ arginfoCode )) {
67
+ if (( $ context -> forceRegeneration || $ stubHash !== $ oldStubHash ) && file_put_contents ($ legacyFile , $ arginfoCode )) {
58
68
echo "Saved $ legacyFile \n" ;
59
69
}
60
70
}
61
71
62
- // Collect parameter name statistics.
63
- foreach ($ fileInfo ->getAllFuncInfos () as $ funcInfo ) {
64
- foreach ($ funcInfo ->args as $ argInfo ) {
65
- if (!isset ($ context ->parameterStats [$ argInfo ->name ])) {
66
- $ context ->parameterStats [$ argInfo ->name ] = 0 ;
67
- }
68
- $ context ->parameterStats [$ argInfo ->name ]++;
69
- }
70
- }
72
+ return $ fileInfo ;
71
73
} catch (Exception $ e ) {
72
74
echo "In $ stubFile: \n{$ e ->getMessage ()}\n" ;
73
75
exit (1 );
@@ -92,10 +94,10 @@ function extractStubHash(string $arginfoFile): ?string {
92
94
}
93
95
94
96
class Context {
97
+ /** @var bool */
98
+ public $ forceParse = false ;
95
99
/** @var bool */
96
100
public $ forceRegeneration = false ;
97
- /** @var array */
98
- public $ parameterStats = [];
99
101
}
100
102
101
103
class SimpleType {
@@ -358,6 +360,8 @@ public function getDeclaration(): string;
358
360
public function getArgInfoName (): string ;
359
361
public function __toString (): string ;
360
362
public function isMagicMethod (): bool ;
363
+ public function isMethod (): bool ;
364
+ public function isConstructor (): bool ;
361
365
}
362
366
363
367
class FunctionName implements FunctionOrMethodName {
@@ -402,6 +406,14 @@ public function __toString(): string {
402
406
public function isMagicMethod (): bool {
403
407
return false ;
404
408
}
409
+
410
+ public function isMethod (): bool {
411
+ return false ;
412
+ }
413
+
414
+ public function isConstructor (): bool {
415
+ return false ;
416
+ }
405
417
}
406
418
407
419
class MethodName implements FunctionOrMethodName {
@@ -434,6 +446,14 @@ public function __toString(): string {
434
446
public function isMagicMethod (): bool {
435
447
return strpos ($ this ->methodName , '__ ' ) === 0 ;
436
448
}
449
+
450
+ public function isMethod (): bool {
451
+ return true ;
452
+ }
453
+
454
+ public function isConstructor (): bool {
455
+ return $ this ->methodName === "__construct " ;
456
+ }
437
457
}
438
458
439
459
class ReturnInfo {
@@ -457,13 +477,17 @@ class FuncInfo {
457
477
/** @var FunctionOrMethodName */
458
478
public $ name ;
459
479
/** @var int */
480
+ public $ classFlags ;
481
+ /** @var int */
460
482
public $ flags ;
461
483
/** @var string|null */
462
484
public $ aliasType ;
463
485
/** @var FunctionName|null */
464
486
public $ alias ;
465
487
/** @var bool */
466
488
public $ isDeprecated ;
489
+ /** @var bool */
490
+ public $ verify ;
467
491
/** @var ArgInfo[] */
468
492
public $ args ;
469
493
/** @var ReturnInfo */
@@ -475,26 +499,45 @@ class FuncInfo {
475
499
476
500
public function __construct (
477
501
FunctionOrMethodName $ name ,
502
+ int $ classFlags ,
478
503
int $ flags ,
479
504
?string $ aliasType ,
480
505
?FunctionOrMethodName $ alias ,
481
506
bool $ isDeprecated ,
507
+ bool $ verify ,
482
508
array $ args ,
483
509
ReturnInfo $ return ,
484
510
int $ numRequiredArgs ,
485
511
?string $ cond
486
512
) {
487
513
$ this ->name = $ name ;
514
+ $ this ->classFlags = $ classFlags ;
488
515
$ this ->flags = $ flags ;
489
516
$ this ->aliasType = $ aliasType ;
490
517
$ this ->alias = $ alias ;
491
518
$ this ->isDeprecated = $ isDeprecated ;
519
+ $ this ->verify = $ verify ;
492
520
$ this ->args = $ args ;
493
521
$ this ->return = $ return ;
494
522
$ this ->numRequiredArgs = $ numRequiredArgs ;
495
523
$ this ->cond = $ cond ;
496
524
}
497
525
526
+ public function isMethod (): bool
527
+ {
528
+ return $ this ->name ->isMethod ();
529
+ }
530
+
531
+ public function isFinalMethod (): bool
532
+ {
533
+ return ($ this ->flags & Class_::MODIFIER_FINAL ) || ($ this ->classFlags & Class_::MODIFIER_FINAL );
534
+ }
535
+
536
+ public function isInstanceMethod (): bool
537
+ {
538
+ return !($ this ->flags & Class_::MODIFIER_STATIC ) && $ this ->isMethod () && !$ this ->name ->isConstructor ();
539
+ }
540
+
498
541
public function equalsApartFromName (FuncInfo $ other ): bool {
499
542
if (count ($ this ->args ) !== count ($ other ->args )) {
500
543
return false ;
@@ -732,6 +775,7 @@ function parseDocComment(DocComment $comment): array {
732
775
function parseFunctionLike (
733
776
PrettyPrinterAbstract $ prettyPrinter ,
734
777
FunctionOrMethodName $ name ,
778
+ int $ classFlags ,
735
779
int $ flags ,
736
780
Node \FunctionLike $ func ,
737
781
?string $ cond
@@ -741,6 +785,7 @@ function parseFunctionLike(
741
785
$ aliasType = null ;
742
786
$ alias = null ;
743
787
$ isDeprecated = false ;
788
+ $ verify = true ;
744
789
$ haveDocReturnType = false ;
745
790
$ docParamTypes = [];
746
791
@@ -763,6 +808,8 @@ function parseFunctionLike(
763
808
}
764
809
} else if ($ tag ->name === 'deprecated ' ) {
765
810
$ isDeprecated = true ;
811
+ } else if ($ tag ->name === 'no-verify ' ) {
812
+ $ verify = false ;
766
813
} else if ($ tag ->name === 'return ' ) {
767
814
$ haveDocReturnType = true ;
768
815
} else if ($ tag ->name === 'param ' ) {
@@ -843,10 +890,12 @@ function parseFunctionLike(
843
890
844
891
return new FuncInfo (
845
892
$ name ,
893
+ $ classFlags ,
846
894
$ flags ,
847
895
$ aliasType ,
848
896
$ alias ,
849
897
$ isDeprecated ,
898
+ $ verify ,
850
899
$ args ,
851
900
$ return ,
852
901
$ numRequiredArgs ,
@@ -917,6 +966,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
917
966
$ prettyPrinter ,
918
967
new FunctionName ($ stmt ->namespacedName ),
919
968
0 ,
969
+ 0 ,
920
970
$ stmt ,
921
971
$ cond
922
972
);
@@ -936,6 +986,11 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
936
986
throw new Exception ("Not implemented {$ classStmt ->getType ()}" );
937
987
}
938
988
989
+ $ classFlags = 0 ;
990
+ if ($ stmt instanceof Class_) {
991
+ $ classFlags = $ stmt ->flags ;
992
+ }
993
+
939
994
$ flags = $ classStmt ->flags ;
940
995
if ($ stmt instanceof Stmt \Interface_) {
941
996
$ flags |= Class_::MODIFIER_ABSTRACT ;
@@ -948,6 +1003,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
948
1003
$ methodInfos [] = parseFunctionLike (
949
1004
$ prettyPrinter ,
950
1005
new MethodName ($ className , $ classStmt ->name ->toString ()),
1006
+ $ classFlags ,
951
1007
$ flags ,
952
1008
$ classStmt ,
953
1009
$ cond
@@ -1262,29 +1318,132 @@ function initPhpParser() {
1262
1318
}
1263
1319
1264
1320
$ optind = null ;
1265
- $ options = getopt ("fh " , ["force-regeneration " , "parameter-stats " , "help " ], $ optind );
1321
+ $ options = getopt ("fh " , ["force-regeneration " , "parameter-stats " , "help " , " verify " ], $ optind );
1266
1322
1267
1323
$ context = new Context ;
1268
1324
$ printParameterStats = isset ($ options ["parameter-stats " ]);
1269
- $ context ->forceRegeneration =
1270
- isset ($ options ["f " ]) || isset ($ options ["force-regeneration " ]) || $ printParameterStats ;
1325
+ $ verify = isset ($ options ["verify " ]);
1326
+ $ context ->forceRegeneration = isset ($ options ["f " ]) || isset ($ options ["force-regeneration " ]);
1327
+ $ context ->forceParse = $ context ->forceRegeneration || $ printParameterStats || $ verify ;
1271
1328
1272
1329
if (isset ($ options ["h " ]) || isset ($ options ["help " ])) {
1273
- die ("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ -h | --help ] [ name.stub.php | directory ] \n\n" );
1330
+ die ("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ --verify ] [ - h | --help ] [ name.stub.php | directory ] \n\n" );
1274
1331
}
1275
1332
1333
+ $ fileInfos = [];
1276
1334
$ location = $ argv [$ optind ] ?? ". " ;
1277
1335
if (is_file ($ location )) {
1278
1336
// Generate single file.
1279
- processStubFile ($ location , $ context );
1337
+ $ fileInfo = processStubFile ($ location , $ context );
1338
+ if ($ fileInfo ) {
1339
+ $ fileInfos [] = $ fileInfo ;
1340
+ }
1280
1341
} else if (is_dir ($ location )) {
1281
- processDirectory ($ location , $ context );
1342
+ $ fileInfos = processDirectory ($ location , $ context );
1282
1343
} else {
1283
1344
echo "$ location is neither a file nor a directory. \n" ;
1284
1345
exit (1 );
1285
1346
}
1286
1347
1287
1348
if ($ printParameterStats ) {
1288
- arsort ($ context ->parameterStats );
1289
- echo json_encode ($ context ->parameterStats , JSON_PRETTY_PRINT ), "\n" ;
1349
+ $ parameterStats = [];
1350
+
1351
+ foreach ($ fileInfos as $ fileInfo ) {
1352
+ foreach ($ fileInfo ->getAllFuncInfos () as $ funcInfo ) {
1353
+ foreach ($ funcInfo ->args as $ argInfo ) {
1354
+ if (!isset ($ context ->parameterStats [$ argInfo ->name ])) {
1355
+ $ parameterStats [$ argInfo ->name ] = 0 ;
1356
+ }
1357
+ $ parameterStats [$ argInfo ->name ]++;
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ arsort ($ parameterStats );
1363
+ echo json_encode ($ parameterStats , JSON_PRETTY_PRINT ), "\n" ;
1364
+ }
1365
+
1366
+ if ($ verify ) {
1367
+ $ errors = [];
1368
+ $ funcMap = [];
1369
+ $ aliases = [];
1370
+
1371
+ foreach ($ fileInfos as $ fileInfo ) {
1372
+ foreach ($ fileInfo ->getAllFuncInfos () as $ funcInfo ) {
1373
+ /** @var FuncInfo $funcInfo */
1374
+ $ funcMap [$ funcInfo ->name ->__toString ()] = $ funcInfo ;
1375
+
1376
+ if ($ funcInfo ->aliasType === "alias " ) {
1377
+ $ aliases [] = $ funcInfo ;
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ foreach ($ aliases as $ aliasFunc ) {
1383
+ if (!isset ($ funcMap [$ aliasFunc ->alias ->__toString ()])) {
1384
+ $ errors [] = "Aliased function {$ aliasFunc ->alias }() cannot be found " ;
1385
+ continue ;
1386
+ }
1387
+
1388
+ if (!$ aliasFunc ->verify ) {
1389
+ continue ;
1390
+ }
1391
+
1392
+ $ aliasedFunc = $ funcMap [$ aliasFunc ->alias ->__toString ()];
1393
+ $ aliasedArgs = $ aliasedFunc ->args ;
1394
+ $ aliasArgs = $ aliasFunc ->args ;
1395
+
1396
+ if ($ aliasFunc ->isInstanceMethod () !== $ aliasedFunc ->isInstanceMethod ()) {
1397
+ if ($ aliasFunc ->isInstanceMethod ()) {
1398
+ $ aliasedArgs = array_slice ($ aliasedArgs , 1 );
1399
+ }
1400
+
1401
+ if ($ aliasedFunc ->isInstanceMethod ()) {
1402
+ $ aliasArgs = array_slice ($ aliasArgs , 1 );
1403
+ }
1404
+ }
1405
+
1406
+ array_map (
1407
+ function (?ArgInfo $ aliasArg , ?ArgInfo $ aliasedArg ) use ($ aliasFunc , $ aliasedFunc , &$ errors ) {
1408
+ if ($ aliasArg === null ) {
1409
+ assert ($ aliasedArg !== null );
1410
+ $ errors [] = "{$ aliasFunc ->name }(): Argument \$$ aliasedArg ->name of aliased function {$ aliasedFunc ->name }() is missing " ;
1411
+ return null ;
1412
+ }
1413
+
1414
+ if ($ aliasedArg === null ) {
1415
+ assert ($ aliasArg !== null );
1416
+ $ errors [] = "{$ aliasedFunc ->name }(): Argument \$$ aliasArg ->name of alias function {$ aliasFunc ->name }() is missing " ;
1417
+ return null ;
1418
+ }
1419
+
1420
+ if ($ aliasArg ->name !== $ aliasedArg ->name ) {
1421
+ $ errors [] = "{$ aliasFunc ->name }(): Argument \$$ aliasArg ->name and argument \$$ aliasedArg ->name of aliased function {$ aliasedFunc ->name }() must have the same name " ;
1422
+ return null ;
1423
+ }
1424
+
1425
+ if ($ aliasArg ->type != $ aliasedArg ->type ) {
1426
+ $ errors [] = "{$ aliasFunc ->name }(): Argument \$$ aliasArg ->name and argument \$$ aliasedArg ->name of aliased function {$ aliasedFunc ->name }() must have the same type " ;
1427
+ }
1428
+
1429
+ if ($ aliasArg ->defaultValue !== $ aliasedArg ->defaultValue ) {
1430
+ $ errors [] = "{$ aliasFunc ->name }(): Argument \$$ aliasArg ->name and argument \$$ aliasedArg ->name of aliased function {$ aliasedFunc ->name }() must have the same default value " ;
1431
+ }
1432
+ },
1433
+ $ aliasArgs , $ aliasedArgs
1434
+ );
1435
+
1436
+ if ((!$ aliasedFunc ->isMethod () || $ aliasedFunc ->isFinalMethod ()) &&
1437
+ (!$ aliasFunc ->isMethod () || $ aliasFunc ->isFinalMethod ()) &&
1438
+ $ aliasFunc ->return != $ aliasedFunc ->return
1439
+ ) {
1440
+ $ errors [] = "{$ aliasFunc ->name }() and {$ aliasedFunc ->name }() must have the same return type " ;
1441
+ }
1442
+ }
1443
+
1444
+ echo implode ("\n" , $ errors );
1445
+ if (!empty ($ errors )) {
1446
+ echo "\n" ;
1447
+ exit (1 );
1448
+ }
1290
1449
}
0 commit comments