diff --git a/BigBite/Docs/Files/DeclareStatementStandard.xml b/BigBite/Docs/Files/DeclareStatementStandard.xml new file mode 100644 index 0000000..21cf699 --- /dev/null +++ b/BigBite/Docs/Files/DeclareStatementStandard.xml @@ -0,0 +1,65 @@ + + + + + + + ( strict_types = 1 ); +declare( strict_types = 1 ); + + + ]]> + + + (strict_types =1) ; +declare( + strict_types = 1 +); + ]]> + + + + + strict_types = 1 ); + ]]> + + + struct_types = 1 ); + ]]> + + + + + { + // code here. +} + ]]> + + + { + // code here. + } + ]]> + + + diff --git a/BigBite/Sniffs/Files/DeclareStatementSniff.php b/BigBite/Sniffs/Files/DeclareStatementSniff.php new file mode 100644 index 0000000..40cb9d3 --- /dev/null +++ b/BigBite/Sniffs/Files/DeclareStatementSniff.php @@ -0,0 +1,824 @@ + + */ + public $validDirectives = array( 'encoding', 'strict_types', 'ticks' ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( \T_DECLARE ); + } + + /** + * Processes this sniff, when one of its tokens is encountered. + * + * @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 ) { + $this->lineEnding = $phpcsFile->eolChar; + + $tokens = $phpcsFile->getTokens(); + + $endOfStatement = $phpcsFile->findEndOfStatement( $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( ! in_array( $tokens[ $endOfStatement ]['code'], array( \T_SEMICOLON, \T_CLOSE_CURLY_BRACKET ), true ) ) { + return; + } + + $this->process_keyword( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_keyword( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_open_paren( $phpcsFile, $stackPtr, $tokens ); + $this->process_directive( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_directive( $phpcsFile, $stackPtr, $tokens ); + $this->process_before_equals( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_equals( $phpcsFile, $stackPtr, $tokens ); + $this->process_before_directive_value( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_directive_value( $phpcsFile, $stackPtr, $tokens ); + $this->process_after_close_paren( $phpcsFile, $stackPtr, $tokens ); + } + + /** + * Check that the declare keyword is lowercase. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_keyword( File $phpcsFile, $stackPtr, array $tokens ) { + $keyword = $tokens[ $stackPtr ]; + + // It conforms. + if ( strtolower( $keyword['content'] ) === $keyword['content'] ) { + return; + } + + $error = 'Declare keyword should be in lower case; found "%s".'; + $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'KeywordCasing', array( $keyword['content'] ) ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $stackPtr, 'declare' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is no whitespace between the declare keyword and the opening parenthesis. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_keyword( File $phpcsFile, $stackPtr, array $tokens ) { + // User is live-coding, or there is a syntax error. + if ( ! array_key_exists( ( $stackPtr + 1 ), $tokens ) ) { + return; + } + + $openParen = $phpcsFile->findNext( \T_OPEN_PARENTHESIS, $stackPtr ); + $nextToken = ( $stackPtr + 1 ); + + // There is no whitespace between the keyword and opening parenthesis. + if ( $nextToken === $openParen ) { + return; + } + + $toReplace = array(); + for ( $i = $nextToken; $i < $openParen; $i++ ) { + if ( \T_WHITESPACE === $tokens[ $i ]['code'] ) { + $toReplace[] = $i; + } + } + + // No whitespace tokens found. + if ( 0 === count( $toReplace ) ) { + return; + } + + $error = 'Expected no whitespace between declare keyword and its opening parenthesis.'; + $fix = $phpcsFile->addFixableError( $error, $stackPtr, 'SpaceFoundAfterKeyword' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + foreach ( $toReplace as $whitespaceToken ) { + $phpcsFile->fixer->replaceToken( $whitespaceToken, '' ); + } + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space after the opening parenthesis. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_open_paren( File $phpcsFile, $stackPtr, array $tokens ) { + $openParen = $phpcsFile->findNext( \T_OPEN_PARENTHESIS, $stackPtr, null, false ); + + // User is live-coding, or there is a syntax error. + if ( false === $openParen ) { + return; + } + + $nextToken = ( $openParen + 1 ); + + // We expect one space. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $nextToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space between opening parenthesis and directive in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $openParen, 'TooMuchSpaceFoundBeforeDirective', array( $spaceLength ) ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + + return; + } + + $error = 'Expected one space between opening parenthesis and directive in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'NoSpaceFoundBeforeDirective' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that the directive is valid and lowercase. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_directive( File $phpcsFile, $stackPtr, array $tokens ) { + $directive = $phpcsFile->findNext( \T_STRING, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $directive ) { + return; + } + + $foundDirective = $tokens[ $directive ]['content']; + $expectedDirective = strtolower( $foundDirective ); + + // Directive is invalid; this takes precedence over casing. + if ( ! in_array( $expectedDirective, $this->validDirectives, true ) ) { + $error = 'Invalid directive found in declare statement; expected one of %s; found %s.'; + $phpcsFile->addError( $error, $directive, 'InvalidDirective', array( implode( ', ', $this->validDirectives ), $foundDirective ) ); + + return; + } + + if ( $foundDirective === $expectedDirective ) { + return; + } + + $error = 'Directives in a declare statement should be lower case; expected %s; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $directive, 'DirectiveNotLowerCase', array( $expectedDirective, $foundDirective ) ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $directive, $expectedDirective ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space after the directive. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_directive( File $phpcsFile, $stackPtr, array $tokens ) { + $directive = $phpcsFile->findNext( \T_STRING, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $directive ) { + return; + } + + $nextToken = ( $directive + 1 ); + $equalsSign = $phpcsFile->findNext( \T_EQUAL, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $equalsSign ) { + return; + } + + // There is no content between directive and equals. + if ( $nextToken === $equalsSign ) { + $error = 'Expected one space between the directive and equals sign in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $directive, 'NoSpaceFoundAfterDirective' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $directive, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // There is whitespace after the directive. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $nextToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space after the directive in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'TooMuchSpaceFoundAfterDirective', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // Non-whitespace content after the directive. + $error = 'Expected one space after the directive in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $directive, 'NoSpaceFoundAfterDirective' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $directive, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space before the equals. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_before_equals( File $phpcsFile, $stackPtr, array $tokens ) { + $directive = $phpcsFile->findNext( \T_STRING, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $directive ) { + return; + } + + $equalsSign = $phpcsFile->findNext( \T_EQUAL, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $equalsSign ) { + return; + } + + $prevToken = ( $equalsSign - 1 ); + + // There is no content between directive and equals. + if ( $prevToken === $directive ) { + // Whitespace addition will be handled by {process_after_directive()}. + return; + } + + // There is whitespace before the equals. + if ( \T_WHITESPACE === $tokens[ $prevToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $prevToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $prevToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $prevToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $prevToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space before the equals sign in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $prevToken, 'TooMuchSpaceFoundBeforeEquals', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $prevToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // Non-whitespace content before the equals. + $error = 'Expected one space before the equals sign in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $equalsSign, 'NoSpaceFoundBeforeEquals' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $equalsSign, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space after the equals sign. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_equals( File $phpcsFile, $stackPtr, array $tokens ) { + $equalsSign = $phpcsFile->findNext( \T_EQUAL, $stackPtr ); + + // User is live coding, or there is a syntax error. + if ( false === $equalsSign ) { + return; + } + + $nextToken = ( $equalsSign + 1 ); + + // There is whitespace after the equals. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $nextToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space after the equals sign in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'TooMuchSpaceFoundAfterEquals', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // Non-whitespace content after the equals. + $error = 'Expected one space after the equals sign in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $equalsSign, 'NoSpaceFoundAfterEquals' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $equalsSign, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space before the directive value. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_before_directive_value( File $phpcsFile, $stackPtr, array $tokens ) { + $equalsSign = $phpcsFile->findNext( \T_EQUAL, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $equalsSign ) { + return; + } + + $directiveValue = $phpcsFile->findNext( array( \T_STRING, \T_LNUMBER ), $equalsSign ); + + // User is live coding, or there is a syntax error. + if ( false === $directiveValue ) { + return; + } + + $prevToken = ( $directiveValue - 1 ); + + // There is no content between the equals and directive value. + if ( $prevToken === $equalsSign ) { + // Whitespace addition will be handled by {process_after_equals()}. + return; + } + + // There is whitespace before the directive value. + if ( \T_WHITESPACE === $tokens[ $prevToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $prevToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $prevToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $prevToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $prevToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space before the directive value in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $directiveValue, 'TooMuchSpaceFoundBeforeDirectiveValue', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $prevToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // Non-whitespace content before the directive value. + $error = 'Expected one space before the directive value in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $directiveValue, 'NoSpaceFoundBeforeDirectiveValue' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $directiveValue, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a single space between directive value and closing parenthesis. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_directive_value( File $phpcsFile, $stackPtr, array $tokens ) { + $equalsSign = $phpcsFile->findNext( \T_EQUAL, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $equalsSign ) { + return; + } + + $directiveValue = $phpcsFile->findNext( \T_WHITESPACE, ( $equalsSign + 1 ), null, true ); + + // User is live-coding, or there is a syntax error. + if ( false === $directiveValue ) { + return; + } + + $nextToken = ( $directiveValue + 1 ); + + // We expect a whitespace character. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $nextToken ]['content'], $this->lineEnding ) ) { + $error = 'The contents of a declare statement should be single-spaced on one line.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'DirectiveOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + // We're done here, no need to check for space length. + return; + } + + if ( 1 === $spaceLength ) { + return; + } + + $error = 'Expected one space after the directive value in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'TooMuchSpaceFoundAfterDirectiveValue', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + // Non-whitespace content after the directive value. + $error = 'Expected one space after the directive value in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $directiveValue, 'NoSpaceFoundAfterDirectiveValue' ); + + if ( true !== $fix ) { + return; + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent( $directiveValue, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + + /** + * Check that there is a semi-colon or curly braces after the closing parenthesis. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_after_close_paren( File $phpcsFile, $stackPtr, array $tokens ) { + $closingParen = $phpcsFile->findNext( \T_CLOSE_PARENTHESIS, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $closingParen ) { + return; + } + + $nextToken = ( $closingParen + 1 ); + + // Statement is termninated with semi-colon. + if ( \T_SEMICOLON === $tokens[ $nextToken ]['code'] ) { + return; + } + + // There is whitespace after the directive value, and then it terminates. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] && \T_SEMICOLON === $tokens[ ( $nextToken + 1 ) ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + $error = 'Expected no space between the closing parenthesis and semi-colon in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'SpaceFoundAfterClosingParen', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, '' ); + $phpcsFile->fixer->endChangeset(); + } + + // We can bail here because the statement terminates, and we need not do further checking. + return; + } + + $this->process_block_statement( $phpcsFile, $stackPtr, $tokens ); + } + + /** + * Process block statement braces. + * + * @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. + * @param array> $tokens All found tokens in the file. + * + * @return void + */ + protected function process_block_statement( File $phpcsFile, $stackPtr, array $tokens ) { + $closingParen = $phpcsFile->findNext( \T_CLOSE_PARENTHESIS, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $closingParen ) { + return; + } + + $openingBrace = $phpcsFile->findNext( \T_OPEN_CURLY_BRACKET, $stackPtr ); + + // User is live-coding, or there is a syntax error. + if ( false === $openingBrace ) { + return; + } + + $nextToken = ( $closingParen + 1 ); + + // We expect a single space between the closing paren and opening curly brace. + if ( \T_WHITESPACE === $tokens[ $nextToken ]['code'] ) { + $spaceLength = strlen( $tokens[ $nextToken ]['content'] ); + + // And no newlines! + if ( false !== strpos( $tokens[ $nextToken ]['content'], $this->lineEnding ) ) { + $error = 'The opening curly brace should be on the same line as the declare keyword.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'OpeningCurlyBraceOnNewLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + } + + if ( 1 !== $spaceLength ) { + $error = 'Expected one space between the closing parenthesis and opening curly brace in a declare statement; found %s.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'TooMuchSpaceFoundAfterClosingParen', array( $spaceLength ) ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + } + } + + // There is no whitespace. + if ( \T_OPEN_CURLY_BRACKET === $tokens[ $nextToken ]['code'] ) { + $error = 'Expected one space between the closing parenthesis and curly brace in a declare statement; found none.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'NoSpaceFoundAfterClosingParen' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $nextToken, ' ' ); + $phpcsFile->fixer->endChangeset(); + } + } + + $openingBrace = $phpcsFile->findNext( \T_OPEN_CURLY_BRACKET, $stackPtr ); + $nextToken = $phpcsFile->findNext( Tokens::$emptyTokens, ( $openingBrace + 1 ), null, true ); + + // The opening curly brace is not the last content on the line. + if ( $nextToken && $tokens[ $openingBrace ]['line'] === $tokens[ $nextToken ]['line'] && \T_CLOSE_CURLY_BRACKET !== $tokens[ $nextToken ]['code'] ) { + $error = 'The opening curly brace should be the last content on a line in a declare statement.'; + $fix = $phpcsFile->addFixableError( $error, $nextToken, 'ContentFoundAfterOpeningCurlyBrace' ); + + if ( true === $fix ) { + $prevToken = $phpcsFile->findPrevious( \T_WHITESPACE, ( $nextToken - 1 ), null, true ); + + $phpcsFile->fixer->beginChangeset(); + for ( $i = ( $prevToken + 1 ); $i < $nextToken; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + $phpcsFile->fixer->addNewlineBefore( $nextToken ); + $phpcsFile->fixer->endChangeset(); + } + } + + // User is live-coding, or there is a syntax error. + if ( ! isset( $tokens[ $tokens[ $openingBrace ]['bracket_closer'] ] ) ) { + return; + } + + $closingBrace = $tokens[ $openingBrace ]['bracket_closer']; + + // Closing brace is aligned with declare keyword. + if ( $tokens[ $closingBrace ]['column'] === $tokens[ $stackPtr ]['column'] ) { + return; + } + + if ( $tokens[ $openingBrace ]['line'] === $tokens[ $closingBrace ]['line'] ) { + $error = 'The closing brace of a declare statement should be on a new line.'; + $fix = $phpcsFile->addFixableError( $error, $closingBrace, 'ClosingBraceWrongLine' ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContentBefore( $closingBrace, $this->lineEnding ); + $phpcsFile->fixer->endChangeset(); + } + + return; + } + + $error = 'The closing brace of a declare statement should be aligned with the declare keyword.'; + $phpcsFile->addError( $error, $closingBrace, 'ClosingBraceNotAligned' ); + } +} diff --git a/BigBite/Tests/Files/DeclareStatementUnitTest.1.inc b/BigBite/Tests/Files/DeclareStatementUnitTest.1.inc new file mode 100644 index 0000000..502110c --- /dev/null +++ b/BigBite/Tests/Files/DeclareStatementUnitTest.1.inc @@ -0,0 +1,63 @@ + + */ + public function getErrorList( $testFile = '' ) { + switch ( $testFile ) { + case 'DeclareStatementUnitTest.1.inc': + return array( + 2 => 4, + 3 => 5, + 4 => 4, + 5 => 6, + 6 => 5, + 7 => 4, + 8 => 4, + 9 => 6, + 10 => 4, + 11 => 5, + 12 => 3, + 15 => 1, + 16 => 3, + 19 => 3, + 20 => 2, + 23 => 6, + 25 => 5, + 29 => 5, + 32 => 1, + 34 => 5, + 36 => 5, + 38 => 4, + 42 => 1, + 43 => 3, + 45 => 4, + 48 => 4, + 51 => 5, + 54 => 5, + 55 => 1, + 57 => 5, + 60 => 0, + 62 => 1, + 63 => 1, + ); + case 'DeclareStatementUnitTest.2.inc': + return array(); + default: + return array(); + } + } + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() { + return array(); + } +} diff --git a/BigBite/ruleset.xml b/BigBite/ruleset.xml index 463145a..48af4b5 100644 --- a/BigBite/ruleset.xml +++ b/BigBite/ruleset.xml @@ -199,14 +199,6 @@ - - - - - - - -