33 * Checks that strict types are declared in the PHP file.
44 *
55 * @author Michał Bundyra <[email protected] > 6- * @copyright 2006-2017 Squiz Pty Ltd (ABN 77 084 670 600)
6+ * @copyright 2006-2018 Squiz Pty Ltd (ABN 77 084 670 600)
77 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
88 */
99
@@ -78,13 +78,58 @@ public function process(File $phpcsFile, $stackPtr)
7878
7979 $ tokens = $ phpcsFile ->getTokens ();
8080
81+ if ($ stackPtr > 0 ) {
82+ $ before = trim ($ phpcsFile ->getTokensAsString (0 , $ stackPtr ));
83+
84+ if ($ before === '' ) {
85+ $ error = 'Unexpected whitespace before PHP opening tag ' ;
86+ $ fix = $ phpcsFile ->addFixableError ($ error , 0 , 'Whitespace ' );
87+
88+ if ($ fix === true ) {
89+ $ phpcsFile ->fixer ->beginChangeset ();
90+ for ($ i = 0 ; $ i < $ stackPtr ; ++$ i ) {
91+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
92+ }
93+
94+ $ phpcsFile ->fixer ->endChangeset ();
95+ }
96+ } else {
97+ $ error = 'Missing strict type declaration as first statement in the script ' ;
98+ $ fix = $ phpcsFile ->addFixableError ($ error , 0 , 'Missing ' );
99+
100+ if ($ fix === true ) {
101+ $ phpcsFile ->fixer ->addContentBefore (
102+ 0 ,
103+ sprintf ('<?php %s ?>%s ' , $ this ->format , $ phpcsFile ->eolChar )
104+ );
105+ }
106+ }//end if
107+
108+ $ this ->checkOtherDeclarations ($ phpcsFile );
109+
110+ return $ phpcsFile ->numTokens ;
111+ }//end if
112+
81113 $ next = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ stackPtr + 1 ), null , true );
82114
83115 if ($ tokens [$ next ]['code ' ] === T_DECLARE ) {
84- $ eos = $ phpcsFile ->findEndOfStatement ($ next );
85- $ string = $ phpcsFile ->getTokensAsString ($ next , ($ eos - $ next + 1 ));
116+ $ string = $ phpcsFile ->findNext (
117+ T_STRING ,
118+ ($ tokens [$ next ]['parenthesis_opener ' ] + 1 ),
119+ $ tokens [$ next ]['parenthesis_closer ' ]
120+ );
121+
122+ if ($ string !== false
123+ && stripos ($ tokens [$ string ]['content ' ], 'strict_types ' ) !== false
124+ ) {
125+ if (isset ($ tokens [$ next ]['scope_closer ' ]) === true
126+ && $ next === $ tokens [$ next ]['scope_condition ' ]
127+ ) {
128+ $ eos = $ tokens [$ next ]['scope_closer ' ];
129+ } else {
130+ $ eos = $ phpcsFile ->findEndOfStatement ($ next );
131+ }
86132
87- if (stripos ($ string , 'strict_types ' ) !== false ) {
88133 $ prev = $ phpcsFile ->findPrevious (T_WHITESPACE , ($ next - 1 ), null , true );
89134 $ after = $ phpcsFile ->findNext (T_WHITESPACE , ($ eos + 1 ), null , true );
90135
@@ -93,7 +138,7 @@ public function process(File $phpcsFile, $stackPtr)
93138 && $ tokens [$ after ]['code ' ] === T_CLOSE_TAG
94139 ) {
95140 if ($ tokens [$ prev ]['line ' ] !== $ tokens [$ next ]['line ' ]) {
96- $ error = 'PHP open tag must be in the same line as declaration. ' ;
141+ $ error = 'PHP open tag must be on the same line as strict type declaration. ' ;
97142 $ fix = $ phpcsFile ->addFixableError ($ error , $ prev , 'OpenTag ' );
98143
99144 if ($ fix === true ) {
@@ -110,7 +155,7 @@ public function process(File $phpcsFile, $stackPtr)
110155 }//end if
111156
112157 if ($ prev !== false && ($ prev < ($ next - 1 ) || $ tokens [$ prev ]['content ' ] !== '<?php ' )) {
113- $ error = 'Expected single space after PHP open tag and before declaration. ' ;
158+ $ error = 'Expected single space after PHP open tag and before strict type declaration. ' ;
114159 $ fix = $ phpcsFile ->addFixableError ($ error , $ prev , 'OpenTagSpace ' );
115160
116161 if ($ fix === true ) {
@@ -125,7 +170,7 @@ public function process(File $phpcsFile, $stackPtr)
125170 }
126171
127172 if ($ tokens [$ after ]['line ' ] !== $ tokens [$ eos ]['line ' ]) {
128- $ error = 'PHP close tag must be in the same line as declaration. ' ;
173+ $ error = 'PHP close tag must be on the same line as strict type declaration. ' ;
129174 $ fix = $ phpcsFile ->addFixableError ($ error , $ after , 'CloseTag ' );
130175
131176 if ($ fix === true ) {
@@ -160,16 +205,22 @@ public function process(File $phpcsFile, $stackPtr)
160205 $ after = false ;
161206 }//end if
162207
163- // Check how many blank lines is before declare statement.
208+ // Check how many blank lines there are before declare statement.
164209 if ($ prev !== false ) {
165210 $ linesBefore = ($ tokens [$ next ]['line ' ] - $ tokens [$ prev ]['line ' ] - 1 );
166211 if ($ linesBefore !== $ this ->linesBefore ) {
167- $ error = 'Invalid number of blank lines before declare statement; expected %d, but found %d ' ;
168- $ data = [
169- $ this ->linesBefore ,
170- $ linesBefore ,
171- ];
172- $ fix = $ phpcsFile ->addFixableError ($ error , $ next , 'LinesBefore ' , $ data );
212+ if ($ linesBefore < 0 ) {
213+ $ error = 'Strict type declaration must be in new line ' ;
214+ $ data = [];
215+ } else {
216+ $ error = 'Invalid number of blank lines before declare statement; expected %d, but found %d ' ;
217+ $ data = [
218+ $ this ->linesBefore ,
219+ $ linesBefore ,
220+ ];
221+ }
222+
223+ $ fix = $ phpcsFile ->addFixableError ($ error , $ next , 'LinesBefore ' , $ data );
173224
174225 if ($ fix === true ) {
175226 $ phpcsFile ->fixer ->beginChangeset ();
@@ -182,15 +233,22 @@ public function process(File $phpcsFile, $stackPtr)
182233 }
183234 }
184235 } else {
236+ // Clear whitespaces between prev and next, but no new lines.
237+ if ($ linesBefore < 0 ) {
238+ for ($ i = ($ prev + 1 ); $ i < $ next ; ++$ i ) {
239+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
240+ }
241+ }
242+
185243 // Add new blank line(s).
186244 while ($ linesBefore < $ this ->linesBefore ) {
187245 $ phpcsFile ->fixer ->addNewlineBefore ($ next );
188246 ++$ linesBefore ;
189247 }
190- }
248+ }//end if
191249
192250 $ phpcsFile ->fixer ->endChangeset ();
193- }
251+ }//end if
194252 }//end if
195253 }//end if
196254
@@ -202,12 +260,18 @@ public function process(File $phpcsFile, $stackPtr)
202260
203261 $ linesAfter = ($ tokens [$ after ]['line ' ] - $ tokens [$ eos ]['line ' ] - 1 );
204262 if ($ linesAfter !== $ this ->linesAfter ) {
205- $ error = 'Invalid number of blank lines after declare statement; expected %d, but found %d ' ;
206- $ data = [
207- $ this ->linesAfter ,
208- $ linesAfter ,
209- ];
210- $ fix = $ phpcsFile ->addFixableError ($ error , $ eos , 'LinesAfter ' , $ data );
263+ if ($ linesAfter < 0 ) {
264+ $ error = 'Strict type declaration must be the only statement in the line ' ;
265+ $ data = [];
266+ } else {
267+ $ error = 'Invalid number of blank lines after declare statement; expected %d, but found %d ' ;
268+ $ data = [
269+ $ this ->linesAfter ,
270+ $ linesAfter ,
271+ ];
272+ }
273+
274+ $ fix = $ phpcsFile ->addFixableError ($ error , $ eos , 'LinesAfter ' , $ data );
211275
212276 if ($ fix === true ) {
213277 $ phpcsFile ->fixer ->beginChangeset ();
@@ -219,50 +283,68 @@ public function process(File $phpcsFile, $stackPtr)
219283 }
220284 }
221285 } else {
286+ // Remove whitespaces between EOS and after token.
287+ if ($ linesAfter < 0 ) {
288+ for ($ i = ($ eos + 1 ); $ i < $ after ; ++$ i ) {
289+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
290+ }
291+ }
292+
293+ // Add new lines after the statement.
222294 while ($ linesAfter < $ this ->linesAfter ) {
223295 $ phpcsFile ->fixer ->addNewline ($ eos );
224296 ++$ linesAfter ;
225297 }
226- }
298+ }//end if
227299
228300 $ phpcsFile ->fixer ->endChangeset ();
229- }
301+ }//end if
230302 }//end if
231303 }//end if
232304
233305 // Check if declare statement match provided format.
306+ $ string = $ phpcsFile ->getTokensAsString ($ next , ($ eos - $ next + 1 ));
234307 if ($ string !== $ this ->format ) {
235- $ error = 'Invalid format of declaration; expected "%s", but found "%s" ' ;
308+ $ error = 'Invalid format of strict type declaration; expected "%s", but found "%s" ' ;
236309 $ data = [
237310 $ this ->format ,
238311 $ string ,
239312 ];
240- $ fix = $ phpcsFile ->addFixableError ($ error , $ next , 'InvalidFormat ' , $ data );
241313
242- if ($ fix === true ) {
243- $ phpcsFile ->fixer ->beginChangeset ();
244- for ($ i = $ next ; $ i < $ eos ; ++$ i ) {
245- $ phpcsFile ->fixer ->replaceToken ($ i , '' );
246- }
314+ if ($ this ->normalize ($ string ) === $ this ->normalize ($ this ->format )) {
315+ $ fix = $ phpcsFile ->addFixableError ($ error , $ next , 'InvalidFormat ' , $ data );
316+
317+ if ($ fix === true ) {
318+ $ phpcsFile ->fixer ->beginChangeset ();
319+ for ($ i = $ next ; $ i < $ eos ; ++$ i ) {
320+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
321+ }
247322
248- $ phpcsFile ->fixer ->replaceToken ($ eos , $ this ->format );
249- $ phpcsFile ->fixer ->endChangeset ();
323+ $ phpcsFile ->fixer ->replaceToken ($ eos , $ this ->format );
324+ $ phpcsFile ->fixer ->endChangeset ();
325+ }
326+ } else {
327+ $ phpcsFile ->addError ($ error , $ next , 'InvalidFormatNotFixable ' , $ data );
250328 }
251- }
329+ }//end if
330+
331+ $ this ->checkOtherDeclarations ($ phpcsFile , $ next );
252332
253333 return (count ($ tokens ) + 1 );
254334 }//end if
255335 }//end if
256336
257- $ error = 'Missing declaration of strict types at the beginning of the file ' ;
337+ $ this ->checkOtherDeclarations ($ phpcsFile , $ next );
338+
339+ $ error = 'Missing strict type declaration at the beginning of the file ' ;
258340 $ fix = $ phpcsFile ->addFixableError ($ error , $ stackPtr , 'NotFound ' );
259341
260342 if ($ fix === true ) {
261343 $ after = $ stackPtr ;
262344 $ first = $ phpcsFile ->findNext (T_WHITESPACE , ($ stackPtr + 1 ), null , true );
263345 if ($ first !== null && $ tokens [$ first ]['code ' ] === T_DOC_COMMENT_OPEN_TAG ) {
264346 foreach ($ tokens [$ first ]['comment_tags ' ] as $ tag ) {
265- if (in_array (strtolower ($ tokens [$ tag ]['content ' ]), $ this ->omitCommentWithTags , true )) {
347+ if (in_array (strtolower ($ tokens [$ tag ]['content ' ]), $ this ->omitCommentWithTags , true ) === true ) {
266348 $ after = $ tokens [$ first ]['comment_closer ' ];
267349 break ;
268350 }
@@ -287,4 +369,67 @@ public function process(File $phpcsFile, $stackPtr)
287369 }//end process()
288370
289371
372+ /**
373+ * Normalize given string by removing all white characters
374+ * and changed to lower case.
375+ *
376+ * @param string $string String to be normalized.
377+ *
378+ * @return string
379+ */
380+ private function normalize ($ string )
381+ {
382+ return strtolower (preg_replace ('/\s/ ' , '' , $ string ));
383+
384+ }//end normalize()
385+
386+
387+ /**
388+ * Process other strict_type declaration in the file and remove them.
389+ * The declaration has to be the very first statement in the script.
390+ *
391+ * @param File $phpcsFile The file being scanned.
392+ * @param int $declare The position of the first declaration.
393+ *
394+ * @return void
395+ */
396+ private function checkOtherDeclarations (File $ phpcsFile , $ declare =0 )
397+ {
398+ $ tokens = $ phpcsFile ->getTokens ();
399+
400+ while (($ declare = $ phpcsFile ->findNext (T_DECLARE , ($ declare + 1 ))) !== false ) {
401+ $ string = $ phpcsFile ->findNext (
402+ T_STRING ,
403+ ($ tokens [$ declare ]['parenthesis_opener ' ] + 1 ),
404+ $ tokens [$ declare ]['parenthesis_closer ' ]
405+ );
406+
407+ if ($ string !== false
408+ && stripos ($ tokens [$ string ]['content ' ], 'strict_types ' ) !== false
409+ ) {
410+ $ error = 'Strict type declaration must be the very first statement in the script ' ;
411+ $ fix = $ phpcsFile ->addFixableError ($ error , $ declare , 'NotFirstStatement ' );
412+
413+ if ($ fix === true ) {
414+ $ end = $ phpcsFile ->findNext (
415+ (Tokens::$ emptyTokens + [T_SEMICOLON ]),
416+ ($ tokens [$ declare ]['parenthesis_closer ' ] + 1 ),
417+ null ,
418+ true
419+ );
420+
421+ if ($ end === false ) {
422+ $ end = $ phpcsFile ->numTokens ;
423+ }
424+
425+ for ($ i = $ declare ; $ i < $ end ; ++$ i ) {
426+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
427+ }
428+ }
429+ }//end if
430+ }//end while
431+
432+ }//end checkOtherDeclarations()
433+
434+
290435}//end class
0 commit comments