14
14
use PHP_CodeSniffer \Sniffs \Sniff ;
15
15
use PHP_CodeSniffer \Util \Tokens ;
16
16
use PHPCSUtils \Fixers \SpacesFixer ;
17
+ use PHPCSUtils \Tokens \Collections ;
17
18
18
19
/**
19
- * Enforce no space around union type and intersection type separators.
20
+ * Enforce spacing rules around union, intersection and DNF type separators.
20
21
*
21
22
* @since 1.0.0
23
+ * @since 1.3.0 Support for DNF types.
22
24
*/
23
25
final class TypeSeparatorSpacingSniff implements Sniff
24
26
{
25
27
28
+ /**
29
+ * Tokens this sniff targets.
30
+ *
31
+ * @since 1.3.0
32
+ *
33
+ * @var array<int|string, int|string>
34
+ */
35
+ private $ targetTokens = [
36
+ \T_TYPE_UNION => \T_TYPE_UNION ,
37
+ \T_TYPE_INTERSECTION => \T_TYPE_INTERSECTION ,
38
+ \T_TYPE_OPEN_PARENTHESIS => \T_TYPE_OPEN_PARENTHESIS ,
39
+ \T_TYPE_CLOSE_PARENTHESIS => \T_TYPE_CLOSE_PARENTHESIS ,
40
+ ];
41
+
26
42
/**
27
43
* Returns an array of tokens this test wants to listen for.
28
44
*
@@ -32,10 +48,7 @@ final class TypeSeparatorSpacingSniff implements Sniff
32
48
*/
33
49
public function register ()
34
50
{
35
- return [
36
- \T_TYPE_UNION ,
37
- \T_TYPE_INTERSECTION ,
38
- ];
51
+ return $ this ->targetTokens ;
39
52
}
40
53
41
54
/**
@@ -53,28 +66,78 @@ public function process(File $phpcsFile, $stackPtr)
53
66
{
54
67
$ tokens = $ phpcsFile ->getTokens ();
55
68
56
- $ type = ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_UNION ) ? 'union ' : 'intersection ' ;
57
- $ code = \ucfirst ($ type ) . 'Type ' ;
69
+ $ type = 'union ' ;
70
+ $ code = 'UnionType ' ;
71
+ if ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_INTERSECTION ) {
72
+ $ type = 'intersection ' ;
73
+ $ code = 'IntersectionType ' ;
74
+ } elseif ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_OPEN_PARENTHESIS ) {
75
+ $ type = 'DNF parenthesis open ' ;
76
+ $ code = 'DNFOpen ' ;
77
+ } elseif ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_CLOSE_PARENTHESIS ) {
78
+ $ type = 'DNF parenthesis close ' ;
79
+ $ code = 'DNFClose ' ;
80
+ }
58
81
59
- $ prevNonEmpty = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ stackPtr - 1 ), null , true );
60
- SpacesFixer::checkAndFix (
61
- $ phpcsFile ,
62
- $ stackPtr ,
63
- $ prevNonEmpty ,
64
- 0 , // Expected spaces.
65
- 'Expected %s before the ' . $ type . ' type separator. Found: %s ' ,
66
- $ code . 'SpacesBefore ' ,
67
- 'error ' ,
68
- 0 , // Severity.
69
- 'Space before ' . $ type . ' type separator '
70
- );
82
+ $ expectedSpaces = 0 ;
83
+ $ prevNonEmpty = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ stackPtr - 1 ), null , true );
84
+ if ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_OPEN_PARENTHESIS ) {
85
+ if ($ tokens [$ prevNonEmpty ]['code ' ] === \T_COLON
86
+ || $ tokens [$ prevNonEmpty ]['code ' ] === \T_CONST
87
+ || isset (Collections::propertyModifierKeywords ()[$ tokens [$ prevNonEmpty ]['code ' ]]) === true
88
+ ) {
89
+ // Start of return type or property/const type. Always demand 1 space.
90
+ $ expectedSpaces = 1 ;
91
+ }
92
+
93
+ if ($ tokens [$ prevNonEmpty ]['code ' ] === \T_OPEN_PARENTHESIS
94
+ || $ tokens [$ prevNonEmpty ]['code ' ] === \T_COMMA
95
+ ) {
96
+ // Start of parameter type. Allow new line/indent before.
97
+ if ($ tokens [$ prevNonEmpty ]['line ' ] === $ tokens [$ stackPtr ]['line ' ]) {
98
+ $ expectedSpaces = 1 ;
99
+ } else {
100
+ $ expectedSpaces = 'skip ' ;
101
+ }
102
+ }
103
+ }
104
+
105
+ if (isset ($ this ->targetTokens [$ tokens [$ prevNonEmpty ]['code ' ]]) === true ) {
106
+ // Prevent duplicate errors when there are two adjacent operators.
107
+ $ expectedSpaces = 'skip ' ;
108
+ }
109
+
110
+ if ($ expectedSpaces !== 'skip ' ) {
111
+ SpacesFixer::checkAndFix (
112
+ $ phpcsFile ,
113
+ $ stackPtr ,
114
+ $ prevNonEmpty ,
115
+ $ expectedSpaces ,
116
+ 'Expected %s before the ' . $ type . ' type separator. Found: %s ' ,
117
+ $ code . 'SpacesBefore ' ,
118
+ 'error ' ,
119
+ 0 , // Severity.
120
+ 'Space before ' . $ type . ' type separator '
121
+ );
122
+ }
123
+
124
+ $ expectedSpaces = 0 ;
125
+ $ nextNonEmpty = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ stackPtr + 1 ), null , true );
126
+ if ($ tokens [$ stackPtr ]['code ' ] === \T_TYPE_CLOSE_PARENTHESIS ) {
127
+ if ($ tokens [$ nextNonEmpty ]['code ' ] === \T_OPEN_CURLY_BRACKET
128
+ || $ tokens [$ nextNonEmpty ]['code ' ] === \T_VARIABLE
129
+ || $ tokens [$ nextNonEmpty ]['code ' ] === \T_STRING
130
+ ) {
131
+ // End of return type, parameter or property/const type. Always demand 1 space.
132
+ $ expectedSpaces = 1 ;
133
+ }
134
+ }
71
135
72
- $ nextNonEmpty = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ stackPtr + 1 ), null , true );
73
136
SpacesFixer::checkAndFix (
74
137
$ phpcsFile ,
75
138
$ stackPtr ,
76
139
$ nextNonEmpty ,
77
- 0 , // Expected spaces.
140
+ $ expectedSpaces ,
78
141
'Expected %s after the ' . $ type . ' type separator. Found: %s ' ,
79
142
$ code . 'SpacesAfter ' ,
80
143
'error ' ,
0 commit comments