File: MbstringFunctionCallRule.php

package info (click to toggle)
php-league-commonmark 2.7.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 8,264 kB
  • sloc: php: 20,396; xml: 1,988; ruby: 45; makefile: 21; javascript: 15
file content (108 lines) | stat: -rw-r--r-- 3,176 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<?php

declare(strict_types=1);

/*
 * This file is part of the league/commonmark package.
 *
 * (c) Colin O'Dell <colinodell@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace League\CommonMark\Tests\PHPStan;

use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PhpParser\Node;

/**
 * Custom phpstan rule that:
 *
 * 1. Disallows the use of certain mbstring functions that could be problematic
 * 2. Requires an explicit encoding be provided to all `mb_*()` functions that support it
 */
final class MbstringFunctionCallRule implements Rule
{
    private array $disallowedFunctionsThatAlterGlobalSettings = [
        'mb_internal_encoding',
        'mb_regex_encoding',
        'mb_detect_order',
        'mb_language',
    ];

    private array $encodingParamPositionCache = [];

    public function getNodeType(): string
    {
        return Node\Expr\FuncCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        if (! $node instanceof Node\Expr\FuncCall) {
            return [];
        }

        if (! $node->name instanceof Node\Name) {
            return [];
        }

        $functionName = $node->name->toString();
        if (! str_starts_with($functionName, 'mb_')) {
            return [];
        }

        if (\in_array($functionName, $this->disallowedFunctionsThatAlterGlobalSettings, true)) {
            return [\sprintf('Use of %s() is not allowed in this library because it alters global settings', $functionName)];
        }

        $encodingParamPosition = $this->getEncodingParamPosition($functionName);
        if ($encodingParamPosition === null) {
            return [];
        }

        $arg = $node->args[$encodingParamPosition] ?? null;
        if ($arg === null) {
            return [\sprintf('%s() is missing the $encoding param (should be "UTF-8")', $functionName)];
        }

        if (! $arg instanceof Node\Arg) {
            return [];
        }

        $encodingArg = $arg->value;
        if (! ($encodingArg instanceof Node\Scalar\String_)) {
            return [\sprintf('%s() must define the $encoding as "UTF-8"', $functionName)];
        }

        if (! \in_array($encodingArg->value, ['UTF-8', 'ASCII'], true)) {
            return [\sprintf('%s() must define the $encoding as "UTF-8" or "ASCII", not "%s"', $functionName, $encodingArg->value)];
        }

        return [];
    }

    private function getEncodingParamPosition(string $function): ?int
    {
        if (isset($this->encodingParamPositionCache[$function])) {
            return $this->encodingParamPositionCache[$function];
        }

        $reflection = new \ReflectionFunction($function);
        $params     = $reflection->getParameters();

        $encodingParamPosition = null;
        foreach ($params as $i => $param) {
            if ($param->getName() === 'encoding') {
                $encodingParamPosition = $i;
                break;
            }
        }

        $this->encodingParamPositionCache[$function] = $encodingParamPosition;

        return $encodingParamPosition;
    }
}