@@ -441,6 +441,7 @@ class PHP extends Tokenizer
441441 T_BACKTICK => 1 ,
442442 T_OPEN_SHORT_ARRAY => 1 ,
443443 T_CLOSE_SHORT_ARRAY => 1 ,
444+ T_TYPE_UNION => 1 ,
444445 ];
445446
446447 /**
@@ -1477,6 +1478,7 @@ function return types. We want to keep the parenthesis map clean,
14771478 T_SELF => T_SELF ,
14781479 T_PARENT => T_PARENT ,
14791480 T_NAMESPACE => T_NAMESPACE ,
1481+ T_STATIC => T_STATIC ,
14801482 T_NS_SEPARATOR => T_NS_SEPARATOR ,
14811483 ];
14821484
@@ -1511,12 +1513,14 @@ function return types. We want to keep the parenthesis map clean,
15111513 }//end for
15121514
15131515 // Any T_ARRAY tokens we find between here and the next
1514- // token that can't be part of the return type need to be
1516+ // token that can't be part of the return type, need to be
15151517 // converted to T_STRING tokens.
15161518 for ($ x ; $ x < $ numTokens ; $ x ++) {
1517- if (is_array ($ tokens [$ x ]) === false || isset ($ allowed [$ tokens [$ x ][0 ]]) === false ) {
1519+ if ((is_array ($ tokens [$ x ]) === false && $ tokens [$ x ] !== '| ' )
1520+ || (is_array ($ tokens [$ x ]) === true && isset ($ allowed [$ tokens [$ x ][0 ]]) === false )
1521+ ) {
15181522 break ;
1519- } else if ($ tokens [$ x ][0 ] === T_ARRAY ) {
1523+ } else if (is_array ( $ tokens [ $ x ]) === true && $ tokens [$ x ][0 ] === T_ARRAY ) {
15201524 $ tokens [$ x ][0 ] = T_STRING ;
15211525
15221526 if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
@@ -1998,6 +2002,7 @@ protected function processAdditional()
19982002 T_PARENT => T_PARENT ,
19992003 T_SELF => T_SELF ,
20002004 T_STATIC => T_STATIC ,
2005+ T_TYPE_UNION => T_TYPE_UNION ,
20012006 ];
20022007
20032008 $ closer = $ this ->tokens [$ x ]['parenthesis_closer ' ];
@@ -2178,6 +2183,177 @@ protected function processAdditional()
21782183 }
21792184 }
21802185
2186+ continue ;
2187+ } else if ($ this ->tokens [$ i ]['code ' ] === T_BITWISE_OR ) {
2188+ /*
2189+ Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
2190+ */
2191+
2192+ $ allowed = [
2193+ T_STRING => T_STRING ,
2194+ T_CALLABLE => T_CALLABLE ,
2195+ T_SELF => T_SELF ,
2196+ T_PARENT => T_PARENT ,
2197+ T_STATIC => T_STATIC ,
2198+ T_FALSE => T_FALSE ,
2199+ T_NULL => T_NULL ,
2200+ T_NS_SEPARATOR => T_NS_SEPARATOR ,
2201+ ];
2202+
2203+ $ suspectedType = null ;
2204+ $ typeTokenCount = 0 ;
2205+
2206+ for ($ x = ($ i + 1 ); $ x < $ numTokens ; $ x ++) {
2207+ if (isset (Util \Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2208+ continue ;
2209+ }
2210+
2211+ if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2212+ ++$ typeTokenCount ;
2213+ continue ;
2214+ }
2215+
2216+ if ($ typeTokenCount > 0
2217+ && ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
2218+ || $ this ->tokens [$ x ]['code ' ] === T_ELLIPSIS )
2219+ ) {
2220+ // Skip past reference and variadic indicators for parameter types.
2221+ ++$ x ;
2222+ continue ;
2223+ }
2224+
2225+ if ($ this ->tokens [$ x ]['code ' ] === T_VARIABLE ) {
2226+ // Parameter/Property defaults can not contain variables, so this could be a type.
2227+ $ suspectedType = 'property or parameter ' ;
2228+ break ;
2229+ }
2230+
2231+ if ($ this ->tokens [$ x ]['code ' ] === T_DOUBLE_ARROW ) {
2232+ // Possible arrow function.
2233+ $ suspectedType = 'return ' ;
2234+ break ;
2235+ }
2236+
2237+ if ($ this ->tokens [$ x ]['code ' ] === T_SEMICOLON ) {
2238+ // Possible abstract method or interface method.
2239+ $ suspectedType = 'return ' ;
2240+ break ;
2241+ }
2242+
2243+ if ($ this ->tokens [$ x ]['code ' ] === T_OPEN_CURLY_BRACKET
2244+ && isset ($ this ->tokens [$ x ]['scope_condition ' ]) === true
2245+ && $ this ->tokens [$ this ->tokens [$ x ]['scope_condition ' ]]['code ' ] === T_FUNCTION
2246+ ) {
2247+ $ suspectedType = 'return ' ;
2248+ }
2249+
2250+ break ;
2251+ }//end for
2252+
2253+ if ($ typeTokenCount === 0 || isset ($ suspectedType ) === false ) {
2254+ // Definitely not a union type, move on.
2255+ continue ;
2256+ }
2257+
2258+ $ typeTokenCount = 0 ;
2259+ $ unionOperators = [$ i ];
2260+ $ confirmed = false ;
2261+
2262+ for ($ x = ($ i - 1 ); $ x >= 0 ; $ x --) {
2263+ if (isset (Util \Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2264+ continue ;
2265+ }
2266+
2267+ if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2268+ ++$ typeTokenCount ;
2269+ continue ;
2270+ }
2271+
2272+ // Union types can't use the nullable operator, but be tolerant to parse errors.
2273+ if ($ typeTokenCount > 0 && $ this ->tokens [$ x ]['code ' ] === T_NULLABLE ) {
2274+ continue ;
2275+ }
2276+
2277+ if ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_OR ) {
2278+ $ unionOperators [] = $ x ;
2279+ continue ;
2280+ }
2281+
2282+ if ($ suspectedType === 'return ' && $ this ->tokens [$ x ]['code ' ] === T_COLON ) {
2283+ $ confirmed = true ;
2284+ break ;
2285+ }
2286+
2287+ if ($ suspectedType === 'property or parameter '
2288+ && (isset (Util \Tokens::$ scopeModifiers [$ this ->tokens [$ x ]['code ' ]]) === true
2289+ || $ this ->tokens [$ x ]['code ' ] === T_VAR )
2290+ ) {
2291+ // This will also confirm constructor property promotion parameters, but that's fine.
2292+ $ confirmed = true ;
2293+ }
2294+
2295+ break ;
2296+ }//end for
2297+
2298+ if ($ confirmed === false
2299+ && $ suspectedType === 'property or parameter '
2300+ && isset ($ this ->tokens [$ i ]['nested_parenthesis ' ]) === true
2301+ ) {
2302+ $ parens = $ this ->tokens [$ i ]['nested_parenthesis ' ];
2303+ $ last = end ($ parens );
2304+
2305+ if (isset ($ this ->tokens [$ last ]['parenthesis_owner ' ]) === true
2306+ && $ this ->tokens [$ this ->tokens [$ last ]['parenthesis_owner ' ]]['code ' ] === T_FUNCTION
2307+ ) {
2308+ $ confirmed = true ;
2309+ } else {
2310+ // No parenthesis owner set, this may be an arrow function which has not yet
2311+ // had additional processing done.
2312+ if (isset ($ this ->tokens [$ last ]['parenthesis_opener ' ]) === true ) {
2313+ for ($ x = ($ this ->tokens [$ last ]['parenthesis_opener ' ] - 1 ); $ x >= 0 ; $ x --) {
2314+ if (isset (Util \Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2315+ continue ;
2316+ }
2317+
2318+ break ;
2319+ }
2320+
2321+ if ($ this ->tokens [$ x ]['code ' ] === T_FN ) {
2322+ for (--$ x ; $ x >= 0 ; $ x --) {
2323+ if (isset (Util \Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true
2324+ || $ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
2325+ ) {
2326+ continue ;
2327+ }
2328+
2329+ break ;
2330+ }
2331+
2332+ if ($ this ->tokens [$ x ]['code ' ] !== T_FUNCTION ) {
2333+ $ confirmed = true ;
2334+ }
2335+ }
2336+ }//end if
2337+ }//end if
2338+
2339+ unset($ parens , $ last );
2340+ }//end if
2341+
2342+ if ($ confirmed === false ) {
2343+ // Not a union type after all, move on.
2344+ continue ;
2345+ }
2346+
2347+ foreach ($ unionOperators as $ x ) {
2348+ $ this ->tokens [$ x ]['code ' ] = T_TYPE_UNION ;
2349+ $ this ->tokens [$ x ]['type ' ] = 'T_TYPE_UNION ' ;
2350+
2351+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
2352+ $ line = $ this ->tokens [$ x ]['line ' ];
2353+ echo "\t* token $ x on line $ line changed from T_BITWISE_OR to T_TYPE_UNION " .PHP_EOL ;
2354+ }
2355+ }
2356+
21812357 continue ;
21822358 } else if ($ this ->tokens [$ i ]['code ' ] === T_STATIC ) {
21832359 for ($ x = ($ i - 1 ); $ x > 0 ; $ x --) {
0 commit comments