diff --git a/Universal/Docs/DeclareStatements/BlockModeStandard.xml b/Universal/Docs/DeclareStatements/BlockModeStandard.xml index a64c7549..5c9be11d 100644 --- a/Universal/Docs/DeclareStatements/BlockModeStandard.xml +++ b/Universal/Docs/DeclareStatements/BlockModeStandard.xml @@ -16,7 +16,7 @@ declare(strict_types=1); declare(encoding='utf-8'); declare(ticks=10): - // Code. + // Code. enddeclare; ]]> @@ -36,20 +36,24 @@ enddeclare; - + - + { - // Code. +declare(strict_types=1) { + // Code. } + +declare(strict_types=1) : + // Code. +enddeclare; ]]> diff --git a/Universal/Sniffs/DeclareStatements/BlockModeSniff.php b/Universal/Sniffs/DeclareStatements/BlockModeSniff.php index d3cdea43..6a152ebe 100644 --- a/Universal/Sniffs/DeclareStatements/BlockModeSniff.php +++ b/Universal/Sniffs/DeclareStatements/BlockModeSniff.php @@ -18,13 +18,13 @@ /** * Checks the style of the declare statement. * - * `declare` directives can be written in two different styles: + * Declare statements can be written in two different styles: * 1. Applied to the rest of the file, usually written at the top of a file like `declare(strict_types=1);`. * 2. Applied to a limited scope using curly braces or using alternative control structure syntax - * (the exception to this rule is the `strict_types` directive). This is known as a block mode. + * (the exception to this rule is the `strict_types` directive). This is known as block mode. * - * You can also have multiple directives written inside the `declare` directive. - * This sniff will check the preferred mode of the `declare` directive. + * There can be multiple directives inside a `declare` statement. + * This sniff checks the preferred mode for the `declare` statements. * * You can modify the sniff by changing the whether the block mode of the encoding and the ticks directives * is allowed, disallowed or required. By default, the ticks directive, if written in @@ -45,7 +45,7 @@ class BlockModeSniff implements Sniff * * @var string */ - const DECLARE_SCOPE_METRIC = 'Declare directive scope'; + const DECLARE_SCOPE_METRIC = 'Declare statement scope'; /** * Name of the metric. @@ -57,10 +57,10 @@ class BlockModeSniff implements Sniff const DECLARE_TYPE_METRIC = 'Declare directive type'; /** - * The option for the encoding directive. + * Whether block mode is allowed for `encoding` directives. * - * Can be one of: 'disallow', 'allow' (no preference), 'require'. - * By default it's disallowed. + * Can be one of: 'disallow', 'allow' (no preference), or 'require'. + * Defaults to: 'disallow'. * * @since 1.0.0 * @@ -69,10 +69,10 @@ class BlockModeSniff implements Sniff public $encodingBlockMode = 'disallow'; /** - * The option for the ticks directive. + * Whether block mode is allowed for `ticks` directives. * - * Can be one of: 'disallow', 'allow' (no preference), 'require'. - * By default it's allowed. + * Can be one of: 'disallow', 'allow' (no preference), or 'require'. + * Defaults to: 'allow'. * * @since 1.0.0 * @@ -83,7 +83,7 @@ class BlockModeSniff implements Sniff /** * The default option for the strict_types directive. * - * Only directive that cannot be written in block mode is strict_types. + * Block mode is not allowed for the `strict_types` directive. * Using it in block mode will throw a PHP fatal error. * * @since 1.0.0 @@ -114,9 +114,7 @@ class BlockModeSniff implements Sniff */ public function register() { - return [ - T_DECLARE - ]; + return [T_DECLARE]; } /** @@ -146,10 +144,12 @@ public function process(File $phpcsFile, $stackPtr) // Get the next string and check if it's an allowed directive. // Find all the directive strings inside the declare statement. for ($i = $openParenPtr; $i <= $closeParenPtr; $i++) { - if ($tokens[$i]['code'] === \T_STRING - && isset($this->allowedDirectives[\strtolower($tokens[$i]['content'])]) - ) { - $directiveStrings[$tokens[$i]['content']] = true; + if ($tokens[$i]['code'] === \T_STRING) { + $contentsLC = \strtolower($tokens[$i]['content']); + if (isset($this->allowedDirectives[$contentsLC])) { + $phpcsFile->recordMetric($i, self::DECLARE_TYPE_METRIC, $contentsLC); + $directiveStrings[$contentsLC] = true; + } } } @@ -163,40 +163,59 @@ public function process(File $phpcsFile, $stackPtr) $usesBlockMode = isset($tokens[$stackPtr]['scope_opener']); if ($usesBlockMode) { - $phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'Block'); + $phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'Block mode'); } else { - $phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'Global'); + $phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'File mode'); } // If strict types is defined using block mode, throw error. if ($usesBlockMode && isset($directiveStrings['strict_types'])) { - $phpcsFile->addError( - 'strict_types declaration must not use block mode.', - $stackPtr, - 'Forbidden' - ); + $error = 'strict_types declaration must not use block mode.'; + $code = 'Forbidden'; + + if (isset($tokens[$stackPtr]['scope_closer'])) { + // If there is no scope closer, we cannot auto-fix. + $phpcsFile->addError($error, $stackPtr, $code); + return; + } + + $fix = $phpcsFile->addFixableError($error, $stackPtr, $code); + + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent($closeParenPtr, ';'); + $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['scope_opener'], ''); + + // Remove potential whitespace between parenthesis closer and the brace. + for ($i = ($tokens[$stackPtr]['scope_opener'] - 1); $i > 0; $i--) { + if ($tokens[$i]['code'] !== \T_WHITESPACE) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['scope_closer'], ''); + $phpcsFile->fixer->endChangeset(); + } return; } - // Check if there is a code between the declare statement and opening brace/alternative syntax. - $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParenPtr + 1), null, true); - $directiveCloserTokens = [\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_CURLY_BRACKET, \T_COLON]; - - if (!in_array($tokens[$nextNonEmpty]['code'], $directiveCloserTokens, true)) { + // Check if there is code between the declare statement and opening brace/alternative syntax. + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParenPtr + 1), null, true); + if ($tokens[$nextNonEmpty]['code'] !== \T_SEMICOLON + && $tokens[$nextNonEmpty]['code'] !== \T_CLOSE_TAG + && $tokens[$nextNonEmpty]['code'] !== \T_OPEN_CURLY_BRACKET + && $tokens[$nextNonEmpty]['code'] !== \T_COLON + ) { $phpcsFile->addError( - 'Unexpected code found after opening the declare statement without closing it.', + 'Unexpected code found after the declare statement.', $stackPtr, 'UnexpectedCodeFound' ); return; } - foreach (\array_keys($directiveStrings) as $directiveString) { - if (isset($this->allowedDirectives[$directiveString])) { - $phpcsFile->recordMetric($stackPtr, self::DECLARE_TYPE_METRIC, $directiveString); - } - } - // Multiple directives - if one requires block mode usage, other has to as well. if (count($directiveStrings) > 1 && (($this->encodingBlockMode === 'disallow' && $this->ticksBlockMode !== 'disallow') @@ -205,7 +224,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->addError( 'Multiple directives found, but one of them is disallowing the use of block mode.', $stackPtr, - 'Forbidden' + 'Forbidden' // <= Duplicate error code for different message (line 175) ); return; } diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc index c97e8e79..0a463018 100644 --- a/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc +++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc @@ -76,7 +76,3 @@ echo 'hi!'; } declare ticks=1; // Intentional live coding error. Ignore. - -/* Rest to the default directives for the next run */ -// phpcs:set Universal.DeclareStatements.BlockMode encodingBlockMode disallow -// phpcs:set Universal.DeclareStatements.BlockMode ticksBlockMode allow diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.2.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.2.inc index 484db4c1..4503293b 100644 --- a/Universal/Tests/DeclareStatements/BlockModeUnitTest.2.inc +++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.2.inc @@ -7,6 +7,4 @@ declare(enCodINg='1'); declare(STRICT_TYPES=1); declare(random_directive=false); -/* Rest to the default directives for the next run */ -// phpcs:set Universal.DeclareStatements.BlockMode encodingBlockMode disallow -// phpcs:set Universal.DeclareStatements.BlockMode ticksBlockMode allow +// What's invalid about these ? (safe for line 4 and 8) Directives are case-INsensitive. diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.3.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.3.inc index 3b1ec74e..c0d34718 100644 --- a/Universal/Tests/DeclareStatements/BlockModeUnitTest.3.inc +++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.3.inc @@ -45,6 +45,6 @@ declare(strict_types=1, encoding='ISO-8859-1'): // Error. enddeclare; enddeclare; -/* Rest to the default directives for the next run */ +/* Reset to the default directives for the next run */ // phpcs:set Universal.DeclareStatements.BlockMode encodingBlockMode disallow // phpcs:set Universal.DeclareStatements.BlockMode ticksBlockMode allow diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.4.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.4.inc index 2939177d..845696d2 100644 --- a/Universal/Tests/DeclareStatements/BlockModeUnitTest.4.inc +++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.4.inc @@ -203,6 +203,6 @@ declare(ticks=1): // Ok. enddeclare; enddeclare; -/* Rest to the default directives for the next run */ +/* Reset to the default directives for the next run */ // phpcs:set Universal.DeclareStatements.BlockMode encodingBlockMode disallow // phpcs:set Universal.DeclareStatements.BlockMode ticksBlockMode allow diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.5.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.5.inc index 4c4ed66b..2a8d2716 100644 --- a/Universal/Tests/DeclareStatements/BlockModeUnitTest.5.inc +++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.5.inc @@ -296,6 +296,6 @@ declare(ticks=1) { // Ok. // Code. } -/* Rest to the default directives for the next run */ +/* Reset to the default directives for the next run */ // phpcs:set Universal.DeclareStatements.BlockMode encodingBlockMode disallow // phpcs:set Universal.DeclareStatements.BlockMode ticksBlockMode allow