Completed
Pull Request — master (#19)
by
unknown
08:16
created

DocSummaryHelper::fixSummaryUcFirst()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 8
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
3
namespace BestIt\Helper;
4
5
use BestIt\AbstractDocSniff;
6
use PHP_CodeSniffer_File;
7
8
/**
9
 * Class DocSummaryHelper
10
 *
11
 * @package BestIt\Helper
12
 * @author Nick Lubisch <[email protected]>
13
 */
14
class DocSummaryHelper
15
{
16
    /**
17
     * The php cs file.
18
     *
19
     * @var PHP_CodeSniffer_File
20
     */
21
    private $file;
22
23
    /**
24
     * The doc comment helper.
25
     *
26
     * @var DocHelper
27
     */
28
    private $docHelper;
29
30
    /**
31
     * Token stack of the current file.
32
     *
33
     * @var array
34
     */
35
    private $tokens;
36
37
    /**
38
     * DocSummaryHelper constructor.
39
     *
40
     * @param PHP_CodeSniffer_File $file The php cs file
41
     * @param DocHelper $docHelper The doc comment helper
42
     */
43 109
    public function __construct(PHP_CodeSniffer_File $file, DocHelper $docHelper)
44
    {
45 109
        $this->file = $file;
46 109
        $this->tokens = $file->getTokens();
47 109
        $this->docHelper = $docHelper;
48 109
    }
49
50
    /**
51
     * Returns pointer to the comment summary.
52
     *
53
     * @return bool|int Pointer to the token or false
54
     */
55 101
    public function getCommentSummaryPointer()
56
    {
57 101
        $commentStartPtr = $this->docHelper->getCommentStartPointer();
58 101
        $commentEndPtr = $this->docHelper->getCommentEndPointer();
59
60 101
        $summaryPtr = $this->file->findNext(
61
            [
62 101
                T_DOC_COMMENT_WHITESPACE,
63 101
                T_DOC_COMMENT_STAR
64
            ],
65 101
            $commentStartPtr + 1,
66
            $commentEndPtr,
0 ignored issues
show
Bug introduced by
It seems like $commentEndPtr defined by $this->docHelper->getCommentEndPointer() on line 58 can also be of type boolean; however, PHP_CodeSniffer_File::findNext() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
67 101
            true
68
        );
69
70 101
        $summaryToken = $this->tokens[$summaryPtr];
71
72 101
        return $summaryToken['code'] === T_DOC_COMMENT_STRING ? $summaryPtr : false;
73
    }
74
75
    /**
76
     * Returns comment summary token.
77
     *
78
     * @return array Summary token array
79
     */
80 97
    public function getCommentSummaryToken()
81
    {
82 97
        return $this->tokens[$this->getCommentSummaryPointer()];
83
    }
84
85
    /**
86
     * Check class comment summary.
87
     *
88
     * @return void
89
     */
90 101
    public function checkCommentSummary()
91
    {
92 101
        $commentEndPtr = $this->docHelper->getCommentEndPointer();
93 101
        $commentStartPtr = $this->docHelper->getCommentStartPointer();
94 101
        $commentStartToken = $this->docHelper->getCommentStartToken();
95
96 101
        $summaryPtr = $this->getCommentSummaryPointer();
97
98 101
        if ($summaryPtr === false) {
99 4
            $this->file->addError(
100 4
                AbstractDocSniff::MESSAGE_NO_SUMMARY,
101
                $commentStartPtr,
102 4
                AbstractDocSniff::CODE_NO_SUMMARY
103
            );
104
105 4
            return;
106
        }
107
108 97
        $summaryToken = $this->tokens[$summaryPtr];
109
110 97
        $this->checkSummaryIsFirstLine($commentStartToken, $summaryToken, $summaryPtr);
111 97
        $this->checkSummaryCapitalLetter($summaryToken, $summaryPtr);
112 97
        $this->checkSummaryLineLength($summaryToken, $summaryPtr);
113 97
        $this->checkLineAfterSummary($summaryPtr, $commentEndPtr);
0 ignored issues
show
Bug introduced by
It seems like $commentEndPtr defined by $this->docHelper->getCommentEndPointer() on line 92 can also be of type boolean; however, BestIt\Helper\DocSummary...checkLineAfterSummary() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
114 97
    }
115
116
    /**
117
     * Checks that the first character of the summary is upper case.
118
     *
119
     * @param array $summaryToken Token of the summary
120
     * @param int $summaryPtr Pointer to the summary
121
     *
122
     * @return void
123
     */
124 97
    private function checkSummaryCapitalLetter(array $summaryToken, $summaryPtr)
125
    {
126 97
        $summaryText = $summaryToken['content'];
127
128 97
        if (ucfirst($summaryText) === $summaryText) {
129 93
            return;
130
        }
131
132 8
        $fixUcFirst = $this->file->addFixableError(
133 8
            AbstractDocSniff::MESSAGE_SUMMARY_UC_FIRST,
134
            $summaryPtr,
135 8
            AbstractDocSniff::CODE_SUMMARY_UC_FIRST
136
        );
137
138 8
        if ($fixUcFirst) {
139 4
            $this->fixSummaryUcFirst($summaryToken, $summaryPtr);
140
        }
141 8
    }
142
143
    /**
144
     * Checks if the line length of the summary is maximum 120 chars.
145
     *
146
     * @param array $summaryToken Token array of the summary
147
     * @param int $summaryPtr Pointer of the summary
148
     *
149
     * @return void
150
     */
151 97
    private function checkSummaryLineLength($summaryToken, $summaryPtr)
152
    {
153 97
        $summaryLineLength = $summaryToken['column'] + $summaryToken['length'];
154
155 97
        if ($summaryLineLength > AbstractDocSniff::MAX_LINE_LENGTH) {
156 4
            $this->file->addError(
157 4
                AbstractDocSniff::MESSAGE_SUMMARY_TOO_LONG,
158
                $summaryPtr,
159 4
                AbstractDocSniff::CODE_SUMMARY_TOO_LONG
160
            );
161
        }
162 97
    }
163
164
    /**
165
     * Checks if the summary is the first line of the comment.
166
     *
167
     * @param array $commentStartToken Token array of the comment start
168
     * @param array $summaryToken Token of the summary
169
     * @param int $summaryPtr Pointer to the summary
170
     *
171
     * @return void
172
     */
173 97
    private function checkSummaryIsFirstLine($commentStartToken, $summaryToken, $summaryPtr)
174
    {
175 97
        if ($summaryToken['line'] !== $commentStartToken['line'] + 1) {
176 8
            $fixSummaryNotFirst = $this->file->addFixableError(
177 8
                AbstractDocSniff::MESSAGE_SUMMARY_NOT_FIRST,
178
                $summaryPtr,
179 8
                AbstractDocSniff::CODE_SUMMARY_NOT_FIRST
180
            );
181
182 8
            if ($fixSummaryNotFirst) {
183 4
                $this->fixSummaryNotFirst();
184
            }
185
        }
186 97
    }
187
188
    /**
189
     * Checks the line after the summary.
190
     *
191
     * @param int $summaryPtr Pointer to the summary
192
     * @param int $commentEndPtr Pointer to the end of the doc comment
193
     *
194
     * @return void
195
     */
196 97
    private function checkLineAfterSummary($summaryPtr, $commentEndPtr)
197
    {
198 97
        $summaryToken = $this->getCommentSummaryToken();
199
200 97
        $nextRelevantPtr = $this->file->findNext(
201
            [
202 97
                T_DOC_COMMENT_WHITESPACE,
203 97
                T_DOC_COMMENT_STAR
204
            ],
205 97
            $summaryPtr + 1,
206
            $commentEndPtr,
207 97
            true
208
        );
209
210 97
        $nextRelevantToken = $this->tokens[$nextRelevantPtr];
211
212 97
        if (($nextRelevantToken['line'] - $summaryToken['line']) === 1) {
213 8
            $fixLineAfterSummary = $this->file->addFixableError(
214 8
                AbstractDocSniff::MESSAGE_NO_LINE_AFTER_SUMMARY,
215
                $summaryPtr,
216 8
                AbstractDocSniff::CODE_NO_LINE_AFTER_SUMMARY
217
            );
218
219 8
            if ($fixLineAfterSummary) {
220 4
                $this->fixNoLineAfterSummary();
221
            }
222
223 8
            return;
224
        }
225 93
    }
226
227
    /**
228
     * Fixes no line after summary.
229
     *
230
     * @return void
231
     */
232 4 View Code Duplication
    private function fixNoLineAfterSummary()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
233
    {
234 4
        $summaryPtr = $this->getCommentSummaryPointer();
235 4
        $summaryToken = $this->getCommentSummaryToken();
236
237 4
        $this->file->fixer->beginChangeset();
238
239 4
        $this->file->fixer->addContent(
240
            $summaryPtr,
0 ignored issues
show
Security Bug introduced by
It seems like $summaryPtr defined by $this->getCommentSummaryPointer() on line 234 can also be of type false; however, PHP_CodeSniffer_Fixer::addContent() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
241 4
            $this->file->eolChar . str_repeat('    ', $summaryToken['level']) . ' *'
242
        );
243
244 4
        $this->file->fixer->endChangeset();
245 4
    }
246
247
    /**
248
     * Fixes summary not first statement.
249
     *
250
     * @return void
251
     */
252 4
    private function fixSummaryNotFirst()
253
    {
254 4
        $commentStartToken = $this->docHelper->getCommentStartToken();
255 4
        $summaryStartToken = $this->getCommentSummaryToken();
256
257 4
        $startLine = $commentStartToken['line'] + 1;
258 4
        $endLine = $summaryStartToken['line'] - 1;
259
260 4
        $this->file->fixer->beginChangeset();
261
262 4
        foreach ($this->tokens as $tokenPtr => $token) {
263 4
            for ($line = $startLine; $line <= $endLine; $line++) {
264 4
                if ($token['line'] === $line) {
265 4
                    $this->file->fixer->replaceToken($tokenPtr, '');
266
                }
267
            }
268
        }
269
270 4
        $this->file->fixer->endChangeset();
271 4
    }
272
273
    /**
274
     * Fixes the first letter of the summary to be uppercase.
275
     *
276
     * @param array $summaryToken Token array of the summary
277
     * @param int $summaryPtr Pointer to the summary
278
     *
279
     * @return void
280
     */
281 4 View Code Duplication
    private function fixSummaryUcFirst(array $summaryToken, $summaryPtr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283 4
        $this->file->fixer->beginChangeset();
284
285 4
        $this->file->fixer->replaceToken($summaryPtr, ucfirst($summaryToken['content']));
286
287 4
        $this->file->fixer->endChangeset();
288 4
    }
289
}
290