Completed
Push — develop ( bd0ea7...cbc256 )
by Mike
08:51
created

tests/features/bootstrap/Ast/ApiContext.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 *  For the full copyright and license information, please view the LICENSE
6
 *  file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2018 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Behat\Contexts\Ast;
14
15
use Behat\Behat\Context\Context;
16
use Behat\Behat\Tester\Exception\PendingException;
17
use Behat\Gherkin\Node\PyStringNode;
18
use phpDocumentor\Descriptor\ArgumentDescriptor;
19
use phpDocumentor\Descriptor\ClassDescriptor;
20
use phpDocumentor\Descriptor\Collection;
21
use phpDocumentor\Descriptor\ConstantDescriptor;
22
use phpDocumentor\Descriptor\DescriptorAbstract;
23
use phpDocumentor\Descriptor\FileDescriptor;
24
use phpDocumentor\Descriptor\FunctionDescriptor;
25
use phpDocumentor\Descriptor\MethodDescriptor;
26
use phpDocumentor\Descriptor\NamespaceDescriptor;
27
use phpDocumentor\Descriptor\PropertyDescriptor;
28
use phpDocumentor\Descriptor\Tag\ParamDescriptor;
29
use phpDocumentor\Descriptor\Tag\ReturnDescriptor;
30
use phpDocumentor\Descriptor\Tag\VersionDescriptor;
31
use phpDocumentor\Descriptor\TraitDescriptor;
32
use phpDocumentor\Reflection\Php\File;
33
use Webmozart\Assert\Assert;
34
35
class ApiContext extends BaseContext implements Context
36
{
37
    /**
38
     * @Then /^the AST has a class named "([^"]*)" in file "([^"]*)"$/
39
     * @throws \Exception
40
     */
41
    public function theASTHasAclassNamedInFile($class, $file)
42
    {
43
        $ast = $this->getAst();
44
45
        $file = $this->processFilePath($file);
46
        /** @var FileDescriptor $fileDescriptor */
47
        $fileDescriptor = $ast->getFiles()->get($file);
48
49
        /** @var ClassDescriptor $classDescriptor */
50
        foreach ($fileDescriptor->getClasses() as $classDescriptor) {
51
            if ($classDescriptor->getName() === $class) {
52
                return;
53
            }
54
        }
55
56
        throw new \Exception(sprintf('Didn\'t find expected class "%s" in "%s"', $class, $file));
57
    }
58
59
    /**
60
     * @Then /^the AST doesn't have a class "([^"]*)"$/
61
     * @throws \Exception
62
     */
63
    public function theASTDoesnTHaveAClass($className)
64
    {
65
        $ast = $this->getAst();
66
        foreach ($ast->getFiles() as $file) {
67
            foreach ($file->getClasses() as $classDescriptor) {
68
                if ($classDescriptor->getName() === $className) {
69
                    throw new \Exception('Found unexpected class');
70
                }
71
            }
72
        }
73
    }
74
75
    /**
76
     * @Then /^the class named "([^"]*)" is in the default package$/
77
     * @throws \Exception
78
     */
79
    public function theASTHasAClassInDefaultPackage($class)
80
    {
81
        $class = $this->findClassByName($class);
82
83
        Assert::eq('Default', $class->getPackage()->getName());
84
    }
85
86
    /**
87
     * @Then /^the AST has a trait named "([^"]*)" in file "([^"]*)"$/
88
     * @throws \Exception
89
     */
90
    public function theASTHasATraitNamedInFile($trait, $file)
91
    {
92
        $ast = $this->getAst();
93
94
        $file = $this->processFilePath($file);
95
        /** @var FileDescriptor $fileDescriptor */
96
        $fileDescriptor = $ast->getFiles()->get($file);
97
98
        /** @var TraitDescriptor $classDescriptor */
99
        foreach ($fileDescriptor->getTraits() as $classDescriptor) {
100
            if ($classDescriptor->getName() === $trait) {
101
                return;
102
            }
103
        }
104
105
        throw new \Exception(sprintf('Didn\'t find expected trait "%s" in "%s"', $trait, $file));
106
    }
107
108
    /**
109
     * @Then the class named ":class" has docblock with content:
110
     */
111
    public function classHasDocblockWithContent($class, PyStringNode $expectedContent)
112
    {
113
        $class = $this->findClassByName($class);
114
115
        Assert::eq($expectedContent->getRaw(), $class->getDescription());
116
    }
117
118
    /**
119
     * @Then class ":classFqsen" has :docElement:
120
     * @throws Exception
121
     */
122
    public function classHasDocblockContent($classFqsen, $docElement, PyStringNode $value)
123
    {
124
        $class = $this->findClassByFqsen($classFqsen);
125
126
        $method = 'get' . $docElement;
127
128
        Assert::eq($value->getRaw(), $class->{$method}());
129
    }
130
131
    /**
132
     * @Then class ":classFqsen" has :elementType :elementName with :docElement:
133
     */
134
    public function classHasElementWithDocblockContent($classFqsen, $elementType, $elementName, $docElement, PyStringNode $value)
135
    {
136
        $class = $this->findClassByFqsen($classFqsen);
137
138
        switch ($elementType) {
139
            case 'method':
140
            case 'constant':
141
                $method = $method = 'get' . $elementType . 's';
142
                break;
143
            case 'property':
144
                $method = 'getProperties';
145
                break;
146
            default:
147
                $method = 'get' . $elementType;
148
                break;
149
        }
150
151
        $element = $class-> {$method}()->get($elementName);
152
        $method = 'get' . $docElement;
153
        $actual = $element->{$method}();
154
155
        Assert::eq($value->getRaw(), $actual, sprintf('"%s" does not match "%s"', $actual, $value->getRaw()));
156
    }
157
158
    /**
159
     * @Then class ":classFqsen" has version :value
160
     */
161
    public function classHasVersion($classFqsen, $value)
162
    {
163
        $class = $this->findClassByFqsen($classFqsen);
164
165
        /** @var VersionDescriptor $tag */
166
        foreach ($class->getVersion() as $tag) {
167
            if ($tag->getVersion() === $value) {
168
                return;
169
            }
170
        }
171
172
        Assert::false(true, sprintf('Didn\'t find expected version "%s"', $value));
173
    }
174
175
    /**
176
     * @Then class ":classFqsen" without tag :tagName
177
     */
178
    public function classWithoutTag($classFqsen, $tagName)
179
    {
180
        $this->classHasTag($classFqsen, $tagName, 0);
181
    }
182
183
    /**
184
     * @param string $classFqsen
185
     * @param string $tagName
186
     * @param int $expectedNumber
187
     * @Then class ":classFqsen" has exactly :expectedNumber tag :tagName
188
     */
189
    public function classHasTag($classFqsen, $tagName, $expectedNumber)
190
    {
191
        $class = $this->findClassByFqsen($classFqsen);
192
        static::AssertTagCount($class, $tagName, $expectedNumber);
193
    }
194
195
    /**
196
     * @param string $classFqsen
197
     * @param string $tagName
198
     * @param string $method
199
     * @Then class ":classFqsen" has a method named :method without tag :tagName
200
     */
201
    public function classHasMethodWithoutTag($classFqsen, $tagName, $method)
202
    {
203
        $this->classHasMethodWithExpectedCountTag($classFqsen, $tagName, $method, 0);
204
    }
205
206
    /**
207
     * @param string $classFqsen
208
     * @param string $tagName
209
     * @param string $methodName
210
     * @Then class ":classFqsen" has a method named :method with exactly :expected tag :tagName
211
     */
212
    public function classHasMethodWithExpectedCountTag($classFqsen, $tagName, $methodName, $expectedCount)
213
    {
214
        $class = $this->findClassByFqsen($classFqsen);
215
        $method = $class->getMethods()->get($methodName);
216
217
        static::AssertTagCount($method, $tagName, $expectedCount);
218
    }
219
220
    /**
221
     * @param string $classFqsen
222
     * @param string $methodName
223
     * @Then class ":classFqsen" has a method :method with argument ":argument is variadic
224
     */
225
    public function classHasMethodWithArgumentVariadic($classFqsen, $methodName, $argument)
226
    {
227
        $class = $this->findClassByFqsen($classFqsen);
228
        /** @var MethodDescriptor $method */
229
        $method = $class->getMethods()->get($methodName);
230
        Assert::keyExists($method->getArguments()->getAll(), $argument);
231
        /** @var ArgumentDescriptor $argumentD */
232
        $argumentD = $method->getArguments()[$argument];
0 ignored issues
show
$argumentD is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
233
234
        //TODO: enable this check when we support variadic arguments.
235
        //Assert::true($argumentD->isVariadic(), 'Expected argument to be variadic');
0 ignored issues
show
Unused Code Comprehensibility introduced by Jaapio
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
236
    }
237
238
    /**
239
     * @param string $classFqsen
240
     * @param string $methodName
241
     * @Then class ":classFqsen" has a method :method
242
     */
243
    public function classHasMethod($classFqsen, $methodName)
244
    {
245
        $class = $this->findClassByFqsen($classFqsen);
246
        /** @var MethodDescriptor $method */
247
        $method = $class->getMethods()->get($methodName, null);
248
        $methodNames = implode(', ', array_keys($class->getMethods()->getAll()));
249
250
        Assert::isInstanceOf(
251
            $method,
252
            MethodDescriptor::class,
253
            "Class $classFqsen does not have a method $methodName, it does have the methods: $methodNames"
254
        );
255
        Assert::eq($methodName, $method->getName());
256
    }
257
258
    /**
259
     * @param string $classFqsen
260
     * @param string $propertyName
261
     * @Then class ":classFqsen" has a property :property
262
     */
263
    public function classHasProperty($classFqsen, $propertyName)
264
    {
265
        $class = $this->findClassByFqsen($classFqsen);
266
        /** @var PropertyDescriptor $property */
267
        $property = $class->getProperties()->get($propertyName, null);
268
        Assert::isInstanceOf($property, PropertyDescriptor::class);
269
        Assert::eq($propertyName, $property->getName());
270
    }
271
272
    /**
273
     * @param string $classFqsen
274
     * @param string $methodName
275
     * @param string $argument
276
     * @param string $type
277
     * @Then class ":classFqsen" has a method :method with argument :argument of type ":type"
278
     */
279
    public function classHasMethodWithArgumentOfType($classFqsen, $methodName, $argument, $type)
280
    {
281
        $class = $this->findClassByFqsen($classFqsen);
282
        /** @var MethodDescriptor $method */
283
        $method = $class->getMethods()->get($methodName);
284
        Assert::keyExists($method->getArguments()->getAll(), $argument);
285
        /** @var ArgumentDescriptor $argumentDescriptor */
286
        $argumentDescriptor = $method->getArguments()[$argument];
287
288
        Assert::eq($type, (string) $argumentDescriptor->getType());
289
    }
290
291
    /**
292
     * @param string $classFqsen
293
     * @param string $methodName
294
     * @param string $param
295
     * @param string $type
296
     * @Then class ":classFqsen" has a method :method with param :param of type ":type"
297
     */
298
    public function classHasMethodWithParamOfType($classFqsen, $methodName, $param, $type)
299
    {
300
        $class = $this->findClassByFqsen($classFqsen);
301
        /** @var MethodDescriptor $method */
302
        $method = $class->getMethods()->get($methodName);
303
        /** @var ParamDescriptor $paramDescriptor */
304
        foreach ($method->getParam() as $paramDescriptor) {
305
            if ($paramDescriptor->getName() === $param) {
306
                Assert::eq($type, (string) $paramDescriptor->getType());
307
            }
308
        }
309
    }
310
311
    /**
312
     * @param string $classFqsen
313
     * @param string $constantName
314
     * @Then class ":classFqsen" has a constant :constantName
315
     */
316
    public function classHasConstant($classFqsen, $constantName)
317
    {
318
        /** @var ClassDescriptor $class */
319
        $class = $this->findClassByFqsen($classFqsen);
320
        $constant = $class->getConstants()->get($constantName);
321
        Assert::isInstanceOf($constant, ConstantDescriptor::class);
322
    }
323
324
    /**
325
     * @param string $className
326
     * @return ClassDescriptor
327
     * @throws \Exception
328
     */
329
    private function findClassByName($className)
330
    {
331
        $ast = $this->getAst();
332
        foreach ($ast->getFiles() as $file) {
333
            foreach ($file->getClasses() as $classDescriptor) {
334
                if ($classDescriptor->getName() === $className) {
335
                    return $classDescriptor;
336
                }
337
            }
338
        }
339
340
        throw new \Exception(sprintf('Didn\'t find expected class "%s"', $className));
341
    }
342
343
    /**
344
     * @param string $tagName
345
     * @param int $expectedNumber
346
     * @param DescriptorAbstract $element
347
     */
348
    private static function AssertTagCount($element, $tagName, $expectedNumber)
349
    {
350
        /** @var Collection $tagCollection */
351
        $tagCollection = $element->getTags()->get($tagName, new Collection());
352
353
        Assert::eq((int) $expectedNumber, $tagCollection->count());
354
        if ($expectedNumber > 0) {
355
            Assert::eq($tagName, $tagCollection[0]->getName());
356
        }
357
    }
358
359
    /**
360
     * @Then /^the ast has a file named "([^"]*)" with a summary:$/
361
     * @throws \Exception
362
     */
363
    public function theAstHasAFileNamedWithASummary(string $fileName, PyStringNode $string)
364
    {
365
        $ast = $this->getAst();
366
        /** @var FileDescriptor $file */
367
        $file = $ast->getFiles()->get($fileName);
368
369
        Assert::eq($string->getRaw(), $file->getSummary());
370
    }
371
372
    /**
373
     * @param string $classFqsen
374
     * @param string $methodName
375
     * @throws Exception
376
     * @Then class ":classFqsen" has a method :method with returntype :returnType
377
     * @Then class ":classFqsen" has a method :method with returntype :returnType without description
378
     */
379
    public function classHasMethodWithReturnType($classFqsen, $methodName, $returnType)
380
    {
381
        $response = $this->findMethodResponse($classFqsen, $methodName);
382
383
        Assert::eq((string) $response->getType(), $returnType);
384
        Assert::eq((string) $response->getDescription(), '');
385
    }
386
387
    /**
388
     * @param string $classFqsen
389
     * @param string $methodName
390
     * @throws Exception
391
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType
392
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType without description
393
     */
394
    public function classHasMagicMethodWithReturnType($classFqsen, $methodName, $returnType)
395
    {
396
        $response = $this->findMagicMethodResponse($classFqsen, $methodName);
397
398
        Assert::eq((string) $response->getType(), $returnType);
399
        Assert::eq((string) $response->getDescription(), '');
400
    }
401
402
    /**
403
     * @param string $classFqsen
404
     * @param string $methodName
405
     * @throws Exception
406
     * @Then class ":classFqsen" has a method :method with returntype :returnType with description:
407
     */
408
    public function classHasMethodWithReturnTypeAndDescription($classFqsen, $methodName, $returnType, PyStringNode $description)
409
    {
410
        $response = $this->findMethodResponse($classFqsen, $methodName);
411
412
        Assert::eq($returnType, (string) $response->getType());
413
        Assert::eq($description, (string) $response->getDescription());
414
    }
415
416
    /**
417
     * @Then class ":classFqsen" has a method ":method" without returntype
418
     * @throws \Exception
419
     */
420
    public function classReturnTaggetReturnWithoutAnyWithoutReturntype($classFqsen, $methodName)
421
    {
422
        $response = $this->findMethodResponse($classFqsen, $methodName);
423
        Assert::eq('mixed', (string) $response->getType());
424
        Assert::eq('', $response->getDescription());
425
    }
426
427
    /**
428
     * @throws Exception
429
     * @Then has function :fqsen with returntype :returnType
430
     * @Then has function :fqsen with returntype :returnType without description
431
     */
432
    public function functionWithReturnType($fqsen, $returnType)
433
    {
434
        $response = $this->findFunctionResponse($fqsen);
435
436
        Assert::eq($returnType, (string) $response->getType());
437
        Assert::eq('', (string) $response->getDescription());
438
    }
439
440
    /**
441
     * @throws Exception
442
     * @Then has function :fqsen with returntype :returnType with description:
443
     */
444
    public function functionWithReturnTypeAndDescription($fqsen, $returnType, PyStringNode $description)
445
    {
446
        $response = $this->findFunctionResponse($fqsen);
447
448
        Assert::eq($returnType, (string) $response->getType());
449
        Assert::eq($description, (string) $response->getDescription());
450
    }
451
452
    /**
453
     * @Then has function :fqsen without returntype
454
     * @throws \Exception
455
     */
456
    public function functionWithoutReturntype($fqsen)
457
    {
458
        $response = $this->findFunctionResponse($fqsen);
459
        Assert::eq('mixed', (string) $response->getType());
460
        Assert::eq('', $response->getDescription());
461
    }
462
463
    /**
464
     * @throws Exception
465
     */
466
    private function findMethodResponse($classFqsen, $methodName): ReturnDescriptor
467
    {
468
        $class = $this->findClassByFqsen($classFqsen);
469
        /** @var MethodDescriptor $method */
470
        $method = $class->getMethods()->get($methodName, null);
471
        Assert::isInstanceOf($method, MethodDescriptor::class);
472
        Assert::eq($methodName, $method->getName());
473
474
        return $method->getResponse();
475
    }
476
477
    /**
478
     * @throws Exception
479
     */
480
    private function findMagicMethodResponse($classFqsen, $methodName): ReturnDescriptor
481
    {
482
        $class = $this->findClassByFqsen($classFqsen);
483
        $match = null;
484
485
        /** @var MethodDescriptor $method */
486
        foreach ($class->getMagicMethods() as $method) {
487
            if ($method->getName() === $methodName) {
488
                $match = $method;
489
            }
490
        }
491
492
        Assert::isInstanceOf($match, MethodDescriptor::class);
493
        Assert::eq($methodName, $match->getName());
494
495
        return $match->getResponse();
496
    }
497
498
    /**
499
     * @throws Exception
500
     */
501
    private function findFunctionResponse(string $fqsen): ReturnDescriptor
502
    {
503
        $function = $this->findFunctionByFqsen($fqsen);
504
        return $function->getResponse();
505
    }
506
507
    /**
508
     * @Then class ":classFqsen" has a magic method :method with argument ":argument" of type :type
509
     */
510
    public function classHasMagicMethodWithArgument($classFqsen, $methodName, $argument, $type)
511
    {
512
        $class = $this->findClassByFqsen($classFqsen);
513
        $match = null;
514
515
        /** @var MethodDescriptor $method */
516
        foreach ($class->getMagicMethods() as $method) {
517
            if ($method->getName() === $methodName) {
518
                $match = $method;
519
            }
520
        }
521
522
        Assert::isInstanceOf($match, MethodDescriptor::class);
523
        Assert::notNull($match->getArguments()->get($argument));
524
    }
525
526
    /**
527
     * @Then /^(\d+) files should be parsed$/
528
     */
529
    public function filesShouldBeParsed($count)
530
    {
531
        Assert::same((int) $count, $this->getAst()->getFiles()->count());
532
    }
533
534
    /**
535
     * @Then /^the ast has a function named "([^"]*)"$/
536
     */
537
    public function theAstHasAFunctionNamed($functionName)
538
    {
539
        Assert::isInstanceOf(
540
            $this->getAst()->getIndexes()->get('functions')->get($functionName . '()'),
541
            FunctionDescriptor::class
542
        );
543
    }
544
545
    /**
546
     * @Then argument :argument of function ":functionName" has no defined type and description is:
547
     */
548
    public function argumentOfFunctionHasNoTypeAndHasDescripion($argument, $functionName, PyStringNode $description)
549
    {
550
        /** @var FunctionDescriptor $functionDescriptor */
551
        $functionDescriptor = $this->getAst()->getIndexes()->get('functions')->get($functionName . '()');
552
        Assert::isInstanceOf(
553
            $functionDescriptor,
554
            FunctionDescriptor::class
555
        );
556
557
        /** @var ArgumentDescriptor $argumentDescriptor */
558
        $argumentDescriptor = $functionDescriptor->getArguments()->get($argument);
559
560
        Assert::isInstanceOf($argumentDescriptor, ArgumentDescriptor::class);
561
562
        Assert::same($description->getRaw(), (string) $argumentDescriptor->getDescription());
563
    }
564
565
    /**
566
     * @Given the namespace ':namespace' has a function named ':functionName'
567
     */
568
    public function theNamespaceFoo(string $namespace, string $functionName)
569
    {
570
        /** @var NamespaceDescriptor $namespace */
571
        $namespace = $this->getAst()->getIndexes()->get('namespaces')->get($namespace);
572
        Assert::isInstanceOf($namespace, NamespaceDescriptor::class);
573
        $function = $this->findFunctionInNamespace($namespace, $functionName);
574
        Assert::isInstanceOf($function, FunctionDescriptor::class);
575
    }
576
577
    private function findFunctionInNamespace(NamespaceDescriptor $namespace, string $functionName)
578
    {
579
        foreach ($namespace->getFunctions()->getAll() as $key => $function) {
580
            if ($function->getName() === $functionName) {
581
                return $function;
582
            }
583
        }
584
585
        return null;
586
    }
587
588
    /**
589
     * @Then /^file "([^"]*)" must contain a marker$/
590
     */
591
    public function fileMustContainAMarker($filename)
592
    {
593
        $ast = $this->getAst();
594
595
        /** @var FileDescriptor $file */
596
        $file = $ast->getFiles()->get($filename);
597
598
        Assert::count($file->getMarkers(), 1);
599
    }
600
601
    /**
602
     * @Then class ":className" must have magic property ":propertyName" of type :type
603
     */
604
    public function classMustHaveMagicPropertyOfType($className, $propertyName, $type)
605
    {
606
        $classDescriptor = $this->findClassByFqsen($className);
607
        /** @var PropertyDescriptor $propertyDescriptor */
608
        $propertyDescriptor = null;
609
        foreach ($classDescriptor->getMagicProperties() as $property) {
610
            if ($property->getName() === $propertyName) {
611
                $propertyDescriptor = $property;
612
                break;
613
            }
614
        }
615
616
        Assert::isInstanceOf($propertyDescriptor, PropertyDescriptor::class);
617
        Assert::eq($type, (string) $propertyDescriptor->getType());
618
    }
619
}
620