@@ -471,6 +471,8 @@ class PHP extends Tokenizer
471
471
T_CLOSE_SHORT_ARRAY => 1 ,
472
472
T_TYPE_UNION => 1 ,
473
473
T_TYPE_INTERSECTION => 1 ,
474
+ T_TYPE_OPEN_PARENTHESIS => 1 ,
475
+ T_TYPE_CLOSE_PARENTHESIS => 1 ,
474
476
];
475
477
476
478
/**
@@ -755,6 +757,9 @@ protected function tokenize($string)
755
757
756
758
/*
757
759
Special case for `static` used as a function name, i.e. `static()`.
760
+
761
+ Note: this may incorrectly change the static keyword directly before a DNF property type.
762
+ If so, this will be caught and corrected for in the additional processing.
758
763
*/
759
764
760
765
if ($ tokenIsArray === true
@@ -2533,22 +2538,24 @@ protected function processAdditional()
2533
2538
if (isset ($ this ->tokens [$ x ]) === true && $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS ) {
2534
2539
$ ignore = Tokens::$ emptyTokens ;
2535
2540
$ ignore += [
2536
- T_ARRAY => T_ARRAY ,
2537
- T_CALLABLE => T_CALLABLE ,
2538
- T_COLON => T_COLON ,
2539
- T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED ,
2540
- T_NAME_QUALIFIED => T_NAME_QUALIFIED ,
2541
- T_NAME_RELATIVE => T_NAME_RELATIVE ,
2542
- T_NULL => T_NULL ,
2543
- T_TRUE => T_TRUE ,
2544
- T_FALSE => T_FALSE ,
2545
- T_NULLABLE => T_NULLABLE ,
2546
- T_PARENT => T_PARENT ,
2547
- T_SELF => T_SELF ,
2548
- T_STATIC => T_STATIC ,
2549
- T_STRING => T_STRING ,
2550
- T_TYPE_INTERSECTION => T_TYPE_INTERSECTION ,
2551
- T_TYPE_UNION => T_TYPE_UNION ,
2541
+ T_ARRAY => T_ARRAY ,
2542
+ T_CALLABLE => T_CALLABLE ,
2543
+ T_COLON => T_COLON ,
2544
+ T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED ,
2545
+ T_NAME_QUALIFIED => T_NAME_QUALIFIED ,
2546
+ T_NAME_RELATIVE => T_NAME_RELATIVE ,
2547
+ T_NULL => T_NULL ,
2548
+ T_TRUE => T_TRUE ,
2549
+ T_FALSE => T_FALSE ,
2550
+ T_NULLABLE => T_NULLABLE ,
2551
+ T_PARENT => T_PARENT ,
2552
+ T_SELF => T_SELF ,
2553
+ T_STATIC => T_STATIC ,
2554
+ T_STRING => T_STRING ,
2555
+ T_TYPE_UNION => T_TYPE_UNION ,
2556
+ T_TYPE_INTERSECTION => T_TYPE_INTERSECTION ,
2557
+ T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS ,
2558
+ T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS ,
2552
2559
];
2553
2560
2554
2561
$ closer = $ this ->tokens [$ x ]['parenthesis_closer ' ];
@@ -2854,10 +2861,15 @@ protected function processAdditional()
2854
2861
continue ;
2855
2862
} else if ($ this ->tokens [$ i ]['code ' ] === T_BITWISE_OR
2856
2863
|| $ this ->tokens [$ i ]['code ' ] === T_BITWISE_AND
2864
+ || $ this ->tokens [$ i ]['code ' ] === T_OPEN_PARENTHESIS
2865
+ || $ this ->tokens [$ i ]['code ' ] === T_CLOSE_PARENTHESIS
2857
2866
) {
2858
2867
/*
2859
2868
Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
2860
2869
Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
2870
+ Convert "(" and ")" to T_TYPE_(OPEN|CLOSE)_PARENTHESIS or leave as T_(OPEN|CLOSE)_PARENTHESIS.
2871
+
2872
+ All type related tokens will be converted in one go as soon as this section is hit.
2861
2873
*/
2862
2874
2863
2875
$ allowed = [
@@ -2874,20 +2886,22 @@ protected function processAdditional()
2874
2886
T_NULL => T_NULL ,
2875
2887
];
2876
2888
2877
- $ suspectedType = null ;
2878
- $ typeTokenCount = 0 ;
2889
+ $ suspectedType = null ;
2890
+ $ typeTokenCountAfter = 0 ;
2879
2891
2880
2892
for ($ x = ($ i + 1 ); $ x < $ numTokens ; $ x ++) {
2881
2893
if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2882
2894
continue ;
2883
2895
}
2884
2896
2885
2897
if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2886
- ++$ typeTokenCount ;
2898
+ ++$ typeTokenCountAfter ;
2887
2899
continue ;
2888
2900
}
2889
2901
2890
- if ($ typeTokenCount > 0
2902
+ if (($ typeTokenCountAfter > 0
2903
+ || ($ this ->tokens [$ i ]['code ' ] === T_CLOSE_PARENTHESIS
2904
+ && isset ($ this ->tokens [$ i ]['parenthesis_owner ' ]) === false ))
2891
2905
&& ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
2892
2906
|| $ this ->tokens [$ x ]['code ' ] === T_ELLIPSIS )
2893
2907
) {
@@ -2918,6 +2932,7 @@ protected function processAdditional()
2918
2932
&& $ this ->tokens [$ this ->tokens [$ x ]['scope_condition ' ]]['code ' ] === T_FUNCTION
2919
2933
) {
2920
2934
$ suspectedType = 'return ' ;
2935
+ break ;
2921
2936
}
2922
2937
2923
2938
if ($ this ->tokens [$ x ]['code ' ] === T_EQUAL ) {
@@ -2929,35 +2944,95 @@ protected function processAdditional()
2929
2944
break ;
2930
2945
}//end for
2931
2946
2932
- if ($ typeTokenCount === 0 || isset ($ suspectedType ) === false ) {
2933
- // Definitely not a union or intersection type, move on.
2947
+ if (($ typeTokenCountAfter === 0
2948
+ && ($ this ->tokens [$ i ]['code ' ] !== T_CLOSE_PARENTHESIS
2949
+ || isset ($ this ->tokens [$ i ]['parenthesis_owner ' ]) === true ))
2950
+ || isset ($ suspectedType ) === false
2951
+ ) {
2952
+ // Definitely not a union, intersection or DNF type, move on.
2934
2953
continue ;
2935
2954
}
2936
2955
2937
2956
if ($ suspectedType === 'property or parameter ' ) {
2938
2957
unset($ allowed [T_STATIC ]);
2939
2958
}
2940
2959
2941
- $ typeTokenCount = 0 ;
2942
- $ typeOperators = [$ i ];
2943
- $ confirmed = false ;
2960
+ $ typeTokenCountBefore = 0 ;
2961
+ $ typeOperators = [$ i ];
2962
+ $ confirmed = false ;
2963
+ $ maybeNullable = null ;
2944
2964
2945
2965
for ($ x = ($ i - 1 ); $ x >= 0 ; $ x --) {
2946
2966
if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2947
2967
continue ;
2948
2968
}
2949
2969
2970
+ if ($ suspectedType === 'property or parameter '
2971
+ && $ this ->tokens [$ x ]['code ' ] === T_STRING
2972
+ && strtolower ($ this ->tokens [$ x ]['content ' ]) === 'static '
2973
+ ) {
2974
+ // Static keyword followed directly by an open parenthesis for a DNF type.
2975
+ // This token should be T_STATIC and was incorrectly identified as a function call before.
2976
+ $ this ->tokens [$ x ]['code ' ] = T_STATIC ;
2977
+ $ this ->tokens [$ x ]['type ' ] = 'T_STATIC ' ;
2978
+
2979
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
2980
+ $ line = $ this ->tokens [$ x ]['line ' ];
2981
+ echo "\t* token $ x on line $ line changed back from T_STRING to T_STATIC " .PHP_EOL ;
2982
+ }
2983
+ }
2984
+
2985
+ if ($ suspectedType === 'property or parameter '
2986
+ && $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS
2987
+ ) {
2988
+ // We need to prevent the open parenthesis for a function/fn declaration from being retokenized
2989
+ // to T_TYPE_OPEN_PARENTHESIS if this is the first parameter in the declaration.
2990
+ if (isset ($ this ->tokens [$ x ]['parenthesis_owner ' ]) === true
2991
+ && $ this ->tokens [$ this ->tokens [$ x ]['parenthesis_owner ' ]]['code ' ] === T_FUNCTION
2992
+ ) {
2993
+ $ confirmed = true ;
2994
+ break ;
2995
+ } else {
2996
+ // This may still be an arrow function which hasn't be handled yet.
2997
+ for ($ y = ($ x - 1 ); $ y > 0 ; $ y --) {
2998
+ if (isset (Tokens::$ emptyTokens [$ this ->tokens [$ y ]['code ' ]]) === false
2999
+ && $ this ->tokens [$ y ]['code ' ] !== T_BITWISE_AND
3000
+ ) {
3001
+ // Non-whitespace content.
3002
+ break ;
3003
+ }
3004
+ }
3005
+
3006
+ if ($ this ->tokens [$ y ]['code ' ] === T_FN ) {
3007
+ $ confirmed = true ;
3008
+ break ;
3009
+ }
3010
+ }
3011
+ }//end if
3012
+
2950
3013
if (isset ($ allowed [$ this ->tokens [$ x ]['code ' ]]) === true ) {
2951
- ++$ typeTokenCount ;
3014
+ ++$ typeTokenCountBefore ;
2952
3015
continue ;
2953
3016
}
2954
3017
2955
- // Union and intersection types can't use the nullable operator, but be tolerant to parse errors.
2956
- if ($ typeTokenCount > 0 && $ this ->tokens [$ x ]['code ' ] === T_NULLABLE ) {
3018
+ // Union, intersection and DNF types can't use the nullable operator, but be tolerant to parse errors.
3019
+ if (($ typeTokenCountBefore > 0
3020
+ || ($ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS && isset ($ this ->tokens [$ x ]['parenthesis_owner ' ]) === false ))
3021
+ && ($ this ->tokens [$ x ]['code ' ] === T_NULLABLE
3022
+ || $ this ->tokens [$ x ]['code ' ] === T_INLINE_THEN )
3023
+ ) {
3024
+ if ($ this ->tokens [$ x ]['code ' ] === T_INLINE_THEN ) {
3025
+ $ maybeNullable = $ x ;
3026
+ }
3027
+
2957
3028
continue ;
2958
3029
}
2959
3030
2960
- if ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_OR || $ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND ) {
3031
+ if ($ this ->tokens [$ x ]['code ' ] === T_BITWISE_OR
3032
+ || $ this ->tokens [$ x ]['code ' ] === T_BITWISE_AND
3033
+ || $ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS
3034
+ || $ this ->tokens [$ x ]['code ' ] === T_CLOSE_PARENTHESIS
3035
+ ) {
2961
3036
$ typeOperators [] = $ x ;
2962
3037
continue ;
2963
3038
}
@@ -3043,14 +3118,40 @@ protected function processAdditional()
3043
3118
$ line = $ this ->tokens [$ x ]['line ' ];
3044
3119
Common::printStatusMessage ("* token $ x on line $ line changed from T_BITWISE_OR to T_TYPE_UNION " , 1 );
3045
3120
}
3046
- } else {
3121
+ } else if ( $ this -> tokens [ $ x ][ ' code ' ] === T_BITWISE_AND ) {
3047
3122
$ this ->tokens [$ x ]['code ' ] = T_TYPE_INTERSECTION ;
3048
3123
$ this ->tokens [$ x ]['type ' ] = 'T_TYPE_INTERSECTION ' ;
3049
3124
3050
3125
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3051
3126
$ line = $ this ->tokens [$ x ]['line ' ];
3052
3127
Common::printStatusMessage ("* token $ x on line $ line changed from T_BITWISE_AND to T_TYPE_INTERSECTION " , 1 );
3053
3128
}
3129
+ } else if ($ this ->tokens [$ x ]['code ' ] === T_OPEN_PARENTHESIS ) {
3130
+ $ this ->tokens [$ x ]['code ' ] = T_TYPE_OPEN_PARENTHESIS ;
3131
+ $ this ->tokens [$ x ]['type ' ] = 'T_TYPE_OPEN_PARENTHESIS ' ;
3132
+
3133
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3134
+ $ line = $ this ->tokens [$ x ]['line ' ];
3135
+ echo "\t* token $ x on line $ line changed from T_OPEN_PARENTHESIS to T_TYPE_OPEN_PARENTHESIS " .PHP_EOL ;
3136
+ }
3137
+ } else if ($ this ->tokens [$ x ]['code ' ] === T_CLOSE_PARENTHESIS ) {
3138
+ $ this ->tokens [$ x ]['code ' ] = T_TYPE_CLOSE_PARENTHESIS ;
3139
+ $ this ->tokens [$ x ]['type ' ] = 'T_TYPE_CLOSE_PARENTHESIS ' ;
3140
+
3141
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3142
+ $ line = $ this ->tokens [$ x ]['line ' ];
3143
+ echo "\t* token $ x on line $ line changed from T_CLOSE_PARENTHESIS to T_TYPE_CLOSE_PARENTHESIS " .PHP_EOL ;
3144
+ }
3145
+ }//end if
3146
+ }//end foreach
3147
+
3148
+ if (isset ($ maybeNullable ) === true ) {
3149
+ $ this ->tokens [$ maybeNullable ]['code ' ] = T_NULLABLE ;
3150
+ $ this ->tokens [$ maybeNullable ]['type ' ] = 'T_NULLABLE ' ;
3151
+
3152
+ if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
3153
+ $ line = $ this ->tokens [$ maybeNullable ]['line ' ];
3154
+ echo "\t* token $ maybeNullable on line $ line changed from T_INLINE_THEN to T_NULLABLE " .PHP_EOL ;
3054
3155
}
3055
3156
}
3056
3157
0 commit comments