Passed
Pull Request — master (#18)
by Andru
04:27
created

DuplicatedArrayKey::castStringFromLiteral()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.432

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 14
ccs 7
cts 10
cp 0.7
crap 4.432
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of PHP Mess Detector.
4
 *
5
 * Copyright (c) Manuel Pichler <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * Licensed under BSD License
9
 * For full copyright and license information, please see the LICENSE file.
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @author Manuel Pichler <[email protected]>
13
 * @copyright Manuel Pichler. All rights reserved.
14
 * @license https://opensource.org/licenses/bsd-license.php BSD License
15
 * @link http://phpmd.org/
16
 */
17
18
namespace PHPMD\Rule\CleanCode;
19
20
use PDepend\Source\AST\AbstractASTNode;
21
use PDepend\Source\AST\ASTArrayElement;
22
use PDepend\Source\AST\ASTLiteral;
23
use PDepend\Source\AST\ASTNode as PDependASTNode;
24
use PHPMD\AbstractNode;
25
use PHPMD\AbstractRule;
26
use PHPMD\Node\ASTNode;
27
use PHPMD\Rule\FunctionAware;
28
use PHPMD\Rule\MethodAware;
29
30
/**
31
 * Duplicated Array Key Rule
32
 *
33
 * This rule detects duplicated array keys.
34
 *
35
 * @author Rafał Wrzeszcz <[email protected]>
36
 * @author Kamil Szymanaski <[email protected]>
37
 */
38
class DuplicatedArrayKey extends AbstractRule implements MethodAware, FunctionAware
39
{
40
    /**
41
     * Retrieves all arrays from single node and performs comparison logic on it
42
     *
43
     * @param AbstractNode $node
44
     * @return void
45
     */
46 17
    public function apply(AbstractNode $node)
47
    {
48 17
        foreach ($node->findChildrenOfType('Array') as $arrayNode) {
49
            /** @var ASTNode $arrayNode */
50 15
            $this->checkForDuplicatedArrayKeys($arrayNode);
51
        }
52 17
    }
53
54
    /**
55
     * This method checks if a given function or method contains an array literal
56
     * with duplicated entries for any key and emits a rule violation if so.
57
     *
58
     * @param ASTNode $node Array node.
59
     * @return void
60
     */
61 15
    private function checkForDuplicatedArrayKeys(ASTNode $node)
62
    {
63 15
        $keys = array();
64
        /** @var ASTArrayElement $arrayElement */
65 15
        foreach ($node->getChildren() as $index => $arrayElement) {
0 ignored issues
show
Documentation Bug introduced by
The method getChildren does not exist on object<PHPMD\Node\ASTNode>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
66 15
            $arrayElement = $this->normalizeKey($arrayElement, $index);
67 15
            if (null === $arrayElement) {
68
                // skip everything that can't be resolved easily
69 2
                continue;
70
            }
71
72 15
            $key = $arrayElement->getImage();
73 15
            if (isset($keys[$key])) {
74 11
                $this->addViolation($node, array($key, $arrayElement->getStartLine()));
75 11
                continue;
76
            }
77 15
            $keys[$key] = $arrayElement;
78
        }
79 15
    }
80
81
    /**
82
     * Changes key name to its string format.
83
     *
84
     * To compare keys, we have to cast them to string.
85
     * Non-associative keys have to use index as its key,
86
     * while boolean and nulls have to be casted respectively.
87
     * As current logic doesn't evaluate expressions nor constants,
88
     * statics, globals, etc. we simply skip them.
89
     *
90
     * @param AbstractASTNode $node Array key to evaluate.
91
     * @param int $index Fallback in case of non-associative arrays
92
     * @return AbstractASTNode Key name
93
     */
94 15
    private function normalizeKey(AbstractASTNode $node, $index)
95
    {
96
        // non-associative - key name equals to its index
97 15
        if (count($node->getChildren()) === 0) {
98
            $node->setImage((string) $index);
99
            return $node;
100
        }
101
        
102 15
        $node = $node->getChild(0);
103 15
        if (!($node instanceof ASTLiteral)) {
104
            // skip expressions, method calls, globals and constants
105 2
            return null;
106
        }
107 15
        $node->setImage($this->castStringFromLiteral($node));
108
109 15
        return $node;
110
    }
111
112
    /**
113
     * Cleans string literals and casts boolean and null values as PHP engine does
114
     *
115
     * @param PDependASTNode $key
116
     * @return string
117
     */
118 15
    private function castStringFromLiteral(PDependASTNode $key)
119
    {
120 15
        $value = $key->getImage();
121 15
        switch ($value) {
122
            case 'false':
123 1
                return '0';
124
            case 'true':
125 1
                return '1';
126
            case 'null':
127 1
                return '';
128
            default:
129 15
                return trim($value, '\'""');
130
        }
131
    }
132
}
133