diff --git a/Universal/Docs/DeclareStatements/BlockModeStandard.xml b/Universal/Docs/DeclareStatements/BlockModeStandard.xml
new file mode 100644
index 00000000..5c9be11d
--- /dev/null
+++ b/Universal/Docs/DeclareStatements/BlockModeStandard.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ // Code.
+}
+
+declare(strict_types=1) :
+ // Code.
+enddeclare;
+ ]]>
+
+
+
diff --git a/Universal/Sniffs/DeclareStatements/BlockModeSniff.php b/Universal/Sniffs/DeclareStatements/BlockModeSniff.php
new file mode 100644
index 00000000..7e79a36b
--- /dev/null
+++ b/Universal/Sniffs/DeclareStatements/BlockModeSniff.php
@@ -0,0 +1,287 @@
+ true,
+ 'ticks' => true,
+ 'encoding' => true,
+ ];
+
+ /**
+ * Returns an array of tokens this test wants to listen for.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function register()
+ {
+ return [T_DECLARE];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 1.0.0
+ *
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
+ * @param int $stackPtr The position of the current token
+ * in the stack passed in $tokens.
+ *
+ * @return void
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) {
+ // Parse error or live coding, bow out.
+ return;
+ }
+
+ $openParenPtr = $tokens[$stackPtr]['parenthesis_opener'];
+ $closeParenPtr = $tokens[$stackPtr]['parenthesis_closer'];
+
+ $directiveStrings = [];
+ // 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) {
+ $contentsLC = \strtolower($tokens[$i]['content']);
+ if (isset($this->allowedDirectives[$contentsLC])) {
+ $phpcsFile->recordMetric($i, self::DECLARE_TYPE_METRIC, $contentsLC);
+ $directiveStrings[$contentsLC] = true;
+ }
+ }
+ }
+
+ unset($i);
+
+ if (empty($directiveStrings)) {
+ // No valid directives were found, this is outside the scope of this sniff.
+ return;
+ }
+
+ $usesBlockMode = isset($tokens[$stackPtr]['scope_opener']);
+
+ if ($usesBlockMode) {
+ $phpcsFile->recordMetric($stackPtr, self::DECLARE_SCOPE_METRIC, 'Block mode');
+ } else {
+ $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'])) {
+ $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 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 the declare statement.',
+ $stackPtr,
+ 'UnexpectedCodeFound'
+ );
+ return;
+ }
+
+ // Multiple directives - if one requires block mode usage, other has to as well.
+ if (count($directiveStrings) > 1
+ && (($this->encodingBlockMode === 'disallow' && $this->ticksBlockMode !== 'disallow')
+ || ($this->ticksBlockMode === 'disallow' && $this->encodingBlockMode !== 'disallow'))
+ ) {
+ $phpcsFile->addError(
+ 'Multiple directives found, but one of them is disallowing the use of block mode.',
+ $stackPtr,
+ 'Forbidden' // <= Duplicate error code for different message (line 175)
+ );
+ return;
+ }
+
+ if (($this->encodingBlockMode === 'allow' || $this->encodingBlockMode === 'require')
+ && $this->ticksBlockMode === 'disallow'
+ && $usesBlockMode && isset($directiveStrings['ticks'])
+ ) {
+ $phpcsFile->addError(
+ 'Block mode is not allowed for ticks directive.',
+ $stackPtr,
+ 'DisallowedTicksBlockMode'
+ );
+ return;
+ }
+
+ if ($this->ticksBlockMode === 'require'
+ && !$usesBlockMode && isset($directiveStrings['ticks'])
+ ) {
+ $phpcsFile->addError(
+ 'Block mode is required for ticks directive.',
+ $stackPtr,
+ 'RequiredTicksBlockMode'
+ );
+ return;
+ }
+
+ if ($this->encodingBlockMode === 'disallow'
+ && ($this->ticksBlockMode === 'allow' || $this->ticksBlockMode === 'require')
+ && $usesBlockMode && isset($directiveStrings['encoding'])
+ ) {
+ $phpcsFile->addError(
+ 'Block mode is not allowed for encoding directive.',
+ $stackPtr,
+ 'DisallowedEncodingBlockMode'
+ );
+ return;
+ }
+
+ if ($this->encodingBlockMode === 'disallow' && $this->ticksBlockMode === 'disallow' && $usesBlockMode) {
+ $phpcsFile->addError(
+ 'Block mode is not allowed for any declare directive.',
+ $stackPtr,
+ 'DisallowedBlockMode'
+ );
+ return;
+ }
+
+ if ($this->encodingBlockMode === 'require'
+ && !$usesBlockMode && isset($directiveStrings['encoding'])
+ ) {
+ $phpcsFile->addError(
+ 'Block mode is required for encoding directive.',
+ $stackPtr,
+ 'RequiredEncodingBlockMode'
+ );
+ return;
+ }
+ }
+}
diff --git a/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc b/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc
new file mode 100644
index 00000000..0a463018
--- /dev/null
+++ b/Universal/Tests/DeclareStatements/BlockModeUnitTest.1.inc
@@ -0,0 +1,78 @@
+ =>
+ */
+ public function getErrorList($testFile = '')
+ {
+ switch ($testFile) {
+ case 'BlockModeUnitTest.1.inc':
+ return [
+ 21 => 1,
+ 25 => 1,
+ 29 => 1,
+ 38 => 1,
+ 47 => 1,
+ 51 => 1,
+ 55 => 1,
+ 56 => 1,
+ 60 => 1,
+ 69 => 1,
+ ];
+
+ case 'BlockModeUnitTest.3.inc':
+ return [
+ 7 => 1,
+ 11 => 1,
+ 19 => 1,
+ 20 => 1,
+ 31 => 1,
+ 38 => 1,
+ 42 => 1,
+ 43 => 1,
+ ];
+
+ case 'BlockModeUnitTest.4.inc':
+ return [
+ 27 => 1,
+ 31 => 1,
+ 56 => 1,
+ 75 => 1,
+ 79 => 1,
+ 86 => 1,
+ 90 => 1,
+ 98 => 1,
+ 102 => 1,
+ 117 => 1,
+ 121 => 1,
+ 125 => 1,
+ 144 => 1,
+ 157 => 1,
+ 161 => 1,
+ 167 => 1,
+ 188 => 1,
+ 190 => 1,
+ ];
+
+ case 'BlockModeUnitTest.5.inc':
+ return [
+ 51 => 1,
+ 55 => 1,
+ 56 => 1,
+ 66 => 1,
+ 86 => 1,
+ 111 => 1,
+ 117 => 1,
+ 126 => 1,
+ 134 => 1,
+ 135 => 1,
+ 144 => 1,
+ 148 => 1,
+ 172 => 1,
+ 181 => 1,
+ 185 => 1,
+ 192 => 1,
+ 213 => 1,
+ 229 => 1,
+ 239 => 1,
+ 245 => 1,
+ 248 => 1,
+ 268 => 1,
+ 276 => 1,
+ 281 => 1,
+ ];
+
+ default:
+ return [];
+ }
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array =>
+ */
+ public function getWarningList()
+ {
+ return [];
+ }
+}