18
18
/**
19
19
* Checks the style of the declare statement.
20
20
*
21
- * `declare` directives can be written in two different styles:
21
+ * Declare statements can be written in two different styles:
22
22
* 1. Applied to the rest of the file, usually written at the top of a file like `declare(strict_types=1);`.
23
23
* 2. Applied to a limited scope using curly braces or using alternative control structure syntax
24
- * (the exception to this rule is the `strict_types` directive). This is known as a block mode.
24
+ * (the exception to this rule is the `strict_types` directive). This is known as block mode.
25
25
*
26
- * You can also have multiple directives written inside the `declare` directive .
27
- * This sniff will check the preferred mode of the `declare` directive .
26
+ * There can be multiple directives inside a `declare` statement .
27
+ * This sniff checks the preferred mode for the `declare` statements .
28
28
*
29
29
* You can modify the sniff by changing the whether the block mode of the encoding and the ticks directives
30
30
* is allowed, disallowed or required. By default, the ticks directive, if written in
@@ -45,7 +45,7 @@ class BlockModeSniff implements Sniff
45
45
*
46
46
* @var string
47
47
*/
48
- const DECLARE_SCOPE_METRIC = 'Declare directive scope ' ;
48
+ const DECLARE_SCOPE_METRIC = 'Declare statement scope ' ;
49
49
50
50
/**
51
51
* Name of the metric.
@@ -57,10 +57,10 @@ class BlockModeSniff implements Sniff
57
57
const DECLARE_TYPE_METRIC = 'Declare directive type ' ;
58
58
59
59
/**
60
- * The option for the encoding directive .
60
+ * Whether block mode is allowed for ` encoding` directives .
61
61
*
62
- * Can be one of: 'disallow', 'allow' (no preference), 'require'.
63
- * By default it's disallowed .
62
+ * Can be one of: 'disallow', 'allow' (no preference), or 'require'.
63
+ * Defaults to: 'disallow' .
64
64
*
65
65
* @since 1.0.0
66
66
*
@@ -69,10 +69,10 @@ class BlockModeSniff implements Sniff
69
69
public $ encodingBlockMode = 'disallow ' ;
70
70
71
71
/**
72
- * The option for the ticks directive .
72
+ * Whether block mode is allowed for ` ticks` directives .
73
73
*
74
- * Can be one of: 'disallow', 'allow' (no preference), 'require'.
75
- * By default it's allowed .
74
+ * Can be one of: 'disallow', 'allow' (no preference), or 'require'.
75
+ * Defaults to: 'allow' .
76
76
*
77
77
* @since 1.0.0
78
78
*
@@ -83,7 +83,7 @@ class BlockModeSniff implements Sniff
83
83
/**
84
84
* The default option for the strict_types directive.
85
85
*
86
- * Only directive that cannot be written in block mode is strict_types.
86
+ * Block mode is not allowed for the ` strict_types` directive .
87
87
* Using it in block mode will throw a PHP fatal error.
88
88
*
89
89
* @since 1.0.0
@@ -114,9 +114,7 @@ class BlockModeSniff implements Sniff
114
114
*/
115
115
public function register ()
116
116
{
117
- return [
118
- T_DECLARE
119
- ];
117
+ return [T_DECLARE ];
120
118
}
121
119
122
120
/**
@@ -146,10 +144,12 @@ public function process(File $phpcsFile, $stackPtr)
146
144
// Get the next string and check if it's an allowed directive.
147
145
// Find all the directive strings inside the declare statement.
148
146
for ($ i = $ openParenPtr ; $ i <= $ closeParenPtr ; $ i ++) {
149
- if ($ tokens [$ i ]['code ' ] === \T_STRING
150
- && isset ($ this ->allowedDirectives [\strtolower ($ tokens [$ i ]['content ' ])])
151
- ) {
152
- $ directiveStrings [$ tokens [$ i ]['content ' ]] = true ;
147
+ if ($ tokens [$ i ]['code ' ] === \T_STRING ) {
148
+ $ contentsLC = \strtolower ($ tokens [$ i ]['content ' ]);
149
+ if (isset ($ this ->allowedDirectives [$ contentsLC ])) {
150
+ $ phpcsFile ->recordMetric ($ i , self ::DECLARE_TYPE_METRIC , $ contentsLC );
151
+ $ directiveStrings [$ contentsLC ] = true ;
152
+ }
153
153
}
154
154
}
155
155
@@ -163,40 +163,59 @@ public function process(File $phpcsFile, $stackPtr)
163
163
$ usesBlockMode = isset ($ tokens [$ stackPtr ]['scope_opener ' ]);
164
164
165
165
if ($ usesBlockMode ) {
166
- $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Block ' );
166
+ $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Block mode ' );
167
167
} else {
168
- $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Global ' );
168
+ $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'File mode ' );
169
169
}
170
170
171
171
// If strict types is defined using block mode, throw error.
172
172
if ($ usesBlockMode && isset ($ directiveStrings ['strict_types ' ])) {
173
- $ phpcsFile ->addError (
174
- 'strict_types declaration must not use block mode. ' ,
175
- $ stackPtr ,
176
- 'Forbidden '
177
- );
173
+ $ error = 'strict_types declaration must not use block mode. ' ;
174
+ $ code = 'Forbidden ' ;
175
+
176
+ if (isset ($ tokens [$ stackPtr ]['scope_closer ' ])) {
177
+ // If there is no scope closer, we cannot auto-fix.
178
+ $ phpcsFile ->addError ($ error , $ stackPtr , $ code );
179
+ return ;
180
+ }
181
+
182
+ $ fix = $ phpcsFile ->addFixableError ($ error , $ stackPtr , $ code );
183
+
184
+ if ($ fix === true ) {
185
+ $ phpcsFile ->fixer ->beginChangeset ();
186
+ $ phpcsFile ->fixer ->addContent ($ closeParenPtr , '; ' );
187
+ $ phpcsFile ->fixer ->replaceToken ($ tokens [$ stackPtr ]['scope_opener ' ], '' );
188
+
189
+ // Remove potential whitespace between parenthesis closer and the brace.
190
+ for ($ i = ($ tokens [$ stackPtr ]['scope_opener ' ] - 1 ); $ i > 0 ; $ i --) {
191
+ if ($ tokens [$ i ]['code ' ] !== \T_WHITESPACE ) {
192
+ break ;
193
+ }
194
+
195
+ $ phpcsFile ->fixer ->replaceToken ($ i , '' );
196
+ }
197
+
198
+ $ phpcsFile ->fixer ->replaceToken ($ tokens [$ stackPtr ]['scope_closer ' ], '' );
199
+ $ phpcsFile ->fixer ->endChangeset ();
200
+ }
178
201
return ;
179
202
}
180
203
181
- // Check if there is a code between the declare statement and opening brace/alternative syntax.
182
- $ nextNonEmpty = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ closeParenPtr + 1 ), null , true );
183
- $ directiveCloserTokens = [\T_SEMICOLON , \T_CLOSE_TAG , \T_OPEN_CURLY_BRACKET , \T_COLON ];
184
-
185
- if (!in_array ($ tokens [$ nextNonEmpty ]['code ' ], $ directiveCloserTokens , true )) {
204
+ // Check if there is code between the declare statement and opening brace/alternative syntax.
205
+ $ nextNonEmpty = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ closeParenPtr + 1 ), null , true );
206
+ if ($ tokens [$ nextNonEmpty ]['code ' ] !== \T_SEMICOLON
207
+ && $ tokens [$ nextNonEmpty ]['code ' ] !== \T_CLOSE_TAG
208
+ && $ tokens [$ nextNonEmpty ]['code ' ] !== \T_OPEN_CURLY_BRACKET
209
+ && $ tokens [$ nextNonEmpty ]['code ' ] !== \T_COLON
210
+ ) {
186
211
$ phpcsFile ->addError (
187
- 'Unexpected code found after opening the declare statement without closing it . ' ,
212
+ 'Unexpected code found after the declare statement. ' ,
188
213
$ stackPtr ,
189
214
'UnexpectedCodeFound '
190
215
);
191
216
return ;
192
217
}
193
218
194
- foreach (\array_keys ($ directiveStrings ) as $ directiveString ) {
195
- if (isset ($ this ->allowedDirectives [$ directiveString ])) {
196
- $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_TYPE_METRIC , $ directiveString );
197
- }
198
- }
199
-
200
219
// Multiple directives - if one requires block mode usage, other has to as well.
201
220
if (count ($ directiveStrings ) > 1
202
221
&& (($ this ->encodingBlockMode === 'disallow ' && $ this ->ticksBlockMode !== 'disallow ' )
@@ -205,7 +224,7 @@ public function process(File $phpcsFile, $stackPtr)
205
224
$ phpcsFile ->addError (
206
225
'Multiple directives found, but one of them is disallowing the use of block mode. ' ,
207
226
$ stackPtr ,
208
- 'Forbidden '
227
+ 'Forbidden ' // <= Duplicate error code for different message (line 175)
209
228
);
210
229
return ;
211
230
}
0 commit comments