66
77use PHP_CodeSniffer \Files \File ;
88use PHP_CodeSniffer \Sniffs \Sniff ;
9+ use PHP_CodeSniffer \Util \Common ;
910use PHP_CodeSniffer \Util \Tokens ;
11+ use SymfonyCustom \Helpers \SniffHelper ;
1012
1113/**
1214 * Throws errors if scalar type name are not valid.
1315 */
1416class ValidScalarTypeNameSniff implements Sniff
1517{
16- /**
17- * Types to replace: key is type to replace, value is type to replace with.
18- *
19- * @var array
20- */
21- public $ types = [
22- 'boolean ' => 'bool ' ,
23- 'double ' => 'float ' ,
24- 'integer ' => 'int ' ,
25- 'real ' => 'float ' ,
26- ];
27-
2818 /**
2919 * @return array
3020 */
3121 public function register (): array
3222 {
33- $ tokens = Tokens::$ castTokens ;
34- $ tokens [] = T_DOC_COMMENT_OPEN_TAG ;
35-
36- return $ tokens ;
23+ return [T_DOC_COMMENT_TAG ];
3724 }
3825
3926 /**
@@ -43,91 +30,94 @@ public function register(): array
4330 public function process (File $ phpcsFile , $ stackPtr ): void
4431 {
4532 $ tokens = $ phpcsFile ->getTokens ();
46- if (T_DOC_COMMENT_OPEN_TAG === $ tokens [$ stackPtr ]['code ' ]) {
47- $ this ->validateDocComment ($ phpcsFile , $ stackPtr );
48- } else {
49- $ this ->validateCast ($ phpcsFile , $ stackPtr );
50- }
51- }
5233
53- /**
54- * @param File $phpcsFile
55- * @param int $stackPtr
56- */
57- private function validateDocComment (File $ phpcsFile , int $ stackPtr ): void
58- {
59- $ tokens = $ phpcsFile ->getTokens ();
60- foreach ($ tokens [$ stackPtr ]['comment_tags ' ] as $ commentTag ) {
61- if (in_array (
62- $ tokens [$ commentTag ]['content ' ],
63- ['@param ' , '@return ' , '@var ' ]
64- )
65- ) {
66- $ docString = $ phpcsFile ->findNext (T_DOC_COMMENT_STRING , $ commentTag );
67- if (false !== $ docString ) {
68- $ stringParts = explode (' ' , $ tokens [$ docString ]['content ' ]);
69- $ typeName = $ stringParts [0 ];
70- $ this ->validateTypeName ($ phpcsFile , $ docString , $ typeName );
34+ if (in_array ($ tokens [$ stackPtr ]['content ' ], SniffHelper::TAGS_WITH_TYPE )) {
35+ preg_match (
36+ '`^((?:\|?(?:array\([^\)]*\)|[ \\\\a-z0-9\[\<\,\>\]]+))*)( .*)?`i ' ,
37+ $ tokens [($ stackPtr + 2 )]['content ' ],
38+ $ match
39+ );
40+
41+ if (isset ($ match [1 ]) === false ) {
42+ return ;
43+ }
44+
45+ // Check type (can be multiple, separated by '|').
46+ $ type = $ match [1 ];
47+ $ typeNames = explode ('| ' , $ type );
48+ $ suggestedNames = [];
49+ foreach ($ typeNames as $ i => $ typeName ) {
50+ $ suggestedName = $ this ->getValidTypeName ($ typeName );
51+
52+ if (in_array ($ suggestedName , $ suggestedNames , true ) === false ) {
53+ $ suggestedNames [] = $ suggestedName ;
7154 }
7255 }
73- }
74- }
7556
76- /**
77- * @param File $phpcsFile
78- * @param int $stackPtr
79- */
80- private function validateCast (File $ phpcsFile , int $ stackPtr ): void
81- {
82- $ tokens = $ phpcsFile ->getTokens ();
83- preg_match ('/^\(\s*(\S+)\s*\)$/ ' , $ tokens [$ stackPtr ]['content ' ], $ matches );
84- $ typeName = $ matches [1 ];
57+ $ suggestedType = implode ('| ' , $ suggestedNames );
58+ if ($ type !== $ suggestedType ) {
59+ $ fix = $ phpcsFile ->addFixableError (
60+ 'For type-hinting in PHPDocs, use %s instead of %s ' ,
61+ $ stackPtr + 2 ,
62+ 'Invalid ' ,
63+ [$ suggestedType , $ type ]
64+ );
65+
66+ if ($ fix ) {
67+ $ replacement = $ suggestedType ;
68+ if (isset ($ match [2 ])) {
69+ $ replacement .= $ match [2 ];
70+ }
8571
86- $ this ->validateTypeName ($ phpcsFile , $ stackPtr , $ typeName );
72+ $ phpcsFile ->fixer ->replaceToken ($ stackPtr + 2 , $ replacement );
73+ }
74+ }
75+ }
8776 }
8877
8978 /**
90- * @param File $phpcsFile
91- * @param int $stackPtr
9279 * @param string $typeName
80+ *
81+ * @return string
9382 */
94- private function validateTypeName ( File $ phpcsFile , int $ stackPtr , string $ typeName ): void
83+ private function getValidTypeName ( string $ typeName ): string
9584 {
96- $ validTypeName = $ this ->getValidTypeName ($ typeName );
97-
98- if (null !== $ validTypeName ) {
99- $ needFix = $ phpcsFile ->addFixableError (
100- 'For type-hinting in PHPDocs and casting, use %s instead of %s ' ,
101- $ stackPtr ,
102- '' ,
103- [$ validTypeName , $ typeName ]
104- );
105- if ($ needFix ) {
106- $ tokens = $ phpcsFile ->getTokens ();
107- $ phpcsFile ->fixer ->beginChangeset ();
108- $ newContent = str_replace (
109- $ typeName ,
110- $ validTypeName ,
111- $ tokens [$ stackPtr ]['content ' ]
112- );
113- $ phpcsFile ->fixer ->replaceToken ($ stackPtr , $ newContent );
114- $ phpcsFile ->fixer ->endChangeset ();
115- }
85+ $ parts = preg_split ('/([\<\,\>])/ ' , $ typeName , -1 , PREG_SPLIT_DELIM_CAPTURE );
86+ $ partsNumber = count ($ parts ) - 1 ;
87+
88+ $ validType = '' ;
89+ for ($ i = 0 ; $ i < $ partsNumber ; $ i += 2 ) {
90+ $ validType .= $ this ->suggestType ($ parts [$ i ]).$ parts [$ i + 1 ];
11691 }
92+
93+ if ('' !== $ parts [$ partsNumber ]) {
94+ $ validType .= $ this ->suggestType ($ parts [$ partsNumber ]);
95+ }
96+
97+ return $ validType ;
11798 }
11899
119100 /**
120101 * @param string $typeName
121102 *
122- * @return string|null
103+ * @return string
123104 */
124- private function getValidTypeName (string $ typeName ): ? string
105+ private function suggestType (string $ typeName ): string
125106 {
126- $ typeName = strtolower ($ typeName );
127- if (isset ($ this ->types [$ typeName ])) {
128- return $ this ->types [$ typeName ];
107+ if ('[] ' === substr ($ typeName , -2 )) {
108+ return $ this ->suggestType (substr ($ typeName , 0 , -2 )).'[] ' ;
109+ }
110+
111+ $ lowerType = strtolower ($ typeName );
112+ switch ($ lowerType ) {
113+ case 'bool ' :
114+ case 'boolean ' :
115+ return 'bool ' ;
116+ case 'int ' :
117+ case 'integer ' :
118+ return 'int ' ;
129119 }
130120
131- return null ;
121+ return Common:: suggestType ( $ typeName ) ;
132122 }
133123}
0 commit comments