Completed
Push — master ( 344e9a...a2e7e2 )
by Wim
9s
created

BaseSniffTest::gatherWarnings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 9.4285
1
<?php
2
/**
3
 * Base sniff test class file
4
 *
5
 * @package PHPCompatibility
6
 */
7
8
/**
9
 * BaseSniffTest
10
 *
11
 * Adds PHPCS sniffing logic and custom assertions for PHPCS errors and 
12
 * warnings
13
 *
14
 * @uses PHPUnit_Framework_TestCase
15
 * @package PHPCompatibility
16
 * @author Jansen Price <[email protected]>
17
 */
18
class BaseSniffTest extends PHPUnit_Framework_TestCase
19
{
20
    /**
21
     * The PHP_CodeSniffer object used for testing.
22
     *
23
     * @var PHP_CodeSniffer
24
     */
25
    protected static $phpcs = null;
26
27
    /**
28
     * Sets up this unit test.
29
     *
30
     * @return void
31
     */
32
    protected function setUp()
33
    {
34
        if (self::$phpcs === null) {
35
            self::$phpcs = new PHP_CodeSniffer();
36
        }
37
38
        PHP_CodeSniffer::setConfigData('testVersion', null, true);
39
        if (method_exists('PHP_CodeSniffer_CLI', 'setCommandLineValues')) { // For PHPCS 2.x
40
            self::$phpcs->cli->setCommandLineValues(array('-p', '--colors'));
41
        }
42
43
        self::$phpcs->process(array(), __DIR__ . '/../');
44
        self::$phpcs->setIgnorePatterns(array());
45
    }
46
47
    /**
48
     * Tear down after each test
49
     *
50
     * @return void
51
     */
52
    public function tearDown()
53
    {
54
        // Reset any settingsStandard (targetPhpVersion)
55
        self::$phpcs->cli->settingsStandard = array();
0 ignored issues
show
Bug introduced by
The property settingsStandard does not seem to exist in PHP_CodeSniffer_CLI.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
56
    }
57
58
    /**
59
     * Sniff a file and return resulting file object
60
     *
61
     * @param string $filename Filename to sniff
62
     * @param string $targetPhpVersion Value of 'testVersion' to set on PHPCS object
63
     * @return PHP_CodeSniffer_File File object
64
     */
65
    public function sniffFile($filename, $targetPhpVersion = null)
66
    {
67
        if (null !== $targetPhpVersion) {
68
            PHP_CodeSniffer::setConfigData('testVersion', $targetPhpVersion, true);
69
        }
70
71
        $filename = realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . $filename;
72
        try {
73
            $phpcsFile = self::$phpcs->processFile($filename);
74
        } catch (Exception $e) {
75
            $this->fail('An unexpected exception has been caught: ' . $e->getMessage());
76
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by BaseSniffTest::sniffFile of type PHP_CodeSniffer_File|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
77
        }
78
79
        return $phpcsFile;
80
    }
81
    
82
    /**
83
     * Assert a PHPCS error on a particular line number
84
     *
85
     * @param PHP_CodeSniffer_File $file Codesniffer file object
86
     * @param int $lineNumber Line number
87
     * @param string $expectedMessage Expected error message (assertContains)
88
     * @return bool
89
     */
90
    public function assertError(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
91
    {
92
        $errors = $this->gatherErrors($file);
93
94
        return $this->assertForType($errors, 'error', $lineNumber, $expectedMessage);
95
    }
96
97
    /**
98
     * Assert a PHPCS warning on a particular line number
99
     *
100
     * @param PHP_CodeSniffer_File $file Codesniffer file object
101
     * @param int $lineNumber Line number
102
     * @param string $expectedMessage Expected message (assertContains)
103
     * @return bool
104
     */
105
    public function assertWarning(PHP_CodeSniffer_File $file, $lineNumber, $expectedMessage)
106
    {
107
        $warnings = $this->gatherWarnings($file);
108
109
        return $this->assertForType($warnings, 'warning', $lineNumber, $expectedMessage);
110
    }
111
112
    /**
113
     * Assert a PHPCS error or warning on a particular line number.
114
     *
115
     * @param array  $issues          Array of issues of a particular type.
116
     * @param string $type            The type of issues, either 'error' or 'warning'.
117
     * @param int    $lineNumber      Line number.
118
     * @param string $expectedMessage Expected message (assertContains).
119
     * @return bool
120
     */
121
    private function assertForType($issues, $type, $lineNumber, $expectedMessage)
122
    {
123
        if (!isset($issues[$lineNumber])) {
124
            throw new Exception("Expected $type '$expectedMessage' on line number $lineNumber, but none found.");
125
        }
126
127
        $insteadFoundMessages = array();
128
129
        // Concat any error messages so we can do an assertContains
130
        foreach ($issues[$lineNumber] as $issue) {
131
            $insteadFoundMessages[] = $issue['message'];
132
        }
133
134
        $insteadMessagesString = implode(', ', $insteadFoundMessages);
135
        return $this->assertContains(
136
            $expectedMessage, $insteadMessagesString,
137
            "Expected $type message '$expectedMessage' on line $lineNumber not found. Instead found: $insteadMessagesString."
138
        );
139
    }
140
141
    /**
142
     * Assert no violation (warning or error) on a given line number
143
     *
144
     * @param PHP_CodeSniffer_File $file Codesniffer File object
145
     * @param mixed $lineNumber Line number
146
     * @return bool
147
     */
148
    public function assertNoViolation(PHP_CodeSniffer_File $file, $lineNumber = 0)
149
    {
150
        $errors   = $this->gatherErrors($file);
151
        $warnings = $this->gatherWarnings($file);
152
153
        if (!count($errors) && !count($warnings)) {
154
            return $this->assertTrue(true);
155
        }
156
157
        if ($lineNumber == 0) {
158
            $allMessages = $errors + $warnings;
159
            // TODO: Update the fail message to give the tester some 
160
            // indication of what the errors or warnings were
161
            return $this->assertEmpty($allMessages, 'Failed asserting no violations in file');
162
        }
163
164
        $encounteredMessages = array();
165
        if (isset($errors[$lineNumber])) {
166
            foreach ($errors[$lineNumber] as $error) {
167
                $encounteredMessages[] = 'ERROR: ' . $error['message'];
168
            }
169
        }
170
171
        if (isset($warnings[$lineNumber])) {
172
            foreach ($warnings[$lineNumber] as $warning) {
173
                $encounteredMessages[] = 'WARNING: ' . $warning['message'];
174
            }
175
        }
176
177
        if (!count($encounteredMessages)) {
178
            return $this->assertTrue(true);
179
        }
180
181
        $failMessage = "Failed asserting no standards violation on line $lineNumber: "
182
            . implode(', ', $encounteredMessages);
183
184
        $this->assertEmpty($encounteredMessages, $failMessage);
185
    }
186
187
    /**
188
     * Show violations in file by line number
189
     * 
190
     * This is useful for debugging sniffs on a file
191
     *
192
     * @param PHP_CodeSniffer_File $file Codesniffer file object
193
     * @return void
194
     */
195
    public function showViolations(PHP_CodeSniffer_File $file)
196
    {
197
        $violations = array(
198
            'errors'   => $this->gatherErrors($file),
199
            'warnings' => $this->gatherWarnings($file),
200
        );
201
202
        return $violations;
203
    }
204
205
    /**
206
     * Gather all error messages by line number from phpcs file result
207
     *
208
     * @param PHP_CodeSniffer_File $file Codesniffer File object
209
     * @return array
210
     */
211
    public function gatherErrors(PHP_CodeSniffer_File $file)
212
    {
213
        $foundErrors = $file->getErrors();
214
215
        return $this->gatherIssues($foundErrors);
216
    }
217
218
    /**
219
     * Gather all warning messages by line number from phpcs file result
220
     *
221
     * @param PHP_CodeSniffer_File $file Codesniffer File object
222
     * @return array
223
     */
224
    public function gatherWarnings(PHP_CodeSniffer_File $file)
225
    {
226
        $foundWarnings = $file->getWarnings();
227
228
        return $this->gatherIssues($foundWarnings);
229
    }
230
    
231
    /**
232
     * Gather all messages or a particular type by line number.
233
     *
234
     * @param array $IssuesArray Array of a particular type of issues,
0 ignored issues
show
Documentation introduced by
There is no parameter named $IssuesArray. Did you maybe mean $issuesArray?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

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

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

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

Loading history...
235
	 *                           i.e. errors or warnings.
236
     * @return array
237
     */
238
    private function gatherIssues($issuesArray)
239
    {
240
        $allIssues = array();
241
        foreach ($issuesArray as $line => $lineIssues) {
242
            foreach ($lineIssues as $column => $issues) {
243
                foreach ($issues as $issue) {
244
245
                    if (!isset($allIssues[$line])) {
246
                        $allIssues[$line] = array();
247
                    }
248
249
                    $allIssues[$line][] = $issue;
250
                }
251
            }
252
        }
253
254
        return $allIssues;
255
    }
256
}
257
258