Completed
Push — develop ( 37deef...f45442 )
by Jaap
05:54
created

ApiContext::theAstHasAFileNamedWithASummary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 8
rs 9.4285
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-2017 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\VersionDescriptor;
26
use phpDocumentor\Descriptor\TraitDescriptor;
27
use PHPUnit\Framework\Assert;
28
29
class ApiContext extends BaseContext implements Context
30
{
31
    /**
32
     * @Then /^the AST has a class named "([^"]*)" in file "([^"]*)"$/
33
     * @throws \Exception
34
     */
35
    public function theASTHasAclassNamedInFile($class, $file)
36
    {
37
        $ast = $this->getAst();
38
39
        $file = $this->processFilePath($file);
40
        /** @var FileDescriptor $fileDescriptor */
41
        $fileDescriptor = $ast->getFiles()->get($file);
42
43
        /** @var ClassDescriptor $classDescriptor */
44
        foreach ($fileDescriptor->getClasses() as $classDescriptor) {
45
            if ($classDescriptor->getName() === $class) {
46
                return;
47
            }
48
        }
49
50
        throw new \Exception(sprintf('Didn\'t find expected class "%s" in "%s"', $class, $file));
51
    }
52
53
    /**
54
     * @Then /^the class named "([^"]*)" is in the default package$/
55
     * @throws \Exception
56
     */
57
    public function theASTHasAClassInDefaultPackage($class)
58
    {
59
        $class = $this->findClassByName($class);
60
61
        Assert::assertEquals('Default', $class->getPackage()->getName());
62
    }
63
64
    /**
65
     * @Then /^the AST has a trait named "([^"]*)" in file "([^"]*)"$/
66
     * @throws \Exception
67
     */
68
    public function theASTHasATraitNamedInFile($trait, $file)
69
    {
70
        $ast = $this->getAst();
71
72
        $file = $this->processFilePath($file);
73
        /** @var FileDescriptor $fileDescriptor */
74
        $fileDescriptor = $ast->getFiles()->get($file);
75
76
        /** @var TraitDescriptor $classDescriptor */
77
        foreach ($fileDescriptor->getTraits() as $classDescriptor) {
78
            if ($classDescriptor->getName() === $trait) {
79
                return;
80
            }
81
        }
82
83
        throw new \Exception(sprintf('Didn\'t find expected trait "%s" in "%s"', $trait, $file));
84
    }
85
86
    /**
87
     * @param $class
88
     * @param $expectedContent
89
     * @Then the class named ":class" has docblock with content:
90
     */
91
    public function classHasDocblockWithContent($class, PyStringNode $expectedContent)
92
    {
93
        $class = $this->findClassByName($class);
94
95
        Assert::assertEquals($expectedContent->getRaw(), $class->getDescription());
96
    }
97
98
    /**
99
     * @param $classFqsen
100
     * @param $docElement
101
     * @param $value
102
     * @Then class ":classFqsen" has :docElement:
103
     * @throws \Exception
104
     */
105
    public function classHasDocblockContent($classFqsen, $docElement, PyStringNode $value)
106
    {
107
        $class = $this->findClassByFqsen($classFqsen);
108
109
        $method = 'get' . $docElement;
110
111
        Assert::assertEquals($value->getRaw(), $class->$method());
112
    }
113
114
    /**
115
     * @param $classFqsen
116
     * @param $elementType
117
     * @param $elementName
118
     * @param $docElement
119
     * @param PyStringNode $value
120
     * @Then class ":classFqsen" has :elementType :elementName with :docElement:
121
     */
122
    public function classHasElementWithDocblockContent($classFqsen, $elementType, $elementName, $docElement, PyStringNode $value)
123
    {
124
        $class = $this->findClassByFqsen($classFqsen);
125
126
        switch ($elementType) {
127
            case 'method':
128
            case 'constant':
129
                $method = $method = 'get' . $elementType . 's';
130
                break;
131
            case 'property':
132
                $method = 'getProperties';
133
                break;
134
            default:
135
                $method = 'get' . $elementType;
136
                break;
137
        }
138
139
        $element = $class-> $method()->get($elementName);
140
141
        $method = 'get' . $docElement;
142
        $actual = $element->$method();
143
144
        Assert::assertEquals($value->getRaw(), $actual,  sprintf('"%s" does not match "%s"', $actual, $value->getRaw()));
0 ignored issues
show
Coding Style introduced by
Expected 1 space instead of 2 after comma in function call.
Loading history...
145
    }
146
147
    /**
148
     * @param $classFqsen
149
     * @param $value
150
     * @Then class ":classFqsen" has version :value
151
     */
152
    public function classHasVersion($classFqsen, $value)
153
    {
154
        $class = $this->findClassByFqsen($classFqsen);
155
156
        /** @var VersionDescriptor $tag */
157
        foreach ($class->getVersion() as $tag) {
158
            if ($tag->getVersion() === $value) {
159
                return;
160
            }
161
        }
162
163
        Assert::fail(sprintf('Didn\'t find expected version "%s"', $value));
164
    }
165
166
    /**
167
     * @param $classFqsen
168
     * @param $tagName
169
     * @Then class ":classFqsen" without tag :tagName
170
     */
171
    public function classWithoutTag($classFqsen, $tagName)
172
    {
173
        $this->classHasTag($classFqsen, $tagName, 0);
174
    }
175
176
    /**
177
     * @param string $classFqsen
178
     * @param string $tagName
179
     * @param int $expectedNumber
180
     * @Then class ":classFqsen" has exactly :expectedNumber tag :tagName
181
     */
182
    public function classHasTag($classFqsen, $tagName, $expectedNumber)
183
    {
184
        $class = $this->findClassByFqsen($classFqsen);
185
        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...
186
    }
187
188
    /**
189
     * @param string $classFqsen
190
     * @param string $tagName
191
     * @param string $method
192
     * @Then class ":classFqsen" has a method named :method without tag :tagName
193
     */
194
    public function classHasMethodWithoutTag($classFqsen, $tagName, $method)
195
    {
196
        $this->classHasMethodWithExpectedCountTag($classFqsen, $tagName, $method, 0);
197
    }
198
199
    /**
200
     * @param string $classFqsen
201
     * @param string $tagName
202
     * @param string $methodName
203
     * @Then class ":classFqsen" has a method named :method with exactly :expected tag :tagName
204
     */
205
    public function classHasMethodWithExpectedCountTag($classFqsen, $tagName, $methodName, $expectedCount)
206
    {
207
        $class = $this->findClassByFqsen($classFqsen);
208
        $method = $class->getMethods()->get($methodName);
209
210
        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...
211
    }
212
213
    /**
214
     * @param string $classFqsen
215
     * @param string $tagName
0 ignored issues
show
Bug introduced by
There is no parameter named $tagName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
216
     * @param string $methodName
217
     * @Then class ":classFqsen" has a method :method with argument ":argument is variadic
218
     */
219
    public function classHasMethodWithAgumentVariadic($classFqsen, $methodName, $argument)
220
    {
221
        $class = $this->findClassByFqsen($classFqsen);
222
        /** @var MethodDescriptor $method */
223
        $method = $class->getMethods()->get($methodName);
224
        Assert::assertArrayHasKey($argument, $method->getArguments());
225
        /** @var ArgumentDescriptor $argumentD */
226
        $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...
227
228
        //TODO: enable this check when we support variadic arguments.
229
        //Assert::assertTrue($argumentD->isVariadic(), 'Expected argument to be variadic');
230
    }
231
232
    /**
233
     * @param string $classFqsen
234
     * @param string $methodName
235
     * @param string $argument
236
     * @param string $type
237
     * @Then class ":classFqsen" has a method :method with argument :argument of type ":type"
238
     */
239
    public function classHasMethodWithAgumentOfType($classFqsen, $methodName, $argument, $type)
240
    {
241
        $class = $this->findClassByFqsen($classFqsen);
242
        /** @var MethodDescriptor $method */
243
        $method = $class->getMethods()->get($methodName);
244
        Assert::assertArrayHasKey($argument, $method->getArguments());
245
        /** @var ArgumentDescriptor $argumentDescriptor */
246
        $argumentDescriptor = $method->getArguments()[$argument];
247
248
        Assert::assertEquals($type, (string)$argumentDescriptor->getTypes());
249
    }
250
251
    /**
252
     * @param string $classFqsen
253
     * @param string $methodName
254
     * @param string $param
255
     * @param string $type
256
     * @Then class ":classFqsen" has a method :method with param :param of type ":type"
257
     */
258
    public function classHasMethodWithParamOfType($classFqsen, $methodName, $param, $type)
259
    {
260
        $class = $this->findClassByFqsen($classFqsen);
261
        /** @var MethodDescriptor $method */
262
        $method = $class->getMethods()->get($methodName);
263
        /** @var ParamDescriptor $paramDescriptor */
264
        foreach ($method->getParam() as $paramDescriptor) {
265
            if($paramDescriptor->getName() === $param) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
266
                Assert::assertEquals($type, (string)$paramDescriptor->getTypes());
267
            }
268
        }
269
    }
