• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / Simple-PHP-Code-Parser / 11462549387

22 Oct 2024 02:37PM UTC coverage: 79.926% (-2.3%) from 82.259%
11462549387

Pull #54

github

web-flow
Merge 01ac05d73 into 898f2c327
Pull Request #54: Update actions/cache action to v4

1302 of 1629 relevant lines covered (79.93%)

6.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.29
/src/voku/SimplePhpParser/Model/PHPFunction.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\SimplePhpParser\Model;
6

7
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
8
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
9
use PhpParser\Comment\Doc;
10
use PhpParser\Node\Stmt\Function_;
11
use ReflectionFunction;
12
use voku\SimplePhpParser\Parsers\Helper\DocFactoryProvider;
13
use voku\SimplePhpParser\Parsers\Helper\Utils;
14

15
class PHPFunction extends BasePHPElement
16
{
17
    use PHPDocElement;
18

19
    /**
20
     * @var PHPParameter[]
21
     */
22
    public array $parameters = [];
23

24
    public ?string $returnPhpDocRaw = null;
25

26
    public ?string $returnType = null;
27

28
    public ?string $returnTypeFromPhpDoc = null;
29

30
    public ?string $returnTypeFromPhpDocSimple = null;
31

32
    public ?string $returnTypeFromPhpDocExtended = null;
33

34
    public ?string $returnTypeFromPhpDocMaybeWithComment = null;
35

36
    public string $summary = '';
37

38
    public string $description = '';
39

40
    /**
41
     * @param Function_   $node
42
     * @param string|null $dummy
43
     *
44
     * @return $this
45
     */
46
    public function readObjectFromPhpNode($node, $dummy = null): self
47
    {
48
        $this->prepareNode($node);
8✔
49

50
        $this->name = static::getFQN($node);
8✔
51

52
        /** @noinspection NotOptimalIfConditionsInspection */
53
        if (\function_exists($this->name)) {
8✔
54
            $reflectionFunction = Utils::createFunctionReflectionInstance($this->name);
5✔
55
            $this->readObjectFromReflection($reflectionFunction);
5✔
56
        }
57

58
        if ($node->returnType) {
8✔
59
            if (!$this->returnType) {
×
60
                if (\method_exists($node->returnType, 'toString')) {
×
61
                    $this->returnType = $node->returnType->toString();
×
62
                } elseif (\property_exists($node->returnType, 'name') && $node->returnType->name) {
×
63
                    $this->returnType = $node->returnType->name;
×
64
                }
65
            }
66

67
            if ($node->returnType instanceof \PhpParser\Node\NullableType) {
×
68
                if ($this->returnType && $this->returnType !== 'null' && \strpos($this->returnType, 'null|') !== 0) {
×
69
                    $this->returnType = 'null|' . $this->returnType;
×
70
                } elseif (!$this->returnType) {
×
71
                    $this->returnType = 'null|mixed';
×
72
                }
73
            }
74
        }
75

76
        $docComment = $node->getDocComment();
8✔
77
        if ($docComment) {
8✔
78
            try {
79
                $phpDoc = Utils::createDocBlockInstance()->create($docComment->getText());
7✔
80
                $this->summary = $phpDoc->getSummary();
7✔
81
                $this->description = (string) $phpDoc->getDescription();
7✔
82
            } catch (\Exception $e) {
×
83
                $tmpErrorMessage = \sprintf(
×
84
                    '%s:%s | %s',
×
85
                    $this->name,
×
86
                    $this->line ?? '?',
×
87
                    \print_r($e->getMessage(), true)
×
88
                );
×
89
                $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
90
            }
91
        }
92

93
        foreach ($node->getParams() as $parameter) {
8✔
94
            $parameterVar = $parameter->var;
8✔
95
            if ($parameterVar instanceof \PhpParser\Node\Expr\Error) {
8✔
96
                $this->parseError[] = \sprintf(
×
97
                    '%s:%s | maybe at this position an expression is required',
×
98
                    $this->line ?? '?',
×
99
                    $this->pos ?? ''
×
100
                );
×
101

102
                return $this;
×
103
            }
104

105
            $paramNameTmp = $parameterVar->name;
8✔
106
            \assert(\is_string($paramNameTmp));
107

108
            if (isset($this->parameters[$paramNameTmp])) {
8✔
109
                $this->parameters[$paramNameTmp] = $this->parameters[$paramNameTmp]->readObjectFromPhpNode($parameter, $node);
5✔
110
            } else {
111
                $this->parameters[$paramNameTmp] = (new PHPParameter($this->parserContainer))->readObjectFromPhpNode($parameter, $node);
3✔
112
            }
113
        }
114

115
        $this->collectTags($node);
8✔
116

117
        $docComment = $node->getDocComment();
8✔
118
        if ($docComment) {
8✔
119
            $this->readPhpDoc($docComment);
7✔
120
        }
121

122
        return $this;
8✔
123
    }
124

125
    /**
126
     * @param ReflectionFunction $function
127
     *
128
     * @return $this
129
     */
130
    public function readObjectFromReflection($function): self
131
    {
132
        $this->name = $function->getName();
5✔
133

134
        if (!$this->line) {
5✔
135
            $lineTmp = $function->getStartLine();
×
136
            if ($lineTmp !== false) {
×
137
                $this->line = $lineTmp;
×
138
            }
139
        }
140

141
        $file = $function->getFileName();
5✔
142
        if ($file) {
5✔
143
            $this->file = $file;
4✔
144
        }
145

146
        $returnType = $function->getReturnType();
5✔
147
        if ($returnType !== null) {
5✔
148
            if (\method_exists($returnType, 'getName')) {
×
149
                $this->returnType = $returnType->getName();
×
150
            } else {
151
                $this->returnType = $returnType . '';
×
152
            }
153

154
            if ($returnType->allowsNull()) {
×
155
                if ($this->returnType && $this->returnType !== 'null' && \strpos($this->returnType, 'null|') !== 0) {
×
156
                    $this->returnType = 'null|' . $this->returnType;
×
157
                } elseif (!$this->returnType) {
×
158
                    $this->returnType = 'null|mixed';
×
159
                }
160
            }
161
        }
162

163
        $docComment = $function->getDocComment();
5✔
164
        if ($docComment) {
5✔
165
            $this->readPhpDoc($docComment);
4✔
166
        }
167

168
        if (!$this->returnTypeFromPhpDoc) {
5✔
169
            try {
170
                $phpDoc = DocFactoryProvider::getDocFactory()->create((string)$function->getDocComment());
2✔
171
                $returnTypeTmp = $phpDoc->getTagsByName('return');
×
172
                if (
173
                    \count($returnTypeTmp) === 1
×
174
                    &&
175
                    $returnTypeTmp[0] instanceof \phpDocumentor\Reflection\DocBlock\Tags\Return_
×
176
                ) {
177
                    $this->returnTypeFromPhpDoc = Utils::parseDocTypeObject($returnTypeTmp[0]->getType());
×
178
                }
179
            } catch (\Exception $e) {
2✔
180
                // ignore
181
            }
182
        }
183

184
        foreach ($function->getParameters() as $parameter) {
5✔
185
            $param = (new PHPParameter($this->parserContainer))->readObjectFromReflection($parameter);
5✔
186
            $this->parameters[$param->name] = $param;
5✔
187
        }
188

189
        $docComment = $function->getDocComment();
5✔
190
        if ($docComment) {
5✔
191
            $this->readPhpDoc($docComment);
4✔
192
        }
193

194
        return $this;
5✔
195
    }
196

197
    /**
198
     * @return string|null
199
     */
200
    public function getReturnType(): ?string
201
    {
202
        if ($this->returnTypeFromPhpDocExtended) {
×
203
            return $this->returnTypeFromPhpDocExtended;
×
204
        }
205

206
        if ($this->returnType) {
×
207
            return $this->returnType;
×
208
        }
209

210
        if ($this->returnTypeFromPhpDocSimple) {
×
211
            return $this->returnTypeFromPhpDocSimple;
×
212
        }
213

214
        return null;
×
215
    }
216

217
    /**
218
     * @param Doc|string $doc
219
     */
220
    protected function readPhpDoc($doc): void
221
    {
222
        if ($doc instanceof Doc) {
14✔
223
            $docComment = $doc->getText();
13✔
224
        } else {
225
            $docComment = $doc;
11✔
226
        }
227
        if ($docComment === '') {
14✔
228
            return;
×
229
        }
230

231
        try {
232
            $phpDoc = Utils::createDocBlockInstance()->create($docComment);
14✔
233

234
            $parsedReturnTag = $phpDoc->getTagsByName('return');
14✔
235

236
            if (!empty($parsedReturnTag)) {
14✔
237
                /** @var Return_ $parsedReturnTagReturn */
238
                $parsedReturnTagReturn = $parsedReturnTag[0];
13✔
239

240
                if ($parsedReturnTagReturn instanceof Return_) {
13✔
241
                    $this->returnTypeFromPhpDocMaybeWithComment = \trim((string) $parsedReturnTagReturn);
13✔
242

243
                    $type = $parsedReturnTagReturn->getType();
13✔
244

245
                    $this->returnTypeFromPhpDoc = Utils::normalizePhpType(\ltrim((string) $type, '\\'));
13✔
246

247
                    $typeTmp = Utils::parseDocTypeObject($type);
13✔
248
                    if ($typeTmp !== '') {
13✔
249
                        $this->returnTypeFromPhpDocSimple = $typeTmp;
13✔
250
                    }
251
                }
252

253
                $this->returnPhpDocRaw = (string) $parsedReturnTagReturn;
13✔
254
                $this->returnTypeFromPhpDocExtended = Utils::modernPhpdoc((string) $parsedReturnTagReturn);
13✔
255
            }
256

257
            $parsedReturnTag = $phpDoc->getTagsByName('psalm-return')
14✔
258
                               + $phpDoc->getTagsByName('phpstan-return');
14✔
259

260
            if (!empty($parsedReturnTag) && $parsedReturnTag[0] instanceof Generic) {
14✔
261
                $parsedReturnTagReturn = (string) $parsedReturnTag[0];
11✔
262

263
                $this->returnTypeFromPhpDocExtended = Utils::modernPhpdoc($parsedReturnTagReturn);
14✔
264
            }
265
        } catch (\Exception $e) {
3✔
266
            $tmpErrorMessage = \sprintf(
3✔
267
                '%s:%s | %s',
3✔
268
                $this->name,
3✔
269
                $this->line ?? '?',
3✔
270
                \print_r($e->getMessage(), true)
3✔
271
            );
3✔
272
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
3✔
273
        }
274

275
        try {
276
            $this->readPhpDocByTokens($docComment);
14✔
277
        } catch (\Exception $e) {
×
278
            $tmpErrorMessage = $this->name . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
279
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
280
        }
281
    }
282

283
    /**
284
     * @throws \PHPStan\PhpDocParser\Parser\ParserException
285
     */
286
    private function readPhpDocByTokens(string $docComment): void
287
    {
288
        $tokens = Utils::modernPhpdocTokens($docComment);
14✔
289

290
        $returnContent = null;
14✔
291
        foreach ($tokens->getTokens() as $token) {
14✔
292
            $content = $token[0];
14✔
293

294
            if ($content === '@return' || $content === '@psalm-return' || $content === '@phpstan-return') {
14✔
295
                // reset
296
                $returnContent = '';
14✔
297

298
                continue;
14✔
299
            }
300

301
            // We can stop if we found the end.
302
            if ($content === '*/') {
14✔
303
                break;
14✔
304
            }
305

306
            if ($returnContent !== null) {
14✔
307
                $returnContent .= $content;
14✔
308
            }
309
        }
310

311
        $returnContent = $returnContent ? \trim($returnContent) : null;
14✔
312
        if ($returnContent) {
14✔
313
            if (!$this->returnPhpDocRaw) {
13✔
314
                $this->returnPhpDocRaw = $returnContent;
2✔
315
            }
316
            $this->returnTypeFromPhpDocExtended = Utils::modernPhpdoc($returnContent);
13✔
317
        }
318
    }
319
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc