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

tests/features/bootstrap/Ast/ApiContext.php (1 issue)

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');
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