@@ -464,6 +464,8 @@ class PHP extends Tokenizer
464
464
T_CLOSE_SHORT_ARRAY => 1 ,
465
465
T_TYPE_UNION => 1 ,
466
466
T_TYPE_INTERSECTION => 1 ,
467
+ T_TYPE_OPEN_PARENTHESIS => 1 ,
468
+ T_TYPE_CLOSE_PARENTHESIS => 1 ,
467
469
];
468
470
469
471
/**
@@ -747,6 +749,9 @@ protected function tokenize($string)
747
749
748
750
/*
749
751
Special case for `static` used as a function name, i.e. `static()`.
752
+
753
+ Note: this may incorrectly change the static keyword directly before a DNF property type.
754
+ If so, this will be caught and corrected for in the additional processing.
750
755
*/
751
756
752
757
if ($ tokenIsArray === true
@@ -2712,21 +2717,23 @@ protected function processAdditional()
2712
2717
if (isset ($ this ->tokens [$ x ]) === true && $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS ) {
2713
2718
$ ignore = Tokens::$ emptyTokens ;
2714
2719
$ ignore += [
2715
- T_ARRAY => T_ARRAY ,
2716
- T_CALLABLE => T_CALLABLE ,
2717
- T_COLON => T_COLON ,
2718
- T_NAMESPACE => T_NAMESPACE ,
2719
- T_NS_SEPARATOR => T_NS_SEPARATOR ,
2720
- T_NULL => T_NULL ,
2721
- T_TRUE => T_TRUE ,
2722
- T_FALSE => T_FALSE ,
2723
- T_NULLABLE => T_NULLABLE ,
2724
- T_PARENT => T_PARENT ,
2725
- T_SELF => T_SELF ,
2726
- T_STATIC => T_STATIC ,
2727
- T_STRING => T_STRING ,
2728
- T_TYPE_UNION => T_TYPE_UNION ,
2729
- T_TYPE_INTERSECTION => T_TYPE_INTERSECTION ,
2720
+ T_ARRAY => T_ARRAY ,
2721
+ T_CALLABLE => T_CALLABLE ,
2722
+ T_COLON => T_COLON ,
2723
+ T_NAMESPACE => T_NAMESPACE ,
2724
+ T_NS_SEPARATOR => T_NS_SEPARATOR ,
2725
+ T_NULL => T_NULL ,
2726
+ T_TRUE => T_TRUE ,
2727
+ T_FALSE => T_FALSE ,
2728
+ T_NULLABLE => T_NULLABLE ,
2729
+ T_PARENT => T_PARENT ,
2730
+ T_SELF => T_SELF ,
2731
+ T_STATIC => T_STATIC ,
2732
+ T_STRING => T_STRING ,
2733
+ T_TYPE_UNION => T_TYPE_UNION ,
2734
+ T_TYPE_INTERSECTION => T_TYPE_INTERSECTION ,
2735
+ T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS ,
2736
+ T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS ,
2730
2737
];
2731
2738
2732
2739
$ closer = $ this ->tokens [$ x ]['parenthesis_closer ' ];
@@ -3029,10 +3036,15 @@ protected function processAdditional()
3029
3036
continue ;
3030
3037
} else if ($ this ->tokens [$ i ]['code ' ] === T_BITWISE_OR
3031
3038
|| $ this ->tokens [$ i ]['code ' ] === T_BITWISE_AND
3039
+ || $ this ->tokens [$ i ]['code ' ] === T_OPEN_PARENTHESIS
3040
+ || $ this ->tokens [$ i ]['code ' ] === T_CLOSE_PARENTHESIS
3032
3041
) {
3033
3042
/*
3034
3043
Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
3035
3044
Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
3045
+ Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
3046
+
3047
+ All type related tokens will be converted in one go as soon as this section is hit.
3036
3048
*/
3037
3049
3038
3050
$ allowed = [
@@ -3048,20 +3060,22 @@ protected function processAdditional()
3048
3060
T_NS_SEPARATOR => T_NS_SEPARATOR ,
3049
3061
];
3050
3062
3051
- $ suspectedType = null ;
3052
- $ typeTokenCount = 0 ;
3063
+ $ suspectedType = null ;
3064
+ $ typeTokenCountAfter = 0 ;
3053
3065
3054
3066
for ($ x = ($ i + 1 ); $ x < $ numTokens ; $ x ++) {
3055
3067
if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
3056
3068
continue ;
3057
3069
}
3058
3070
3059
3071
if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
3060
- ++$ typeTokenCount ;
3072
+ ++$ typeTokenCountAfter ;
3061
3073
continue ;
3062
3074
}
3063
3075
3064
- if ($ typeTokenCount > 0
3076
+ if (($ typeTokenCountAfter > 0
3077
+ || ($ this ->tokens [$ i ]['code ' ] === T_CLOSE_PARENTHESIS
3078
+ && isset ($ this ->tokens [$ i ]['parenthesis_owner ' ]) === false ))
3065
3079
&& ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
3066
3080
|| $ this ->tokens [$ x ]['code ' ] === T_ELLIPSIS )
3067
3081
) {
@@ -3092,6 +3106,7 @@ protected function processAdditional()
3092
3106
&& $ this ->tokens [$ this ->tokens [$ x ]['scope_condition ' ]]['code ' ] === T_FUNCTION
3093
3107
) {
3094
3108
$ suspectedType = 'return ' ;
3109
+ break ;
3095
3110
}
3096
3111
3097
3112
if ($ this ->tokens [$ x ]['code ' ] === T_EQUAL ) {
@@ -3103,35 +3118,95 @@ protected function processAdditional()
3103
3118
break ;
3104
3119
}//end for
3105
3120
3106
- if ($ typeTokenCount === 0 || isset ($ suspectedType ) === false ) {
3107
- // Definitely not a union or intersection type, move on.
3121
+ if (($ typeTokenCountAfter === 0
3122
+ && ($ this ->tokens [$ i ]['code ' ] !== T_CLOSE_PARENTHESIS
3123
+ || isset ($ this ->tokens [$ i ]['parenthesis_owner ' ]) === true ))
3124
+ || isset ($ suspectedType ) === false
3125
+ ) {
3126
+ // Definitely not a union, intersection or DNF type, move on.
3108
3127
continue ;
3109
3128
}
3110
3129
3111
3130
if ($ suspectedType === 'property or parameter ' ) {
3112
3131
unset($ allowed [T_STATIC ]);
3113
3132
}
3114
3133
3115
- $ typeTokenCount = 0 ;
3116
- $ typeOperators = [$ i ];
3117
- $ confirmed = false ;
3134
+ $ typeTokenCountBefore = 0 ;
3135
+ $ typeOperators = [$ i ];
3136
+ $ confirmed = false ;
3137
+ $ maybeNullable = null ;
3118
3138
3119
3139
for ($ x = ($ i - 1 ); $ x >= 0 ; $ x --) {
3120
3140
if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
3121
3141
continue ;
3122
3142
}
3123
3143
3144
+ if ($ suspectedType === 'property or parameter '
3145
+ && $ this ->tokens [$ x ]['code ' ] === T_STRING
3146
+ && strtolower ($ this ->tokens [$ x ]['content ' ]) === 'static '
3147
+ ) {
3148
+ // Static keyword followed directly by an open parenthesis for a DNF type.
3149
+ // This token should be T_STATIC and was incorrectly identified as a function call before.
3150
+ $ this ->tokens [$ x ]['code ' ] = T_STATIC ;
3151
+ $ this ->tokens [$ x ]['type ' ] = 'T_STATIC ' ;
3152
+
3153
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3154
+ $ line = $ this ->tokens [$ x ]['line ' ];
3155
+ echo "\t* token $ x on line $ line changed back from T_STRING to T_STATIC " .PHP_EOL ;
3156
+ }
3157
+ }
3158
+
3159
+ if ($ suspectedType === 'property or parameter '
3160
+ && $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS
3161
+ ) {
3162
+ // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
3163
+ // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
3164
+ if (isset ($ this ->tokens [$ x ]['parenthesis_owner ' ]) === true
3165
+ && $ this ->tokens [$ this ->tokens [$ x ]['parenthesis_owner ' ]]['code ' ] === T_FUNCTION
3166
+ ) {
3167
+ $ confirmed = true ;
3168
+ break ;
3169
+ } else {
3170
+ // This may still be an arrow function which hasn't be handled yet.
3171
+ for ($ y = ($ x - 1 ); $ y > 0 ; $ y --) {
3172
+ if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ y ]['code ' ]]) === false
3173
+ && $ this ->tokens [$ y ]['code ' ] !== T_BITWISE_AND
3174
+ ) {
3175
+ // Non-whitespace content.
3176
+ break ;
3177
+ }
3178
+ }
3179
+
3180
+ if ($ this ->tokens [$ y ]['code ' ] === T_FN ) {
3181
+ $ confirmed = true ;
3182
+ break ;
3183
+ }
3184
+ }
3185
+ }//end if
3186
+
3124
3187
if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
3125
- ++$ typeTokenCount ;
3188
+ ++$ typeTokenCountBefore ;
3126
3189
continue ;
3127
3190
}
3128
3191
3129
- // Union and intersection types can't use the nullable operator, but be tolerant to parse errors.
3130
- if ($ typeTokenCount > 0 && $ this ->tokens [$ x ]['code ' ] === T_NULLABLE ) {
3192
+ // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3193
+ if (($ typeTokenCountBefore > 0
3194
+ || ($ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS && isset ($ this ->tokens [$ x ]['parenthesis_owner ' ]) === false ))
3195
+ && ($ this ->tokens [$ x ]['code ' ] === T_NULLABLE
3196
+ || $ this ->tokens [$ x ]['code ' ] === T_INLINE_THEN )
3197
+ ) {
3198
+ if ($ this ->tokens [$ x ]['code ' ] === T_INLINE_THEN ) {
3199
+ $ maybeNullable = $ x ;
3200
+ }
3201
+
3131
3202
continue ;
3132
3203
}
3133
3204
3134
- if ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_OR || $ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND ) {
3205
+ if ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_OR
3206
+ || $ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
3207
+ || $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS
3208
+ || $ this ->tokens [$ x ]['code ' ] === T_CLOSE_PARENTHESIS
3209
+ ) {
3135
3210
$ typeOperators [] = $ x ;
3136
3211
continue ;
3137
3212
}
@@ -3217,14 +3292,40 @@ protected function processAdditional()
3217
3292
$ line = $ this ->tokens [$ x ]['line ' ];
3218
3293
echo "\t* token $ x on line $ line changed from T_BITWISE_OR to T_TYPE_UNION " .PHP_EOL ;
3219
3294
}
3220
- } else {
3295
+ } else if ( $ this -> tokens [ $ x ][ ' code ' ] === T_BITWISE_AND ) {
3221
3296
$ this ->tokens [$ x ]['code ' ] = T_TYPE_INTERSECTION ;
3222
3297
$ this ->tokens [$ x ]['type ' ] = 'T_TYPE_INTERSECTION ' ;
3223
3298
3224
3299
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3225
3300
$ line = $ this ->tokens [$ x ]['line ' ];
3226
3301
echo "\t* token $ x on line $ line changed from T_BITWISE_AND to T_TYPE_INTERSECTION " .PHP_EOL ;
3227
3302
}
3303
+ } else if ($ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS ) {
3304
+ $ this ->tokens [$ x ]['code ' ] = T_TYPE_OPEN_PARENTHESIS ;
3305
+ $ this ->tokens [$ x ]['type ' ] = 'T_TYPE_OPEN_PARENTHESIS ' ;
3306
+
3307
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3308
+ $ line = $ this ->tokens [$ x ]['line ' ];
3309
+ echo "\t* token $ x on line $ line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS " .PHP_EOL ;
3310
+ }
3311
+ } else if ($ this ->tokens [$ x ]['code ' ] === T_CLOSE_PARENTHESIS ) {
3312
+ $ this ->tokens [$ x ]['code ' ] = T_TYPE_CLOSE_PARENTHESIS ;
3313
+ $ this ->tokens [$ x ]['type ' ] = 'T_TYPE_CLOSE_PARENTHESIS ' ;
3314
+
3315
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3316
+ $ line = $ this ->tokens [$ x ]['line ' ];
3317
+ echo "\t* token $ x on line $ line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS " .PHP_EOL ;
3318
+ }
3319
+ }//end if
3320
+ }//end foreach
3321
+
3322
+ if (isset ($ maybeNullable ) === true ) {
3323
+ $ this ->tokens [$ maybeNullable ]['code ' ] = T_NULLABLE ;
3324
+ $ this ->tokens [$ maybeNullable ]['type ' ] = 'T_NULLABLE ' ;
3325
+
3326
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3327
+ $ line = $ this ->tokens [$ maybeNullable ]['line ' ];
3328
+ echo "\t* token $ maybeNullable on line $ line changed from T_INLINE_THEN to T_NULLABLE " .PHP_EOL ;
3228
3329
}
3229
3330
}
3230
3331
0 commit comments