1818/**
1919 * Checks the style of the declare statement.
2020 *
21- * `declare` directives can be written in two different styles:
21+ * Declare statements can be written in two different styles:
2222 * 1. Applied to the rest of the file, usually written at the top of a file like `declare(strict_types=1);`.
2323 * 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.
2525 *
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 .
2828 *
2929 * You can modify the sniff by changing the whether the block mode of the encoding and the ticks directives
3030 * is allowed, disallowed or required. By default, the ticks directive, if written in
@@ -45,7 +45,7 @@ class BlockModeSniff implements Sniff
4545 *
4646 * @var string
4747 */
48- const DECLARE_SCOPE_METRIC = 'Declare directive scope ' ;
48+ const DECLARE_SCOPE_METRIC = 'Declare statement scope ' ;
4949
5050 /**
5151 * Name of the metric.
@@ -57,10 +57,10 @@ class BlockModeSniff implements Sniff
5757 const DECLARE_TYPE_METRIC = 'Declare directive type ' ;
5858
5959 /**
60- * The option for the encoding directive .
60+ * Whether block mode is allowed for ` encoding` directives .
6161 *
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' .
6464 *
6565 * @since 1.0.0
6666 *
@@ -69,10 +69,10 @@ class BlockModeSniff implements Sniff
6969 public $ encodingBlockMode = 'disallow ' ;
7070
7171 /**
72- * The option for the ticks directive .
72+ * Whether block mode is allowed for ` ticks` directives .
7373 *
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' .
7676 *
7777 * @since 1.0.0
7878 *
@@ -83,7 +83,7 @@ class BlockModeSniff implements Sniff
8383 /**
8484 * The default option for the strict_types directive.
8585 *
86- * Only directive that cannot be written in block mode is strict_types.
86+ * Block mode is not allowed for the ` strict_types` directive .
8787 * Using it in block mode will throw a PHP fatal error.
8888 *
8989 * @since 1.0.0
@@ -114,9 +114,7 @@ class BlockModeSniff implements Sniff
114114 */
115115 public function register ()
116116 {
117- return [
118- T_DECLARE
119- ];
117+ return [T_DECLARE ];
120118 }
121119
122120 /**
@@ -146,10 +144,12 @@ public function process(File $phpcsFile, $stackPtr)
146144 // Get the next string and check if it's an allowed directive.
147145 // Find all the directive strings inside the declare statement.
148146 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+ }
153153 }
154154 }
155155
@@ -163,40 +163,59 @@ public function process(File $phpcsFile, $stackPtr)
163163 $ usesBlockMode = isset ($ tokens [$ stackPtr ]['scope_opener ' ]);
164164
165165 if ($ usesBlockMode ) {
166- $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Block ' );
166+ $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Block mode ' );
167167 } else {
168- $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'Global ' );
168+ $ phpcsFile ->recordMetric ($ stackPtr , self ::DECLARE_SCOPE_METRIC , 'File mode ' );
169169 }
170170
171171 // If strict types is defined using block mode, throw error.
172172 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+ }
178201 return ;
179202 }
180203
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+ ) {
186211 $ phpcsFile ->addError (
187- 'Unexpected code found after opening the declare statement without closing it . ' ,
212+ 'Unexpected code found after the declare statement. ' ,
188213 $ stackPtr ,
189214 'UnexpectedCodeFound '
190215 );
191216 return ;
192217 }
193218
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-
200219 // Multiple directives - if one requires block mode usage, other has to as well.
201220 if (count ($ directiveStrings ) > 1
202221 && (($ this ->encodingBlockMode === 'disallow ' && $ this ->ticksBlockMode !== 'disallow ' )
@@ -205,7 +224,7 @@ public function process(File $phpcsFile, $stackPtr)
205224 $ phpcsFile ->addError (
206225 'Multiple directives found, but one of them is disallowing the use of block mode. ' ,
207226 $ stackPtr ,
208- 'Forbidden '
227+ 'Forbidden ' // <= Duplicate error code for different message (line 175)
209228 );
210229 return ;
211230 }
0 commit comments