Completed
Push — develop ( 3a2f11...a89061 )
by Jaap
04:16
created

ApiContext::filesShouldBeParsed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\Gherkin\Node\PyStringNode;
17
use phpDocumentor\Descriptor\ArgumentDescriptor;
18
use phpDocumentor\Descriptor\ClassDescriptor;
19
use phpDocumentor\Descriptor\Collection;
20
use phpDocumentor\Descriptor\ConstantDescriptor;
21
use phpDocumentor\Descriptor\DescriptorAbstract;
22
use phpDocumentor\Descriptor\FileDescriptor;
23
use phpDocumentor\Descriptor\MethodDescriptor;
24
use phpDocumentor\Descriptor\Tag\ParamDescriptor;
25
use phpDocumentor\Descriptor\Tag\ReturnDescriptor;
26
use phpDocumentor\Descriptor\Tag\VersionDescriptor;
27
use phpDocumentor\Descriptor\TraitDescriptor;
28
use phpDocumentor\Reflection\Php\File;
29
use PHPUnit\Framework\Assert;
30
31
class ApiContext extends BaseContext implements Context
32
{
33
    /**
34
     * @Then /^the AST has a class named "([^"]*)" in file "([^"]*)"$/
35
     * @throws \Exception
36
     */
37
    public function theASTHasAclassNamedInFile($class, $file)
38
    {
39
        $ast = $this->getAst();
40
41
        $file = $this->processFilePath($file);
42
        /** @var FileDescriptor $fileDescriptor */
43
        $fileDescriptor = $ast->getFiles()->get($file);
44
45
        /** @var ClassDescriptor $classDescriptor */
46
        foreach ($fileDescriptor->getClasses() as $classDescriptor) {
47
            if ($classDescriptor->getName() === $class) {
48
                return;
49
            }
50
        }
51
52
        throw new \Exception(sprintf('Didn\'t find expected class "%s" in "%s"', $class, $file));
53
    }
54
55
    /**
56
     * @Then /^the AST doesn't have a class "([^"]*)"$/
57
     * @throws \Exception
58
     */
59
    public function theASTDoesnTHaveAClass($className)
60
    {
61
        $ast = $this->getAst();
62
        foreach ($ast->getFiles() as $file) {
63
            foreach ($file->getClasses() as $classDescriptor) {
64
                if ($classDescriptor->getName() === $className) {
65
                    throw new \Exception('Found unexpected class');
66
                }
67
            }
68
        }
69
    }
70
71
    /**
72
     * @Then /^the class named "([^"]*)" is in the default package$/
73
     * @throws \Exception
74
     */
75
    public function theASTHasAClassInDefaultPackage($class)
76
    {
77
        $class = $this->findClassByName($class);
78
79
        Assert::assertEquals('Default', $class->getPackage()->getName());
80
    }
81
82
    /**
83
     * @Then /^the AST has a trait named "([^"]*)" in file "([^"]*)"$/
84
     * @throws \Exception
85
     */
86
    public function theASTHasATraitNamedInFile($trait, $file)
87
    {
88
        $ast = $this->getAst();
89
90
        $file = $this->processFilePath($file);
91
        /** @var FileDescriptor $fileDescriptor */
92
        $fileDescriptor = $ast->getFiles()->get($file);
93
94
        /** @var TraitDescriptor $classDescriptor */
95
        foreach ($fileDescriptor->getTraits() as $classDescriptor) {
96
            if ($classDescriptor->getName() === $trait) {
97
                return;
98
            }
99
        }
100
101
        throw new \Exception(sprintf('Didn\'t find expected trait "%s" in "%s"', $trait, $file));
102
    }
103
104
    /**
105
     * @Then the class named ":class" has docblock with content:
106
     */
107
    public function classHasDocblockWithContent($class, PyStringNode $expectedContent)
108
    {
109
        $class = $this->findClassByName($class);
110
111
        Assert::assertEquals($expectedContent->getRaw(), $class->getDescription());
112
    }
113
114
    /**
115
     * @Then class ":classFqsen" has :docElement:
116
     * @throws Exception
117
     */
118
    public function classHasDocblockContent($classFqsen, $docElement, PyStringNode $value)
119
    {
120
        $class = $this->findClassByFqsen($classFqsen);
121
122
        $method = 'get' . $docElement;
123
124
        Assert::assertEquals($value->getRaw(), $class->{$method}());
125
    }
126
127
    /**
128
     * @Then class ":classFqsen" has :elementType :elementName with :docElement:
129
     */
130
    public function classHasElementWithDocblockContent($classFqsen, $elementType, $elementName, $docElement, PyStringNode $value)
131
    {
132
        $class = $this->findClassByFqsen($classFqsen);
133
134
        switch ($elementType) {
135
            case 'method':
136
            case 'constant':
137
                $method = $method = 'get' . $elementType . 's';
138
                break;
139
            case 'property':
140
                $method = 'getProperties';
141
                break;
142
            default:
143
                $method = 'get' . $elementType;
144
                break;
145
        }
146
147
        $element = $class-> {$method}()->get($elementName);
148
        $method = 'get' . $docElement;
149
        $actual = $element->{$method}();
150
151
        Assert::assertEquals($value->getRaw(), $actual, sprintf('"%s" does not match "%s"', $actual, $value->getRaw()));
152
    }
153
154
    /**
155
     * @Then class ":classFqsen" has version :value
156
     */
157
    public function classHasVersion($classFqsen, $value)
158
    {
159
        $class = $this->findClassByFqsen($classFqsen);
160
161
        /** @var VersionDescriptor $tag */
162
        foreach ($class->getVersion() as $tag) {
163
            if ($tag->getVersion() === $value) {
164
                return;
165
            }
166
        }
167
168
        Assert::fail(sprintf('Didn\'t find expected version "%s"', $value));
169
    }
170
171
    /**
172
     * @Then class ":classFqsen" without tag :tagName
173
     */
174
    public function classWithoutTag($classFqsen, $tagName)
175
    {
176
        $this->classHasTag($classFqsen, $tagName, 0);
177
    }
178
179
    /**
180
     * @param string $classFqsen
181
     * @param string $tagName
182
     * @param int $expectedNumber
183
     * @Then class ":classFqsen" has exactly :expectedNumber tag :tagName
184
     */
185
    public function classHasTag($classFqsen, $tagName, $expectedNumber)
186
    {
187
        $class = $this->findClassByFqsen($classFqsen);
188
        static::AssertTagCount($class, $tagName, $expectedNumber);
0 ignored issues
show
Bug introduced by
Since AssertTagCount() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of AssertTagCount() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
189
    }
190
191
    /**
192
     * @param string $classFqsen
193
     * @param string $tagName
194
     * @param string $method
195
     * @Then class ":classFqsen" has a method named :method without tag :tagName
196
     */
197
    public function classHasMethodWithoutTag($classFqsen, $tagName, $method)
198
    {
199
        $this->classHasMethodWithExpectedCountTag($classFqsen, $tagName, $method, 0);
200
    }
201
202
    /**
203
     * @param string $classFqsen
204
     * @param string $tagName
205
     * @param string $methodName
206
     * @Then class ":classFqsen" has a method named :method with exactly :expected tag :tagName
207
     */
208
    public function classHasMethodWithExpectedCountTag($classFqsen, $tagName, $methodName, $expectedCount)
209
    {
210
        $class = $this->findClassByFqsen($classFqsen);
211
        $method = $class->getMethods()->get($methodName);
212
213
        static::AssertTagCount($method, $tagName, $expectedCount);
0 ignored issues
show
Bug introduced by
Since AssertTagCount() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of AssertTagCount() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
214
    }
215
216
    /**
217
     * @param string $classFqsen
218
     * @param string $methodName
219
     * @Then class ":classFqsen" has a method :method with argument ":argument is variadic
220
     */
221
    public function classHasMethodWithAgumentVariadic($classFqsen, $methodName, $argument)
222
    {
223
        $class = $this->findClassByFqsen($classFqsen);
224
        /** @var MethodDescriptor $method */
225
        $method = $class->getMethods()->get($methodName);
226
        Assert::assertArrayHasKey($argument, $method->getArguments());
227
        /** @var ArgumentDescriptor $argumentD */
228
        $argumentD = $method->getArguments()[$argument];
0 ignored issues
show
Unused Code introduced by
$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...
229
230
        //TODO: enable this check when we support variadic arguments.
231
        //Assert::assertTrue($argumentD->isVariadic(), 'Expected argument to be variadic');
0 ignored issues
show
Unused Code Comprehensibility introduced by
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...
232
    }
233
234
    /**
235
     * @param string $classFqsen
236
     * @param string $methodName
237
     * @Then class ":classFqsen" has a method :method
238
     */
239
    public function classHasMethod($classFqsen, $methodName)
240
    {
241
        $class = $this->findClassByFqsen($classFqsen);
242
        /** @var MethodDescriptor $method */
243
        $method = $class->getMethods()->get($methodName, null);
244
        Assert::assertInstanceOf(MethodDescriptor::class, $method);
245
        Assert::assertEquals($methodName, $method->getName());
246
    }
247
248
    /**
249
     * @param string $classFqsen
250
     * @param string $methodName
251
     * @param string $argument
252
     * @param string $type
253
     * @Then class ":classFqsen" has a method :method with argument :argument of type ":type"
254
     */
255
    public function classHasMethodWithAgumentOfType($classFqsen, $methodName, $argument, $type)
256
    {
257
        $class = $this->findClassByFqsen($classFqsen);
258
        /** @var MethodDescriptor $method */
259
        $method = $class->getMethods()->get($methodName);
260
        Assert::assertArrayHasKey($argument, $method->getArguments());
261
        /** @var ArgumentDescriptor $argumentDescriptor */
262
        $argumentDescriptor = $method->getArguments()[$argument];
263
264
        Assert::assertEquals($type, (string) $argumentDescriptor->getTypes());
265
    }
266
267
    /**
268
     * @param string $classFqsen
269
     * @param string $methodName
270
     * @param string $param
271
     * @param string $type
272
     * @Then class ":classFqsen" has a method :method with param :param of type ":type"
273
     */
274
    public function classHasMethodWithParamOfType($classFqsen, $methodName, $param, $type)
275
    {
276
        $class = $this->findClassByFqsen($classFqsen);
277
        /** @var MethodDescriptor $method */
278
        $method = $class->getMethods()->get($methodName);
279
        /** @var ParamDescriptor $paramDescriptor */
280
        foreach ($method->getParam() as $paramDescriptor) {
281
            if ($paramDescriptor->getName() === $param) {
282
                Assert::assertEquals($type, (string) $paramDescriptor->getTypes());
283
            }
284
        }
285
    }
286
287
    /**
288
     * @param string $classFqsen
289
     * @param string $constantName
290
     * @Then class ":classFqsen" has a constant :constantName
291
     */
292
    public function classHasConstant($classFqsen, $constantName)
293
    {
294
        /** @var ClassDescriptor $class */
295
        $class = $this->findClassByFqsen($classFqsen);
296
        $constant = $class->getConstants()->get($constantName);
297
        Assert::assertInstanceOf(ConstantDescriptor::class, $constant);
298
    }
299
300
    /**
301
     * @param string $className
302
     * @return ClassDescriptor
303
     * @throws \Exception
304
     */
305
    private function findClassByName($className)
306
    {
307
        $ast = $this->getAst();
308
        foreach ($ast->getFiles() as $file) {
309
            foreach ($file->getClasses() as $classDescriptor) {
310
                if ($classDescriptor->getName() === $className) {
311
                    return $classDescriptor;
312
                }
313
            }
314
        }
315
316
        throw new \Exception(sprintf('Didn\'t find expected class "%s"', $className));
317
    }
318
319
    /**
320
     * @param string $tagName
321
     * @param int $expectedNumber
322
     * @param DescriptorAbstract $element
323
     */
324
    private static function AssertTagCount($element, $tagName, $expectedNumber)
325
    {
326
        /** @var Collection $tagCollection */
327
        $tagCollection = $element->getTags()->get($tagName, new Collection());
328
329
        Assert::assertEquals((int) $expectedNumber, $tagCollection->count());
330
        if ($expectedNumber > 0) {
331
            Assert::assertEquals($tagName, $tagCollection[0]->getName());
332
        }
333
    }
334
335
    /**
336
     * @Then /^the ast has a file named "([^"]*)" with a summary:$/
337
     * @throws \Exception
338
     */
339
    public function theAstHasAFileNamedWithASummary(string $fileName, PyStringNode $string)
340
    {
341
        $ast = $this->getAst();
342
        /** @var FileDescriptor $file */
343
        $file = $ast->getFiles()->get($fileName);
344
345
        Assert::assertEquals($string->getRaw(), $file->getSummary());
346
    }
347
348
    /**
349
     * @param string $classFqsen
350
     * @param string $methodName
351
     * @throws Exception
352
     * @Then class ":classFqsen" has a method :method with returntype :returnType
353
     * @Then class ":classFqsen" has a method :method with returntype :returnType without description
354
     */
355
    public function classHasMethodWithReturnType($classFqsen, $methodName, $returnType)
356
    {
357
        $response = $this->findMethodResponse($classFqsen, $methodName);
358
359
        Assert::assertEquals($returnType, (string) $response->getTypes());
360
        Assert::assertEquals('', (string) $response->getDescription());
361
    }
362
363
    /**
364
     * @param string $classFqsen
365
     * @param string $methodName
366
     * @throws Exception
367
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType
368
     * @Then class ":classFqsen" has a magic method :method with returntype :returnType without description
369
     */
370
    public function classHasMagicMethodWithReturnType($classFqsen, $methodName, $returnType)
371
    {
372
        $response = $this->findMagicMethodResponse($classFqsen, $methodName);
373
374
        Assert::assertEquals($returnType, (string) $response->getTypes());
375
        Assert::assertEquals('', (string) $response->getDescription());
376
    }
377
378
    /**
379
     * @param string $classFqsen
380
     * @param string $methodName
381
     * @throws Exception
382
     * @Then class ":classFqsen" has a method :method with returntype :returnType with description:
383
     */
384
    public function classHasMethodWithReturnTypeAndDescription($classFqsen, $methodName, $returnType, PyStringNode $description)
385
    {
386
        $response = $this->findMethodResponse($classFqsen, $methodName);
387
388
        Assert::assertEquals($returnType, (string) $response->getTypes());
389
        Assert::assertEquals($description, (string) $response->getDescription());
390
    }
391
392
    /**
393
     * @Then class ":classFqsen" has a method ":method" without returntype
394
     * @throws \Exception
395
     */
396
    public function classReturnTaggetReturnWithoutAnyWithoutReturntype($classFqsen, $methodName)
397
    {
398
        $response = $this->findMethodResponse($classFqsen, $methodName);
399
        Assert::assertEquals('mixed', (string) $response->getTypes());
400
        Assert::assertEquals('', $response->getDescription());
401
    }
402
403
    /**
404
     * @throws Exception
405
     * @Then has function :fqsen with returntype :returnType
406
     * @Then has function :fqsen with returntype :returnType without description
407
     */
408
    public function functionWithReturnType($fqsen, $returnType)
409
    {
410
        $response = $this->findFunctionResponse($fqsen);
411
412
        Assert::assertEquals($returnType, (string) $response->getTypes());
413
        Assert::assertEquals('', (string) $response->getDescription());
414
    }
415
416
    /**
417
     * @throws Exception
418
     * @Then has function :fqsen with returntype :returnType with description:
419
     */
420
    public function functionWithReturnTypeAndDescription($fqsen, $returnType, PyStringNode $description)
421
    {
422
        $response = $this->findFunctionResponse($fqsen);
423
424
        Assert::assertEquals($returnType, (string) $response->getTypes());
425
        Assert::assertEquals($description, (string) $response->getDescription());
426
    }
427
428
    /**
429
     * @Then has function :fqsen without returntype
430
     * @throws \Exception
431
     */
432
    public function functionWithoutReturntype($fqsen)
433
    {
434
        $response = $this->findFunctionResponse($fqsen);
435
        Assert::assertEquals('mixed', (string) $response->getTypes());
436
        Assert::assertEquals('', $response->getDescription());
437
    }
438
439
    /**
440
     * @throws Exception
441
     */
442
    private function findMethodResponse($classFqsen, $methodName): ReturnDescriptor
443
    {
444
        $class = $this->findClassByFqsen($classFqsen);
445
        /** @var MethodDescriptor $method */
446
        $method = $class->getMethods()->get($methodName, null);
447
        Assert::assertInstanceOf(MethodDescriptor::class, $method);
448
        Assert::assertEquals($methodName, $method->getName());
449
450
        return $method->getResponse();
451
    }
452
453
    /**
454
     * @throws Exception
455
     */
456
    private function findMagicMethodResponse($classFqsen, $methodName): ReturnDescriptor
457
    {
458
        $class = $this->findClassByFqsen($classFqsen);
459
        $match = null;
460
461
        /** @var MethodDescriptor $method */
462
        foreach ($class->getMagicMethods() as $method) {
463
            if ($method->getName() === $methodName) {
464
                $match = $method;
465
            }
466
        }
467
468
        Assert::assertInstanceOf(MethodDescriptor::class, $match);
469
        Assert::assertEquals($methodName, $match->getName());
470
471
        return $match->getResponse();
472
    }
473
474
    /**
475
     * @throws Exception
476
     */
477
    private function findFunctionResponse(string $fqsen): ReturnDescriptor
478
    {
479
        $function = $this->findFunctionByFqsen($fqsen);
480
        return $function->getResponse();
481
    }
482
483
    /**
484
     * @Then class ":classFqsen" has a magic method :method with argument ":argument" of type :type
485
     */
486
    public function classHasMagicMethodWithArgument($classFqsen, $methodName, $argument, $type)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
487
    {
488
        $class = $this->findClassByFqsen($classFqsen);
489
        $match = null;
490
491
        /** @var MethodDescriptor $method */
492
        foreach ($class->getMagicMethods() as $method) {
493
            if ($method->getName() === $methodName) {
494
                $match = $method;
495
            }
496
        }
497
498
        Assert::assertInstanceOf(MethodDescriptor::class, $match);
499
        Assert::assertNotNull($match->getArguments()->get($argument));
500
    }
501
502
    /**
503
     * @Then /^(\d+) files should be parsed$/
504
     */
505
    public function filesShouldBeParsed($count)
506
    {
507
        \PHPUnit\Framework\Assert::assertSame((int) $count, $this->getAst()->getFiles()->count());
508
    }
509
}
510