45
45
use PHPStan \PhpDocParser \Ast \Type \TypeNode ;
46
46
use PHPStan \PhpDocParser \Ast \Type \UnionTypeNode ;
47
47
use PHPStan \PhpDocParser \Lexer \Lexer ;
48
+ use PHPStan \PhpDocParser \Parser \ConstExprParser ;
49
+ use PHPStan \PhpDocParser \Parser \PhpDocParser ;
48
50
use PHPStan \PhpDocParser \Parser \TokenIterator ;
51
+ use PHPStan \PhpDocParser \Parser \TypeParser ;
49
52
use PHPStan \PhpDocParser \ParserConfig ;
53
+ use PHPUnit \Framework \TestCase ;
54
+ use function array_map ;
50
55
use function array_pop ;
56
+ use function array_slice ;
51
57
use function array_splice ;
52
58
use function array_unshift ;
53
59
use function array_values ;
54
60
use function assert ;
55
61
use function count ;
62
+ use function implode ;
63
+ use function preg_match ;
64
+ use function preg_replace_callback ;
65
+ use function preg_split ;
66
+ use function str_repeat ;
67
+ use function str_replace ;
68
+ use function strlen ;
56
69
use const PHP_EOL ;
57
70
58
- class PrinterTest extends PrinterTestBase
71
+ class PrinterTest extends TestCase
59
72
{
60
73
74
+ private TypeParser $ typeParser ;
75
+
76
+ private PhpDocParser $ phpDocParser ;
77
+
78
+ protected function setUp (): void
79
+ {
80
+ $ config = new ParserConfig (['lines ' => true , 'indexes ' => true ]);
81
+ $ constExprParser = new ConstExprParser ($ config );
82
+ $ this ->typeParser = new TypeParser ($ config , $ constExprParser );
83
+ $ this ->phpDocParser = new PhpDocParser (
84
+ $ config ,
85
+ $ this ->typeParser ,
86
+ $ constExprParser ,
87
+ );
88
+ }
89
+
61
90
/**
62
91
* @return iterable<array{string, string, NodeVisitor}>
63
92
*/
@@ -2177,14 +2206,385 @@ public function enterNode(Node $node)
2177
2206
2178
2207
},
2179
2208
];
2209
+
2210
+ $ singleCommentLineAddFront = new class extends AbstractNodeVisitor {
2211
+
2212
+ public function enterNode (Node $ node )
2213
+ {
2214
+ if ($ node instanceof ArrayShapeNode) {
2215
+ array_unshift ($ node ->items , PrinterTest::withComment (
2216
+ new ArrayShapeItemNode (null , false , new IdentifierTypeNode ('float ' )),
2217
+ '// A fractional number ' ,
2218
+ ));
2219
+ }
2220
+
2221
+ return $ node ;
2222
+ }
2223
+
2224
+ };
2225
+
2226
+ yield [
2227
+ self ::nowdoc ('
2228
+ /**
2229
+ * @param array{} $foo
2230
+ */ ' ),
2231
+ self ::nowdoc ('
2232
+ /**
2233
+ * @param array{float} $foo
2234
+ */ ' ),
2235
+ $ singleCommentLineAddFront ,
2236
+ ];
2237
+
2238
+ yield [
2239
+ self ::nowdoc ('
2240
+ /**
2241
+ * @param array{string} $foo
2242
+ */ ' ),
2243
+ self ::nowdoc ('
2244
+ /**
2245
+ * @param array{// A fractional number
2246
+ * float,
2247
+ * string} $foo
2248
+ */ ' ),
2249
+ $ singleCommentLineAddFront ,
2250
+ ];
2251
+
2252
+ yield [
2253
+ self ::nowdoc ('
2254
+ /**
2255
+ * @param array{
2256
+ * string,int} $foo
2257
+ */ ' ),
2258
+ self ::nowdoc ('
2259
+ /**
2260
+ * @param array{
2261
+ * // A fractional number
2262
+ * float,
2263
+ * string,int} $foo
2264
+ */ ' ),
2265
+ $ singleCommentLineAddFront ,
2266
+ ];
2267
+
2268
+ yield [
2269
+ self ::nowdoc ('
2270
+ /**
2271
+ * @param array{
2272
+ * string,
2273
+ * int
2274
+ * } $foo
2275
+ */ ' ),
2276
+ self ::nowdoc ('
2277
+ /**
2278
+ * @param array{
2279
+ * // A fractional number
2280
+ * float,
2281
+ * string,
2282
+ * int
2283
+ * } $foo
2284
+ */ ' ),
2285
+ $ singleCommentLineAddFront ,
2286
+ ];
2287
+
2288
+ $ singleCommentLineAddMiddle = new class extends AbstractNodeVisitor {
2289
+
2290
+ public function enterNode (Node $ node )
2291
+ {
2292
+ $ newItem = PrinterTest::withComment (
2293
+ new ArrayShapeItemNode (null , false , new IdentifierTypeNode ('float ' )),
2294
+ '// A fractional number ' ,
2295
+ );
2296
+
2297
+ if ($ node instanceof ArrayShapeNode) {
2298
+ if (count ($ node ->items ) === 0 ) {
2299
+ $ node ->items [] = $ newItem ;
2300
+ } else {
2301
+ array_splice ($ node ->items , 1 , 0 , [$ newItem ]);
2302
+ }
2303
+ }
2304
+
2305
+ return $ node ;
2306
+ }
2307
+
2308
+ };
2309
+
2310
+ yield [
2311
+ self ::nowdoc ('
2312
+ /**
2313
+ * @param array{} $foo
2314
+ */ ' ),
2315
+ self ::nowdoc ('
2316
+ /**
2317
+ * @param array{float} $foo
2318
+ */ ' ),
2319
+ $ singleCommentLineAddMiddle ,
2320
+ ];
2321
+
2322
+ yield [
2323
+ self ::nowdoc ('
2324
+ /**
2325
+ * @param array{string} $foo
2326
+ */ ' ),
2327
+ self ::nowdoc ('
2328
+ /**
2329
+ * @param array{string,
2330
+ * // A fractional number
2331
+ * float} $foo
2332
+ */ ' ),
2333
+ $ singleCommentLineAddMiddle ,
2334
+ ];
2335
+
2336
+ yield [
2337
+ self ::nowdoc ('
2338
+ /**
2339
+ * @param array{
2340
+ * string,int} $foo
2341
+ */ ' ),
2342
+ self ::nowdoc ('
2343
+ /**
2344
+ * @param array{
2345
+ * string,
2346
+ * // A fractional number
2347
+ * float,int} $foo
2348
+ */ ' ),
2349
+ $ singleCommentLineAddMiddle ,
2350
+ ];
2351
+
2352
+ yield [
2353
+ self ::nowdoc ('
2354
+ /**
2355
+ * @param array{
2356
+ * string,
2357
+ * int
2358
+ * } $foo
2359
+ */ ' ),
2360
+ self ::nowdoc ('
2361
+ /**
2362
+ * @param array{
2363
+ * string,
2364
+ * // A fractional number
2365
+ * float,
2366
+ * int
2367
+ * } $foo
2368
+ */ ' ),
2369
+ $ singleCommentLineAddMiddle ,
2370
+ ];
2371
+
2372
+ $ addCommentToCallableParamsFront = new class extends AbstractNodeVisitor {
2373
+
2374
+ public function enterNode (Node $ node )
2375
+ {
2376
+ if ($ node instanceof CallableTypeNode) {
2377
+ array_unshift ($ node ->parameters , PrinterTest::withComment (
2378
+ new CallableTypeParameterNode (new IdentifierTypeNode ('Foo ' ), false , false , '$foo ' , false ),
2379
+ '// never pet a burning dog ' ,
2380
+ ));
2381
+ }
2382
+
2383
+ return $ node ;
2384
+ }
2385
+
2386
+ };
2387
+
2388
+ yield [
2389
+ self ::nowdoc ('
2390
+ /**
2391
+ * @param callable(Bar $bar): int $a
2392
+ */ ' ),
2393
+ self ::nowdoc ('
2394
+ /**
2395
+ * @param callable(// never pet a burning dog
2396
+ * Foo $foo,
2397
+ * Bar $bar): int $a
2398
+ */ ' ),
2399
+ $ addCommentToCallableParamsFront ,
2400
+ ];
2401
+
2402
+ $ addCommentToCallableParamsMiddle = new class extends AbstractNodeVisitor {
2403
+
2404
+ public function enterNode (Node $ node )
2405
+ {
2406
+ if ($ node instanceof CallableTypeNode) {
2407
+ $ node ->parameters [] = PrinterTest::withComment (
2408
+ new CallableTypeParameterNode (new IdentifierTypeNode ('Bar ' ), false , false , '$bar ' , false ),
2409
+ '// never pet a burning dog ' ,
2410
+ );
2411
+ }
2412
+
2413
+ return $ node ;
2414
+ }
2415
+
2416
+ };
2417
+
2418
+ yield [
2419
+ self ::nowdoc ('
2420
+ /**
2421
+ * @param callable(Foo $foo): int $a
2422
+ */ ' ),
2423
+ self ::nowdoc ('
2424
+ /**
2425
+ * @param callable(Foo $foo,
2426
+ * // never pet a burning dog
2427
+ * Bar $bar): int $a
2428
+ */ ' ),
2429
+ $ addCommentToCallableParamsMiddle ,
2430
+ ];
2431
+
2432
+ $ addCommentToObjectShapeItemFront = new class extends AbstractNodeVisitor {
2433
+
2434
+ public function enterNode (Node $ node )
2435
+ {
2436
+ if ($ node instanceof ObjectShapeNode) {
2437
+ array_unshift ($ node ->items , PrinterTest::withComment (
2438
+ new ObjectShapeItemNode (new IdentifierTypeNode ('foo ' ), false , new IdentifierTypeNode ('float ' )),
2439
+ '// A fractional number ' ,
2440
+ ));
2441
+ }
2442
+
2443
+ return $ node ;
2444
+ }
2445
+
2446
+ };
2447
+
2448
+ yield [
2449
+ self ::nowdoc ('
2450
+ /**
2451
+ * @param object{bar: string} $foo
2452
+ */ ' ),
2453
+ self ::nowdoc ('
2454
+ /**
2455
+ * @param object{// A fractional number
2456
+ * foo: float,
2457
+ * bar: string} $foo
2458
+ */ ' ),
2459
+ $ addCommentToObjectShapeItemFront ,
2460
+ ];
2461
+
2462
+ yield [
2463
+ self ::nowdoc ('
2464
+ /**
2465
+ * @param object{
2466
+ * bar:string,naz:int} $foo
2467
+ */ ' ),
2468
+ self ::nowdoc ('
2469
+ /**
2470
+ * @param object{
2471
+ * // A fractional number
2472
+ * foo: float,
2473
+ * bar:string,naz:int} $foo
2474
+ */ ' ),
2475
+ $ addCommentToObjectShapeItemFront ,
2476
+ ];
2477
+
2478
+ yield [
2479
+ self ::nowdoc ('
2480
+ /**
2481
+ * @param object{
2482
+ * bar:string,
2483
+ * naz:int
2484
+ * } $foo
2485
+ */ ' ),
2486
+ self ::nowdoc ('
2487
+ /**
2488
+ * @param object{
2489
+ * // A fractional number
2490
+ * foo: float,
2491
+ * bar:string,
2492
+ * naz:int
2493
+ * } $foo
2494
+ */ ' ),
2495
+ $ addCommentToObjectShapeItemFront ,
2496
+ ];
2497
+
2498
+ $ addCommentToObjectShapeItemMiddle = new class extends AbstractNodeVisitor {
2499
+
2500
+ public function enterNode (Node $ node )
2501
+ {
2502
+ if ($ node instanceof ObjectShapeNode) {
2503
+ $ newItem = PrinterTest::withComment (
2504
+ new ObjectShapeItemNode (new IdentifierTypeNode ('bar ' ), false , new IdentifierTypeNode ('float ' )),
2505
+ '// A fractional number ' ,
2506
+ );
2507
+ if (count ($ node ->items ) === 0 ) {
2508
+ $ node ->items [] = $ newItem ;
2509
+ } else {
2510
+ array_splice ($ node ->items , 1 , 0 , [$ newItem ]);
2511
+ }
2512
+ }
2513
+
2514
+ return $ node ;
2515
+ }
2516
+
2517
+ };
2518
+
2519
+ yield [
2520
+ self ::nowdoc ('
2521
+ /**
2522
+ * @param object{} $foo
2523
+ */ ' ),
2524
+ self ::nowdoc ('
2525
+ /**
2526
+ * @param object{bar: float} $foo
2527
+ */ ' ),
2528
+ $ addCommentToObjectShapeItemMiddle ,
2529
+ ];
2530
+
2531
+ yield [
2532
+ self ::nowdoc ('
2533
+ /**
2534
+ * @param object{foo:string} $foo
2535
+ */ ' ),
2536
+ self ::nowdoc ('
2537
+ /**
2538
+ * @param object{foo:string,
2539
+ * // A fractional number
2540
+ * bar: float} $foo
2541
+ */ ' ),
2542
+ $ addCommentToObjectShapeItemMiddle ,
2543
+ ];
2544
+
2545
+ yield [
2546
+ self ::nowdoc ('
2547
+ /**
2548
+ * @param object{
2549
+ * foo:string,naz:int} $foo
2550
+ */ ' ),
2551
+ self ::nowdoc ('
2552
+ /**
2553
+ * @param object{
2554
+ * foo:string,
2555
+ * // A fractional number
2556
+ * bar: float,naz:int} $foo
2557
+ */ ' ),
2558
+ $ addCommentToObjectShapeItemMiddle ,
2559
+ ];
2560
+
2561
+ yield [
2562
+ self ::nowdoc ('
2563
+ /**
2564
+ * @param object{
2565
+ * foo:string,
2566
+ * naz:int
2567
+ * } $foo
2568
+ */ ' ),
2569
+ self ::nowdoc ('
2570
+ /**
2571
+ * @param object{
2572
+ * foo:string,
2573
+ * // A fractional number
2574
+ * bar: float,
2575
+ * naz:int
2576
+ * } $foo
2577
+ */ ' ),
2578
+ $ addCommentToObjectShapeItemMiddle ,
2579
+ ];
2180
2580
}
2181
2581
2182
2582
/**
2183
2583
* @dataProvider dataPrintFormatPreserving
2184
2584
*/
2185
2585
public function testPrintFormatPreserving (string $ phpDoc , string $ expectedResult , NodeVisitor $ visitor ): void
2186
2586
{
2187
- $ config = new ParserConfig ([]);
2587
+ $ config = new ParserConfig ([' lines ' => true , ' indexes ' => true , ' comments ' => true ]);
2188
2588
$ lexer = new Lexer ($ config );
2189
2589
$ tokens = new TokenIterator ($ lexer ->tokenize ($ phpDoc ));
2190
2590
$ phpDocNode = $ this ->phpDocParser ->parse ($ tokens );
@@ -2206,6 +2606,30 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult
2206
2606
);
2207
2607
}
2208
2608
2609
+ private function unsetAttributes (Node $ node ): Node
2610
+ {
2611
+ $ visitor = new class extends AbstractNodeVisitor {
2612
+
2613
+ public function enterNode (Node $ node )
2614
+ {
2615
+ $ node ->setAttribute (Attribute::START_LINE , null );
2616
+ $ node ->setAttribute (Attribute::END_LINE , null );
2617
+ $ node ->setAttribute (Attribute::START_INDEX , null );
2618
+ $ node ->setAttribute (Attribute::END_INDEX , null );
2619
+ $ node ->setAttribute (Attribute::ORIGINAL_NODE , null );
2620
+ $ node ->setAttribute (Attribute::COMMENTS , null );
2621
+
2622
+ return $ node ;
2623
+ }
2624
+
2625
+ };
2626
+
2627
+ $ traverser = new NodeTraverser ([$ visitor ]);
2628
+
2629
+ /** @var PhpDocNode */
2630
+ return $ traverser ->traverse ([$ node ])[0 ];
2631
+ }
2632
+
2209
2633
/**
2210
2634
* @return iterable<array{TypeNode, string}>
2211
2635
*/
@@ -2388,4 +2812,51 @@ public function testPrintPhpDocNode(PhpDocNode $node, string $expectedResult): v
2388
2812
);
2389
2813
}
2390
2814
2815
+ /**
2816
+ * @template TNode of Node
2817
+ * @param TNode $node
2818
+ * @return TNode
2819
+ */
2820
+ public static function withComment (Node $ node , string $ comment ): Node
2821
+ {
2822
+ $ node ->setAttribute (Attribute::COMMENTS , [new Comment ($ comment )]);
2823
+ return $ node ;
2824
+ }
2825
+
2826
+ public static function nowdoc (string $ str ): string
2827
+ {
2828
+ $ lines = preg_split ('/ \\n/ ' , $ str );
2829
+
2830
+ if ($ lines === false ) {
2831
+ return '' ;
2832
+ }
2833
+
2834
+ if (count ($ lines ) < 2 ) {
2835
+ return '' ;
2836
+ }
2837
+
2838
+ // Toss out the first line
2839
+ $ lines = array_slice ($ lines , 1 , count ($ lines ) - 1 );
2840
+
2841
+ // normalize any tabs to spaces
2842
+ $ lines = array_map (static fn ($ line ) => preg_replace_callback ('/(\t+)/m ' , static function ($ matches ) {
2843
+ $ fixed = str_repeat (' ' , strlen ($ matches [1 ]));
2844
+ return $ fixed ;
2845
+ }, $ line ), $ lines );
2846
+
2847
+ // take the ws from the first line and subtract them from all lines
2848
+ $ matches = [];
2849
+
2850
+ if (preg_match ('/(^[ \t]+)/ ' , $ lines [0 ] ?? '' , $ matches ) !== 1 ) {
2851
+ return '' ;
2852
+ }
2853
+
2854
+ $ numLines = count ($ lines );
2855
+ for ($ i = 0 ; $ i < $ numLines ; ++$ i ) {
2856
+ $ lines [$ i ] = str_replace ($ matches [0 ], '' , $ lines [$ i ] ?? '' );
2857
+ }
2858
+
2859
+ return implode ("\n" , $ lines );
2860
+ }
2861
+
2391
2862
}
0 commit comments