mirror of
https://github.com/10h30/genesis-simple-sidebars.git
synced 2026-07-16 21:13:21 +09:00
Introducing coding standards validation.
This commit is contained in:
committed by
Nathan Rice
parent
6e40c921ab
commit
fd504d41ac
+534
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress Coding Standard.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
|
||||
* @license https://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
namespace WordPress\Sniffs\Arrays;
|
||||
|
||||
use WordPress\Sniff;
|
||||
use WordPress\PHPCSHelper;
|
||||
use PHP_CodeSniffer_Tokens as Tokens;
|
||||
|
||||
/**
|
||||
* Enforces WordPress array indentation for multi-line arrays.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.12.0
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*
|
||||
* {@internal This sniff should eventually be pulled upstream as part of a solution
|
||||
* for https://github.com/squizlabs/PHP_CodeSniffer/issues/582 }}
|
||||
*/
|
||||
class ArrayIndentationSniff extends Sniff {
|
||||
|
||||
/**
|
||||
* Should tabs be used for indenting?
|
||||
*
|
||||
* If TRUE, fixes will be made using tabs instead of spaces.
|
||||
* The size of each tab is important, so it should be specified
|
||||
* using the --tab-width CLI argument.
|
||||
*
|
||||
* {@internal While for WPCS this should always be `true`, this property
|
||||
* was added in anticipation of upstreaming the sniff.
|
||||
* This property is the same as used in `Generic.WhiteSpace.ScopeIndent`.}}
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $tabIndent = true;
|
||||
|
||||
/**
|
||||
* The --tab-width CLI value that is being used.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $tab_width;
|
||||
|
||||
/**
|
||||
* Tokens to ignore for subsequent lines in a multi-line array item.
|
||||
*
|
||||
* Property is set in the register() method.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ignore_tokens = array();
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
/*
|
||||
* Set the $ignore_tokens property.
|
||||
*
|
||||
* Existing heredoc, nowdoc and inline HTML indentation should be respected at all times.
|
||||
*/
|
||||
$this->ignore_tokens = Tokens::$heredocTokens;
|
||||
unset( $this->ignore_tokens[ \T_START_HEREDOC ], $this->ignore_tokens[ \T_START_NOWDOC ] );
|
||||
$this->ignore_tokens[ \T_INLINE_HTML ] = \T_INLINE_HTML;
|
||||
|
||||
return array(
|
||||
\T_ARRAY,
|
||||
\T_OPEN_SHORT_ARRAY,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes this test, when one of its tokens is encountered.
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_token( $stackPtr ) {
|
||||
if ( ! isset( $this->tab_width ) ) {
|
||||
$this->tab_width = PHPCSHelper::get_tab_width( $this->phpcsFile );
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the array opener & closer.
|
||||
*/
|
||||
$array_open_close = $this->find_array_open_close( $stackPtr );
|
||||
if ( false === $array_open_close ) {
|
||||
// Array open/close could not be determined.
|
||||
return;
|
||||
}
|
||||
|
||||
$opener = $array_open_close['opener'];
|
||||
$closer = $array_open_close['closer'];
|
||||
|
||||
if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) {
|
||||
// Not interested in single line arrays.
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the closing bracket is lined up with the start of the content on the line
|
||||
* containing the array opener.
|
||||
*/
|
||||
$opener_line_spaces = $this->get_indentation_size( $opener );
|
||||
$closer_line_spaces = ( $this->tokens[ $closer ]['column'] - 1 );
|
||||
|
||||
if ( $closer_line_spaces !== $opener_line_spaces ) {
|
||||
$error = 'Array closer not aligned correctly; expected %s space(s) but found %s';
|
||||
$error_code = 'CloseBraceNotAligned';
|
||||
|
||||
/*
|
||||
* Report & fix the issue if the close brace is on its own line with
|
||||
* nothing or only indentation whitespace before it.
|
||||
*/
|
||||
if ( 0 === $closer_line_spaces
|
||||
|| ( \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code']
|
||||
&& 1 === $this->tokens[ ( $closer - 1 ) ]['column'] )
|
||||
) {
|
||||
$this->add_array_alignment_error(
|
||||
$closer,
|
||||
$error,
|
||||
$error_code,
|
||||
$opener_line_spaces,
|
||||
$closer_line_spaces,
|
||||
$this->get_indentation_string( $opener_line_spaces )
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* Otherwise, only report the error, don't try and fix it (yet).
|
||||
*
|
||||
* It will get corrected in a future loop of the fixer once the closer
|
||||
* has been moved to its own line by the `ArrayDeclarationSpacing` sniff.
|
||||
*/
|
||||
$this->phpcsFile->addError(
|
||||
$error,
|
||||
$closer,
|
||||
$error_code,
|
||||
array( $opener_line_spaces, $closer_line_spaces )
|
||||
);
|
||||
}
|
||||
|
||||
unset( $error, $error_code );
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify & correct the array item indentation.
|
||||
*/
|
||||
$array_items = $this->get_function_call_parameters( $stackPtr );
|
||||
if ( empty( $array_items ) ) {
|
||||
// Strange, no array items found.
|
||||
return;
|
||||
}
|
||||
|
||||
$expected_spaces = ( $opener_line_spaces + $this->tab_width );
|
||||
$expected_indent = $this->get_indentation_string( $expected_spaces );
|
||||
$end_of_previous_item = $opener;
|
||||
|
||||
foreach ( $array_items as $item ) {
|
||||
$end_of_this_item = ( $item['end'] + 1 );
|
||||
|
||||
// Find the line on which the item starts.
|
||||
$first_content = $this->phpcsFile->findNext(
|
||||
array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
|
||||
$item['start'],
|
||||
$end_of_this_item,
|
||||
true
|
||||
);
|
||||
|
||||
// Deal with trailing comments.
|
||||
if ( false !== $first_content
|
||||
&& \T_COMMENT === $this->tokens[ $first_content ]['code']
|
||||
&& $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line']
|
||||
) {
|
||||
$first_content = $this->phpcsFile->findNext(
|
||||
array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE, \T_COMMENT ),
|
||||
( $first_content + 1 ),
|
||||
$end_of_this_item,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ( false === $first_content ) {
|
||||
$end_of_previous_item = $end_of_this_item;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bow out from reporting and fixing mixed multi-line/single-line arrays.
|
||||
// That is handled by the ArrayDeclarationSpacingSniff.
|
||||
if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $end_of_previous_item ]['line']
|
||||
|| ( 1 !== $this->tokens[ $first_content ]['column']
|
||||
&& \T_WHITESPACE !== $this->tokens[ ( $first_content - 1 ) ]['code'] )
|
||||
) {
|
||||
return $closer;
|
||||
}
|
||||
|
||||
$found_spaces = ( $this->tokens[ $first_content ]['column'] - 1 );
|
||||
|
||||
if ( $found_spaces !== $expected_spaces ) {
|
||||
$this->add_array_alignment_error(
|
||||
$first_content,
|
||||
'Array item not aligned correctly; expected %s spaces but found %s',
|
||||
'ItemNotAligned',
|
||||
$expected_spaces,
|
||||
$found_spaces,
|
||||
$expected_indent
|
||||
);
|
||||
}
|
||||
|
||||
// No need for further checking if this is a one-line array item.
|
||||
if ( $this->tokens[ $first_content ]['line'] === $this->tokens[ $item['end'] ]['line'] ) {
|
||||
$end_of_previous_item = $end_of_this_item;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Multi-line array items.
|
||||
*
|
||||
* Verify & if needed, correct the indentation of subsequent lines.
|
||||
* Subsequent lines may be indented more or less than the mimimum expected indent,
|
||||
* but the "first line after" should be indented - at least - as much as the very first line
|
||||
* of the array item.
|
||||
* Indentation correction for subsequent lines will be based on that diff.
|
||||
*/
|
||||
|
||||
// Find first token on second line of the array item.
|
||||
// If the second line is a heredoc/nowdoc, continue on until we find a line with a different token.
|
||||
// Same for the second line of a multi-line text string.
|
||||
for ( $ptr = ( $first_content + 1 ); $ptr <= $item['end']; $ptr++ ) {
|
||||
if ( $this->tokens[ $first_content ]['line'] !== $this->tokens[ $ptr ]['line']
|
||||
&& 1 === $this->tokens[ $ptr ]['column']
|
||||
&& false === $this->ignore_token( $ptr )
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$first_content_on_line2 = $this->phpcsFile->findNext(
|
||||
array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
|
||||
$ptr,
|
||||
$end_of_this_item,
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $first_content_on_line2 ) {
|
||||
/*
|
||||
* Apparently there were only tokens in the ignore list on subsequent lines.
|
||||
*
|
||||
* In that case, the comma after the array item might be on a line by itself,
|
||||
* so check its placement.
|
||||
*/
|
||||
if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line']
|
||||
&& \T_COMMA === $this->tokens[ $end_of_this_item ]['code']
|
||||
&& ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces
|
||||
) {
|
||||
$this->add_array_alignment_error(
|
||||
$end_of_this_item,
|
||||
'Comma after multi-line array item not aligned correctly; expected %s spaces, but found %s',
|
||||
'MultiLineArrayItemCommaNotAligned',
|
||||
$expected_spaces,
|
||||
( $this->tokens[ $end_of_this_item ]['column'] - 1 ),
|
||||
$expected_indent
|
||||
);
|
||||
}
|
||||
|
||||
$end_of_previous_item = $end_of_this_item;
|
||||
continue;
|
||||
}
|
||||
|
||||
$found_spaces_on_line2 = $this->get_indentation_size( $first_content_on_line2 );
|
||||
$expected_spaces_on_line2 = $expected_spaces;
|
||||
|
||||
if ( $found_spaces < $found_spaces_on_line2 ) {
|
||||
$expected_spaces_on_line2 += ( $found_spaces_on_line2 - $found_spaces );
|
||||
}
|
||||
|
||||
if ( $found_spaces_on_line2 !== $expected_spaces_on_line2 ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Multi-line array item not aligned correctly; expected %s spaces, but found %s',
|
||||
$first_content_on_line2,
|
||||
'MultiLineArrayItemNotAligned',
|
||||
array(
|
||||
$expected_spaces_on_line2,
|
||||
$found_spaces_on_line2,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$expected_indent_on_line2 = $this->get_indentation_string( $expected_spaces_on_line2 );
|
||||
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
// Fix second line for the array item.
|
||||
if ( 1 === $this->tokens[ $first_content_on_line2 ]['column']
|
||||
&& \T_COMMENT === $this->tokens[ $first_content_on_line2 ]['code']
|
||||
) {
|
||||
$actual_comment = ltrim( $this->tokens[ $first_content_on_line2 ]['content'] );
|
||||
$replacement = $expected_indent_on_line2 . $actual_comment;
|
||||
|
||||
$this->phpcsFile->fixer->replaceToken( $first_content_on_line2, $replacement );
|
||||
|
||||
} else {
|
||||
$this->fix_alignment_error( $first_content_on_line2, $expected_indent_on_line2 );
|
||||
}
|
||||
|
||||
// Fix subsequent lines.
|
||||
for ( $i = ( $first_content_on_line2 + 1 ); $i <= $item['end']; $i++ ) {
|
||||
// We're only interested in the first token on each line.
|
||||
if ( 1 !== $this->tokens[ $i ]['column'] ) {
|
||||
if ( $this->tokens[ $i ]['line'] === $this->tokens[ $item['end'] ]['line'] ) {
|
||||
// We might as well quit if we're past the first token on the last line.
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$first_content_on_line = $this->phpcsFile->findNext(
|
||||
array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
|
||||
$i,
|
||||
$end_of_this_item,
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $first_content_on_line ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore lines with heredoc and nowdoc tokens and subsequent lines in multi-line strings.
|
||||
if ( true === $this->ignore_token( $first_content_on_line ) ) {
|
||||
$i = $first_content_on_line;
|
||||
continue;
|
||||
}
|
||||
|
||||
$found_spaces_on_line = $this->get_indentation_size( $first_content_on_line );
|
||||
$expected_spaces_on_line = ( $expected_spaces_on_line2 + ( $found_spaces_on_line - $found_spaces_on_line2 ) );
|
||||
$expected_spaces_on_line = max( $expected_spaces_on_line, 0 ); // Can't be below 0.
|
||||
$expected_indent_on_line = $this->get_indentation_string( $expected_spaces_on_line );
|
||||
|
||||
if ( $found_spaces_on_line !== $expected_spaces_on_line ) {
|
||||
if ( 1 === $this->tokens[ $first_content_on_line ]['column']
|
||||
&& \T_COMMENT === $this->tokens[ $first_content_on_line ]['code']
|
||||
) {
|
||||
$actual_comment = ltrim( $this->tokens[ $first_content_on_line ]['content'] );
|
||||
$replacement = $expected_indent_on_line . $actual_comment;
|
||||
|
||||
$this->phpcsFile->fixer->replaceToken( $first_content_on_line, $replacement );
|
||||
} else {
|
||||
$this->fix_alignment_error( $first_content_on_line, $expected_indent_on_line );
|
||||
}
|
||||
}
|
||||
|
||||
// Move past any potential empty lines between the previous non-empty line and this one.
|
||||
// No need to do the fixes twice.
|
||||
$i = $first_content_on_line;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the placement of the comma after the array item as it might be on a line by itself.
|
||||
*/
|
||||
if ( $this->tokens[ $item['end'] ]['line'] !== $this->tokens[ $end_of_this_item ]['line']
|
||||
&& \T_COMMA === $this->tokens[ $end_of_this_item ]['code']
|
||||
&& ( $this->tokens[ $end_of_this_item ]['column'] - 1 ) !== $expected_spaces
|
||||
) {
|
||||
$this->add_array_alignment_error(
|
||||
$end_of_this_item,
|
||||
'Comma after array item not aligned correctly; expected %s spaces, but found %s',
|
||||
'MultiLineArrayItemCommaNotAligned',
|
||||
$expected_spaces,
|
||||
( $this->tokens[ $end_of_this_item ]['column'] - 1 ),
|
||||
$expected_indent
|
||||
);
|
||||
}
|
||||
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
}
|
||||
|
||||
$end_of_previous_item = $end_of_this_item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the token be ignored ?
|
||||
*
|
||||
* This method is only intended to be used with the first token on a line
|
||||
* for subsequent lines in an multi-line array item.
|
||||
*
|
||||
* @param int $ptr Stack pointer to the first token on a line.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function ignore_token( $ptr ) {
|
||||
$token_code = $this->tokens[ $ptr ]['code'];
|
||||
|
||||
if ( isset( $this->ignore_tokens[ $token_code ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If it's a subsequent line of a multi-line sting, it will not start with a quote
|
||||
* character, nor just *be* a quote character.
|
||||
*/
|
||||
if ( \T_CONSTANT_ENCAPSED_STRING === $token_code
|
||||
|| \T_DOUBLE_QUOTED_STRING === $token_code
|
||||
) {
|
||||
// Deal with closing quote of a multi-line string being on its own line.
|
||||
if ( "'" === $this->tokens[ $ptr ]['content']
|
||||
|| '"' === $this->tokens[ $ptr ]['content']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deal with subsequent lines of a multi-line string where the token is broken up per line.
|
||||
if ( "'" !== $this->tokens[ $ptr ]['content'][0]
|
||||
&& '"' !== $this->tokens[ $ptr ]['content'][0]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the line indentation whitespace.
|
||||
*
|
||||
* @param int $ptr Stack pointer to an arbitrary token on a line.
|
||||
*
|
||||
* @return int Nr of spaces found. Where necessary, tabs are translated to spaces.
|
||||
*/
|
||||
protected function get_indentation_size( $ptr ) {
|
||||
|
||||
// Find the first token on the line.
|
||||
for ( ; $ptr >= 0; $ptr-- ) {
|
||||
if ( 1 === $this->tokens[ $ptr ]['column'] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$whitespace = '';
|
||||
|
||||
if ( \T_WHITESPACE === $this->tokens[ $ptr ]['code']
|
||||
|| \T_DOC_COMMENT_WHITESPACE === $this->tokens[ $ptr ]['code']
|
||||
) {
|
||||
return $this->tokens[ $ptr ]['length'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case for multi-line, non-docblock comments.
|
||||
* Only applicable for subsequent lines in an array item.
|
||||
*
|
||||
* First/Single line is tokenized as T_WHITESPACE + T_COMMENT
|
||||
* Subsequent lines are tokenized as T_COMMENT including the indentation whitespace.
|
||||
*/
|
||||
if ( \T_COMMENT === $this->tokens[ $ptr ]['code'] ) {
|
||||
$content = $this->tokens[ $ptr ]['content'];
|
||||
$actual_comment = ltrim( $content );
|
||||
$whitespace = str_replace( $actual_comment, '', $content );
|
||||
}
|
||||
|
||||
return \strlen( $whitespace );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an indentation string.
|
||||
*
|
||||
* @param int $nr Number of spaces the indentation should be.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_indentation_string( $nr ) {
|
||||
if ( 0 >= $nr ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Space-based indentation.
|
||||
if ( false === $this->tabIndent ) {
|
||||
return str_repeat( ' ', $nr );
|
||||
}
|
||||
|
||||
// Tab-based indentation.
|
||||
$num_tabs = (int) floor( $nr / $this->tab_width );
|
||||
$remaining = ( $nr % $this->tab_width );
|
||||
$tab_indent = str_repeat( "\t", $num_tabs );
|
||||
$tab_indent .= str_repeat( ' ', $remaining );
|
||||
|
||||
return $tab_indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error and fix incorrect array alignment.
|
||||
*
|
||||
* @param int $ptr Stack pointer to the first content on the line.
|
||||
* @param string $error Error message.
|
||||
* @param string $error_code Error code.
|
||||
* @param int $expected Expected nr of spaces (tabs translated to space value).
|
||||
* @param int $found Found nr of spaces (tabs translated to space value).
|
||||
* @param string $new_indent Whitespace indent replacement content.
|
||||
*/
|
||||
protected function add_array_alignment_error( $ptr, $error, $error_code, $expected, $found, $new_indent ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError( $error, $ptr, $error_code, array( $expected, $found ) );
|
||||
if ( true === $fix ) {
|
||||
$this->fix_alignment_error( $ptr, $new_indent );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix incorrect array alignment.
|
||||
*
|
||||
* @param int $ptr Stack pointer to the first content on the line.
|
||||
* @param string $new_indent Whitespace indent replacement content.
|
||||
*/
|
||||
protected function fix_alignment_error( $ptr, $new_indent ) {
|
||||
if ( 1 === $this->tokens[ $ptr ]['column'] ) {
|
||||
$this->phpcsFile->fixer->addContentBefore( $ptr, $new_indent );
|
||||
} else {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $ptr - 1 ), $new_indent );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user