270
271
    /**
272
     * @param string $classFqsen
273
     * @param string $constantName
274
     * @Then class ":classFqsen" has a constant :constantName
275
     */
276
    public function classHasConstant($classFqsen, $constantName)
277
    {
278
        /** @var ClassDescriptor $class */
279
        $class = $this->findClassByFqsen($classFqsen);
280
        $constant = $class->getConstants()->get($constantName);
281
        Assert::assertInstanceOf(ConstantDescriptor::class, $constant);
282
    }
283
284
    /**
285
     * @param string $className
286
     * @return ClassDescriptor
287
     * @throws \Exception
288
     */
289
    private function findClassByName($className)
290
    {
291
        $ast = $this->getAst();
292
        foreach ($ast->getFiles() as $file) {
293
            foreach ($file->getClasses() as $classDescriptor) {
294
                if ($classDescriptor->getName() === $className) {
295
                    return $classDescriptor;
296
                }
297
            }
298
        }
299
300
        throw new \Exception(sprintf('Didn\'t find expected class "%s"', $className));
301
    }
302
303
    /**
304
     * @param string $tagName
305
     * @param int $expectedNumber
306
     * @param DescriptorAbstract $element
307
     */
308
    private static function AssertTagCount($element, $tagName, $expectedNumber)
0 ignored issues
show
Coding Style introduced by
Method name "ApiContext::AssertTagCount" is not in camel caps format
Loading history...
309
    {
310
        /** @var Collection $tagCollection */
311
        $tagCollection = $element->getTags()->get($tagName, new Collection());
312
313
        Assert::assertEquals((int)$expectedNumber, $tagCollection->count());
314
        if ($expectedNumber > 0) {
315
            Assert::assertEquals($tagName, $tagCollection[0]->getName());
316
        }
317
    }
318
319
    /**
320
     * @Then /^the ast has a file named "([^"]*)" with a summary:$/
321
     * @throws \Exception
322
     */
323
    public function theAstHasAFileNamedWithASummary(string $fileName, PyStringNode $string)
324
    {
325
        $ast = $this->getAst();
326
        /** @var FileDescriptor $file */
327
        $file = $ast->getFiles()->get($fileName);
328
329
        Assert::assertEquals($string->getRaw(), $file->getSummary());
330
    }
331
}
332