Completed
Push — develop ( 63ab30...d8c8ca )
by Jaap
38:43 queued 28:43
created

ApiContext::classHasMethodWithParamOfType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 4
dl 0
loc 12
rs 9.4285
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\Behat\Hook\Scope\BeforeScenarioScope;
17
use Behat\Gherkin\Node\PyStringNode;
18
use phpDocumentor\Behat\Contexts\EnvironmentContext;
19
use phpDocumentor\Descriptor\ArgumentDescriptor;
20
use phpDocumentor\Descriptor\ClassDescriptor;
21
use phpDocumentor\Descriptor\Collection;
22
use phpDocumentor\Descriptor\ConstantDescriptor;
23
use phpDocumentor\Descriptor\DescriptorAbstract;
24
use phpDocumentor\Descriptor\FileDescriptor;
25
use phpDocumentor\Descriptor\MethodDescriptor;
26
use phpDocumentor\Descriptor\ProjectDescriptor;
27
use phpDocumentor\Descriptor\Tag\ParamDescriptor;
28
use phpDocumentor\Descriptor\Tag\VersionDescriptor;
29
use phpDocumentor\Reflection\DocBlock\Tag\SeeTag;
30
use PHPUnit\Framework\Assert;
31
32
class ApiContext extends BaseContext implements Context
33
{
34
    /**
35
     * @Then /^the AST has a class named "([^"]*)" in file "([^"]*)"$/
36
     * @throws \Exception
37
     */
38
    public function theASTHasAclassNamedInFile($class, $file)
39
    {
40
        $ast = $this->getAst();
41
42
        $file = $this->processFilePath($file);
43
        /** @var FileDescriptor $fileDescriptor */
44
        $fileDescriptor = $ast->getFiles()->get($file);
45
46
        /** @var ClassDescriptor $classDescriptor */
47
        foreach ($fileDescriptor->getClasses() as $classDescriptor) {
48
            if ($classDescriptor->getName() === $class) {
49
                return;
50
            }
51
        }
52
53
        throw new \Exception(sprintf('Didn\'t find expected class "%s" in "%s"', $class, $file));
54
    }
55
56
    /**
57
     * @Then /^the class named "([^"]*)" is in the default package$/
58
     * @throws \Exception
59
     */
60
    public function theASTHasAClassInDefaultPackage($class)
61
    {
62
        $class = $this->findClassByName($class);
63
64
        Assert::assertEquals('Default', $class->getPackage()->getName());
65
    }
66
67
    /**
68
     * @param $class
69
     * @param $expectedContent
70
     * @Then the class named ":class" has docblock with content:
71
     */
72
    public function classHasDocblockWithContent($class, PyStringNode $expectedContent)
73
    {
74
        $class = $this->findClassByName($class);
75
76
        Assert::assertEquals($expectedContent->getRaw(), $class->getDescription());
77
    }
78
79
    /**
80
     * @param $classFqsen
81
     * @param $docElement
82
     * @param $value
83
     * @Then class ":classFqsen" has :docElement:
84
     * @throws \Exception
85
     */
86
    public function classHasDocblockContent($classFqsen, $docElement, PyStringNode $value)
87
    {
88
        $class = $this->findClassByFqsen($classFqsen);
89
90
        $method = 'get' . $docElement;
91
92
        Assert::assertEquals($value->getRaw(), $class->$method());
93
    }
94
95
    /**
96
     * @param $classFqsen
97
     * @param $elementType
98
     * @param $elementName
99
     * @param $docElement
100
     * @param PyStringNode $value
101
     * @Then class ":classFqsen" has :elementType :elementName with :docElement:
102
     */
103
    public function classHasElementWithDocblockContent($classFqsen, $elementType, $elementName, $docElement, PyStringNode $value)
104
    {
105
        $class = $this->findClassByFqsen($classFqsen);
106
107
        switch ($elementType) {
108
            case 'method':
109
            case 'constant':
110
                $method = $method = 'get' . $elementType . 's';
111
                break;
112
            case 'property':
113
                $method = 'getProperties';
114
                break;
115
            default:
116
                $method = 'get' . $elementType;
117
                break;
118
        }
119
120
        $element = $class-> $method()->get($elementName);
121
122
        $method = 'get' . $docElement;
123
        $actual = $element->$method();
124
125
        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...
126
    }
127
128
    /**
129
     * @param $classFqsen
130
     * @param $value
131
     * @Then class ":classFqsen" has version :value
132
     */
133
    public function classHasVersion($classFqsen, $value)
134
    {
135
        $class = $this->findClassByFqsen($classFqsen);
136
137
        /** @var VersionDescriptor $tag */
138
        foreach ($class->getVersion() as $tag) {
139
            if ($tag->getVersion() === $value) {
140
                return;
141
            }
142
        }
143
144
        Assert::fail(sprintf('Didn\'t find expected version "%s"', $value));
145
    }
146
147
    /**
148
     * @param $classFqsen
149
     * @param $tagName
150
     * @Then class ":classFqsen" without tag :tagName
151
     */
152
    public function classWithoutTag($classFqsen, $tagName)
153
    {
154
        $this->classHasTag($classFqsen, $tagName, 0);
155
    }
156
157
    /**
158
     * @param string $classFqsen
159
     * @param string $tagName
160
     * @param int $expectedNumber
161
     * @Then class ":classFqsen" has exactly :expectedNumber tag :tagName
162
     */
163
    public function classHasTag($classFqsen, $tagName, $expectedNumber)
164
    {
165
        $class = $this->findClassByFqsen($classFqsen);
166
        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...
167
    }
168
169
    /**
170
     * @param string $classFqsen
171
     * @param string $tagName
172
     * @param string $method
173
     * @Then class ":classFqsen" has a method named :method without tag :tagName
174
     */
175
    public function classHasMethodWithoutTag($classFqsen, $tagName, $method)
176
    {
177
        $this->classHasMethodWithExpectedCountTag($classFqsen, $tagName, $method, 0);
178
    }
179
180
    /**
181
     * @param string $classFqsen
182
     * @param string $tagName
183
     * @param string $methodName
184
     * @Then class ":classFqsen" has a method named :method with exactly :expected tag :tagName
185
     */
186
    public function classHasMethodWithExpectedCountTag($classFqsen, $tagName, $methodName, $expectedCount)
187
    {
188
        $class = $this->findClassByFqsen($classFqsen);
189
        $method = $class->getMethods()->get($methodName);
190
191
        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...
192
    }
193
194
    /**
195
     * @param string $classFqsen
196
     * @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...
197
     * @param string $methodName
198
     * @Then class ":classFqsen" has a method :method with argument ":argument is variadic
199
     */
200
    public function classHasMethodWithAgumentVariadic($classFqsen, $methodName, $argument)
201
    {
202
        $class = $this->findClassByFqsen($classFqsen);
203
        /** @var MethodDescriptor $method */
204
        $method = $class->getMethods()->get($methodName);
205
        Assert::assertArrayHasKey($argument, $method->getArguments());
206
        /** @var ArgumentDescriptor $argumentD */
207
        $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...
208
209
        //TODO: enable this check when we support variadic arguments.
210
        //Assert::assertTrue($argumentD->isVariadic(), 'Expected argument to be variadic');
211
    }
212
213
    /**
214
     * @param string $classFqsen
215
     * @param string $methodName
216
     * @param string $argument
217
     * @param string $type
218
     * @Then class ":classFqsen" has a method :method with argument :argument of type ":type"
219
     */
220
    public function classHasMethodWithAgumentOfType($classFqsen, $methodName, $argument, $type)
221
    {
222
        $class = $this->findClassByFqsen($classFqsen);
223
        /** @var MethodDescriptor $method */
224
        $method = $class->getMethods()->get($methodName);
225
        Assert::assertArrayHasKey($argument, $method->getArguments());
226
        /** @var ArgumentDescriptor $argumentDescriptor */
227
        $argumentDescriptor = $method->getArguments()[$argument];
228
229
        Assert::assertEquals($type, (string)$argumentDescriptor->getTypes());
230
    }
231
232
    /**
233
     * @param string $classFqsen
234
     * @param string $methodName
235
     * @param string $param
236
     * @param string $type
237
     * @Then class ":classFqsen" has a method :method with param :param of type ":type"
238
     */
239
    public function classHasMethodWithParamOfType($classFqsen, $methodName, $param, $type)
240
    {
241
        $class = $this->findClassByFqsen($classFqsen);
242
        /** @var MethodDescriptor $method */
243
        $method = $class->getMethods()->get($methodName);
244
        /** @var ParamDescriptor $paramDescriptor */
245
        foreach ($method->getParam() as $paramDescriptor) {
246
            if($paramDescriptor->getName() === $param) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
247
                Assert::assertEquals($type, (string)$paramDescriptor->getTypes());
248
            }
249
        }
250
    }
