@@ -1015,13 +1015,16 @@ class PropertyInfo
1015
1015
public $ type ;
1016
1016
/** @var Expr|null */
1017
1017
public $ defaultValue ;
1018
+ /** @var bool */
1019
+ public $ isReadonly ;
1018
1020
1019
- public function __construct (PropertyName $ name , int $ flags , ?Type $ type , ?Expr $ defaultValue )
1021
+ public function __construct (PropertyName $ name , int $ flags , ?Type $ type , ?Expr $ defaultValue, bool $ isReadonly )
1020
1022
{
1021
1023
$ this ->name = $ name ;
1022
1024
$ this ->flags = $ flags ;
1023
1025
$ this ->type = $ type ;
1024
1026
$ this ->defaultValue = $ defaultValue ;
1027
+ $ this ->isReadonly = $ isReadonly ;
1025
1028
}
1026
1029
1027
1030
public function discardInfoForOldPhpVersions (): void {
@@ -1184,6 +1187,42 @@ private function getFlagsAsString(): string
1184
1187
1185
1188
return $ flags ;
1186
1189
}
1190
+
1191
+ public function getFieldSynopsisElement (DOMDocument $ doc ): DOMElement
1192
+ {
1193
+ $ fieldsynopsisElement = $ doc ->createElement ("fieldsynopsis " );
1194
+
1195
+ if ($ this ->flags & Class_::MODIFIER_PUBLIC ) {
1196
+ $ fieldsynopsisElement ->appendChild ($ doc ->createElement ("modifier " , "public " ));
1197
+ } elseif ($ this ->flags & Class_::MODIFIER_PROTECTED ) {
1198
+ $ fieldsynopsisElement ->appendChild ($ doc ->createElement ("modifier " , "protected " ));
1199
+ } elseif ($ this ->flags & Class_::MODIFIER_PRIVATE ) {
1200
+ $ fieldsynopsisElement ->appendChild ($ doc ->createElement ("modifier " , "private " ));
1201
+ }
1202
+
1203
+ if ($ this ->flags & Class_::MODIFIER_STATIC ) {
1204
+ $ fieldsynopsisElement ->appendChild ($ doc ->createElement ("modifier " , "static " ));
1205
+ } elseif ($ this ->flags & Class_::MODIFIER_READONLY || $ this ->isReadonly ) {
1206
+ $ fieldsynopsisElement ->appendChild ($ doc ->createElement ("modifier " , "readonly " ));
1207
+ }
1208
+
1209
+ if ($ this ->type ) {
1210
+ $ typeElement = $ doc ->createElement ("type " );
1211
+ $ fieldsynopsisElement ->appendChild ($ typeElement );
1212
+ }
1213
+
1214
+ $ className = str_replace ("\\" , "- " , $ this ->name ->class ->toLowerString ());
1215
+ $ varnameElement = $ doc ->createElement ("varname " , $ this ->name ->property );
1216
+ $ varnameElement ->setAttribute ("linkend " , "$ className.props. " . $ this ->name ->property );
1217
+ $ fieldsynopsisElement ->appendChild ($ varnameElement );
1218
+
1219
+ if ($ this ->defaultValue ) {
1220
+ $ initializerElement = $ doc ->createElement ("initializer " , "" );
1221
+ $ fieldsynopsisElement ->appendChild ($ initializerElement );
1222
+ }
1223
+
1224
+ return $ fieldsynopsisElement ;
1225
+ }
1187
1226
}
1188
1227
1189
1228
class ClassInfo {
@@ -1332,6 +1371,151 @@ private function getFlagsAsString(): string
1332
1371
1333
1372
return implode ("| " , $ flags );
1334
1373
}
1374
+
1375
+ public function getClassSynopsisDocument (): ?string {
1376
+
1377
+ $ doc = new DOMDocument ();
1378
+ $ doc ->formatOutput = true ;
1379
+ $ classSynopsis = $ this ->getClassSynopsisElement ($ doc );
1380
+ if (!$ classSynopsis ) {
1381
+ return null ;
1382
+ }
1383
+
1384
+ $ doc ->appendChild ($ classSynopsis );
1385
+
1386
+ return $ doc ->saveXML ();
1387
+ }
1388
+
1389
+ public function getClassSynopsisElement (DOMDocument $ doc ): ?DOMElement {
1390
+
1391
+ $ classSynopsis = $ doc ->createElement ("classsynopsis " );
1392
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1393
+
1394
+ $ ooElement = self ::createOoElement ($ doc , $ this , false , false );
1395
+ $ classSynopsis ->appendChild ($ ooElement );
1396
+
1397
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1398
+
1399
+ $ classSynopsisInfo = $ doc ->createElement ("classsynopsisinfo " );
1400
+ $ classSynopsisInfo ->appendChild (new DOMText ("\n " ));
1401
+ $ ooElement = self ::createOoElement ($ doc , $ this , true , false );
1402
+ $ classSynopsisInfo ->appendChild ($ ooElement );
1403
+ $ classSynopsisInfo ->appendChild (new DOMText ("\n " ));
1404
+
1405
+ $ classSynopsis ->appendChild ($ classSynopsisInfo );
1406
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1407
+
1408
+ foreach ($ this ->extends as $ k => $ parent ) {
1409
+ $ ooElement = self ::createOoElement ($ doc , $ this , false , $ k === 0 ); // TODO fix isExtends for interfaces
1410
+ $ classSynopsisInfo ->appendChild ($ ooElement );
1411
+ $ classSynopsisInfo ->appendChild (new DOMText ("\n " ));
1412
+ }
1413
+
1414
+ if ($ this ->implements ) {
1415
+ $ ooElement = self ::createOoElement ($ doc , $ this , false , false );
1416
+ $ classSynopsisInfo ->appendChild ($ ooElement );
1417
+ $ classSynopsisInfo ->appendChild (new DOMText ("\n " ));
1418
+ }
1419
+
1420
+ if (!empty ($ this ->propertyInfos )) {
1421
+ /*
1422
+ <fieldsynopsis>
1423
+ <modifier>public</modifier>
1424
+ <modifier>readonly</modifier>
1425
+ <type>string</type>
1426
+ <varname linkend="domdocument.props.actualencoding">actualEncoding</varname>
1427
+ </fieldsynopsis>
1428
+ */
1429
+
1430
+ $ classSynopsisInfo = $ doc ->createElement ("classsynopsisinfo " , "&Properties; " );
1431
+ $ classSynopsisInfo ->setAttribute ("role " , "comment " );
1432
+ $ classSynopsis ->appendChild ($ classSynopsisInfo );
1433
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1434
+
1435
+ foreach ($ this ->propertyInfos as $ propertyInfo ) {
1436
+ $ fieldSynopsisElement = $ propertyInfo ->getFieldSynopsisElement ($ doc );
1437
+ $ classSynopsis ->appendChild ($ fieldSynopsisElement );
1438
+ $ classSynopsisInfo ->appendChild (new DOMText ("\n " ));
1439
+ }
1440
+ }
1441
+
1442
+ if (!empty ($ this ->funcInfos )) {
1443
+ $ classSynopsisInfo = $ doc ->createElement ("classsynopsisinfo " , "&Methods; " );
1444
+ $ classSynopsisInfo ->setAttribute ("role " , "comment " );
1445
+ $ classSynopsis ->appendChild ($ classSynopsisInfo );
1446
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1447
+
1448
+ $ className = $ this ->getClassSynopsisFilename ();
1449
+
1450
+ $ includeElement = $ doc ->createElement ("xi:include " );
1451
+ $ includeElement ->setAttribute ("xpointer " , "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class. $ className')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[not(@role='procedural')] " );
1452
+ $ includeElement ->appendChild (new DOMText ("\n " ));
1453
+ $ fallbackElement = $ doc ->createElement ("xi:fallback " );
1454
+ $ includeElement ->appendChild ($ fallbackElement );
1455
+ $ includeElement ->appendChild (new DOMText ("\n " ));
1456
+ $ classSynopsis ->appendChild ($ includeElement );
1457
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1458
+
1459
+ $ includeElement = $ doc ->createElement ("xi:include " );
1460
+ $ includeElement ->setAttribute ("xpointer " , "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class. $ className')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[1]) " );
1461
+ $ classSynopsis ->appendChild ($ includeElement );
1462
+ $ classSynopsis ->appendChild (new DOMText ("\n " ));
1463
+ }
1464
+
1465
+ return $ classSynopsis ;
1466
+ }
1467
+
1468
+ private function createOoElementWithModifiers (DOMDocument $ doc ): DOMElement {
1469
+ return self ::createOoElement ($ doc , $ this , true , false );
1470
+ }
1471
+
1472
+ private static function createOoElement (DOMDocument $ doc , ClassInfo $ classInfo , bool $ withModifiers , bool $ isExtends ): DOMElement {
1473
+ if ($ classInfo ->type !== "class " && $ classInfo ->type !== "interface " ) {
1474
+ throw new Exception ("Class synopsis generation is not implemented for " . $ classInfo ->type );
1475
+ }
1476
+
1477
+ $ ooElement = $ doc ->createElement ('oo ' . $ classInfo ->type );
1478
+ if ($ isExtends ) {
1479
+ $ ooElement = $ doc ->createElement ('modifier ' , 'extends ' );
1480
+ $ ooElement ->appendChild (new DOMText ("\n " ));
1481
+ } elseif ($ withModifiers ) {
1482
+ if ($ classInfo ->flags & Class_::MODIFIER_FINAL ) {
1483
+ $ ooElement ->appendChild ($ doc ->createElement ('modifier ' , 'final ' ));
1484
+ $ ooElement ->appendChild (new DOMText ("\n " ));
1485
+ }
1486
+ if ($ classInfo ->flags & Class_::MODIFIER_ABSTRACT ) {
1487
+ $ ooElement ->appendChild ($ doc ->createElement ('modifier ' , 'abstract ' ));
1488
+ $ ooElement ->appendChild (new DOMText ("\n " ));
1489
+ }
1490
+ }
1491
+
1492
+ $ ooElement ->appendChild (new DOMText ("\n " ));
1493
+ $ nameElement = $ doc ->createElement ($ classInfo ->type . 'name ' , $ classInfo ->name ->toString ());
1494
+ $ ooElement ->appendChild ($ nameElement );
1495
+ $ ooElement ->appendChild (new DOMText ("\n " ));
1496
+
1497
+ return $ ooElement ;
1498
+ }
1499
+
1500
+ public function getClassSynopsisFilename (): string {
1501
+ return strtolower (implode ('- ' , $ this ->name ->parts ));
1502
+ }
1503
+
1504
+ private function appendMethodSynopsisTypeToElement (DOMDocument $ doc , DOMElement $ elementToAppend , Type $ type ) {
1505
+ if (count ($ type ->types ) > 1 ) {
1506
+ $ typeElement = $ doc ->createElement ('type ' );
1507
+ $ typeElement ->setAttribute ("class " , "union " );
1508
+
1509
+ foreach ($ type ->types as $ type ) {
1510
+ $ unionTypeElement = $ doc ->createElement ('type ' , $ type ->name );
1511
+ $ typeElement ->appendChild ($ unionTypeElement );
1512
+ }
1513
+ } else {
1514
+ $ typeElement = $ doc ->createElement ('type ' , $ type ->types [0 ]->name );
1515
+ }
1516
+
1517
+ $ elementToAppend ->appendChild ($ typeElement );
1518
+ }
1335
1519
}
1336
1520
1337
1521
class FileInfo {
@@ -1593,12 +1777,15 @@ function parseProperty(
1593
1777
?DocComment $ comment
1594
1778
): PropertyInfo {
1595
1779
$ docType = false ;
1780
+ $ isReadonly = false ;
1596
1781
1597
1782
if ($ comment ) {
1598
1783
$ tags = parseDocComment ($ comment );
1599
1784
foreach ($ tags as $ tag ) {
1600
1785
if ($ tag ->name === 'var ' ) {
1601
1786
$ docType = true ;
1787
+ } elseif ($ tag ->name === 'readonly ' ) {
1788
+ $ isReadonly = true ;
1602
1789
}
1603
1790
}
1604
1791
}
@@ -1623,7 +1810,8 @@ function parseProperty(
1623
1810
new PropertyName ($ class , $ property ->name ->__toString ()),
1624
1811
$ flags ,
1625
1812
$ propertyType ,
1626
- $ property ->default
1813
+ $ property ->default ,
1814
+ $ isReadonly
1627
1815
);
1628
1816
}
1629
1817
@@ -2069,6 +2257,23 @@ function generateFunctionEntries(?Name $className, array $funcInfos): string {
2069
2257
return $ code ;
2070
2258
}
2071
2259
2260
+ /**
2261
+ * @param ClassInfo[] $classMap
2262
+ * @return array<string, string>
2263
+ */
2264
+ function generateClassSynopses (array $ classMap ): array {
2265
+ $ result = [];
2266
+
2267
+ foreach ($ classMap as $ classInfo ) {
2268
+ $ classSynopsis = $ classInfo ->getClassSynopsisDocument ($ classMap );
2269
+ if ($ classSynopsis !== null ) {
2270
+ $ result [$ classInfo ->getClassSynopsisFilename () . ".xml " ] = $ classSynopsis ;
2271
+ }
2272
+ }
2273
+
2274
+ return $ result ;
2275
+ }
2276
+
2072
2277
/**
2073
2278
* @param FuncInfo[] $funcMap
2074
2279
* @param FuncInfo[] $aliasMap
@@ -2322,15 +2527,24 @@ function initPhpParser() {
2322
2527
}
2323
2528
2324
2529
$ optind = null ;
2325
- $ options = getopt ("fh " , ["force-regeneration " , "parameter-stats " , "help " , "verify " , "generate-methodsynopses " , "replace-methodsynopses " ], $ optind );
2530
+ $ options = getopt (
2531
+ "fh " ,
2532
+ [
2533
+ "force-regeneration " , "parameter-stats " , "help " , "verify " , "generate-classsynopses " , "replace-classsynopses " ,
2534
+ "generate-methodsynopses " , "replace-methodsynopses "
2535
+ ],
2536
+ $ optind
2537
+ );
2326
2538
2327
2539
$ context = new Context ;
2328
2540
$ printParameterStats = isset ($ options ["parameter-stats " ]);
2329
2541
$ verify = isset ($ options ["verify " ]);
2542
+ $ generateClassSynopses = isset ($ options ["generate-classsynopses " ]);
2543
+ $ replaceClassSynopses = isset ($ options ["replace-classsynopses " ]);
2330
2544
$ generateMethodSynopses = isset ($ options ["generate-methodsynopses " ]);
2331
2545
$ replaceMethodSynopses = isset ($ options ["replace-methodsynopses " ]);
2332
2546
$ context ->forceRegeneration = isset ($ options ["f " ]) || isset ($ options ["force-regeneration " ]);
2333
- $ context ->forceParse = $ context ->forceRegeneration || $ printParameterStats || $ verify || $ generateMethodSynopses || $ replaceMethodSynopses ;
2547
+ $ context ->forceParse = $ context ->forceRegeneration || $ printParameterStats || $ verify || $ generateClassSynopses || $ replaceClassSynopses || $ generateMethodSynopses || $ replaceMethodSynopses ;
2334
2548
$ targetMethodSynopses = $ argv [$ optind + 1 ] ?? null ;
2335
2549
if ($ replaceMethodSynopses && $ targetMethodSynopses === null ) {
2336
2550
die ("A target directory must be provided. \n" );
@@ -2375,19 +2589,28 @@ function initPhpParser() {
2375
2589
echo json_encode ($ parameterStats , JSON_PRETTY_PRINT ), "\n" ;
2376
2590
}
2377
2591
2592
+ /** @var ClassInfo[] $classMap */
2593
+ $ classMap = [];
2378
2594
/** @var FuncInfo[] $funcMap */
2379
2595
$ funcMap = [];
2380
2596
/** @var FuncInfo[] $aliasMap */
2381
2597
$ aliasMap = [];
2382
2598
2383
2599
foreach ($ fileInfos as $ fileInfo ) {
2384
- foreach ($ fileInfo ->getAllFuncInfos () as $ funcInfo ) {
2385
- /** @var FuncInfo $funcInfo */
2386
- $ funcMap [$ funcInfo ->name ->__toString ()] = $ funcInfo ;
2600
+ foreach ($ fileInfo ->classInfos as $ classInfo ) {
2601
+ $ classMap [$ classInfo ->name ->__toString ()] = $ classInfo ;
2387
2602
2388
- // TODO: Don't use aliasMap for methodsynopsis?
2389
- if ($ funcInfo ->aliasType === "alias " ) {
2390
- $ aliasMap [$ funcInfo ->alias ->__toString ()] = $ funcInfo ;
2603
+ foreach ($ fileInfo ->funcInfos as $ funcInfo ) {
2604
+ $ funcMap [$ funcInfo ->name ->__toString ()] = $ funcInfo ;
2605
+ }
2606
+
2607
+ foreach ($ classInfo ->funcInfos as $ funcInfo ) {
2608
+ $ funcMap [$ funcInfo ->name ->__toString ()] = $ funcInfo ;
2609
+
2610
+ // TODO: Don't use aliasMap for methodsynopsis?
2611
+ if ($ funcInfo ->aliasType === "alias " ) {
2612
+ $ aliasMap [$ funcInfo ->alias ->__toString ()] = $ funcInfo ;
2613
+ }
2391
2614
}
2392
2615
}
2393
2616
}
@@ -2468,6 +2691,23 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc
2468
2691
}
2469
2692
}
2470
2693
2694
+ if ($ generateClassSynopses ) {
2695
+ $ classSynopsesDirectory = getcwd () . "/classsynopses " ;
2696
+
2697
+ $ classSynopses = generateClassSynopses ($ classMap );
2698
+ if (!empty ($ classSynopses )) {
2699
+ if (!file_exists ($ classSynopsesDirectory )) {
2700
+ mkdir ($ classSynopsesDirectory );
2701
+ }
2702
+
2703
+ foreach ($ classSynopses as $ filename => $ content ) {
2704
+ if (file_put_contents ("$ classSynopsesDirectory/ $ filename " , $ content )) {
2705
+ echo "Saved $ filename \n" ;
2706
+ }
2707
+ }
2708
+ }
2709
+ }
2710
+
2471
2711
if ($ generateMethodSynopses ) {
2472
2712
$ methodSynopsesDirectory = getcwd () . "/methodsynopses " ;
2473
2713
0 commit comments