251
252
    /**
253
     * @param string $classFqsen
254
     * @param string $constantName
255
     * @Then class ":classFqsen" has a constant :constantName
256
     */
257
    public function classHasConstant($classFqsen, $constantName)
258
    {
259
        /** @var ClassDescriptor $class */
260
        $class = $this->findClassByFqsen($classFqsen);
261
        $constant = $class->getConstants()->get($constantName);
262
        Assert::assertInstanceOf(ConstantDescriptor::class, $constant);
263
    }
264
265
    /**
266
     * @param string $className
267
     * @return ClassDescriptor
268
     * @throws \Exception
269
     */
270
    private function findClassByName($className)
271
    {
272
        $ast = $this->getAst();
273
        foreach ($ast->getFiles() as $file) {
274
            foreach ($file->getClasses() as $classDescriptor) {
275
                if ($classDescriptor->getName() === $className) {
276
                    return $classDescriptor;
277
                }
278
            }
279
        }
280
281
        throw new \Exception(sprintf('Didn\'t find expected class "%s"', $className));
282
    }
283
284
    /**
285
     * @param string $tagName
286
     * @param int $expectedNumber
287
     * @param DescriptorAbstract $element
288
     */
289
    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...
290
    {
291
        /** @var Collection $tagCollection */
292
        $tagCollection = $element->getTags()->get($tagName, new Collection());
293
294
        Assert::assertEquals((int)$expectedNumber, $tagCollection->count());
295
        if ($expectedNumber > 0) {
296
            Assert::assertEquals($tagName, $tagCollection[0]->getName());
297
        }
298
    }
299
}
300