mirror of
https://github.com/10h30/genesis-simple-sidebars.git
synced 2026-07-20 15:03:34 +09:00
Introducing coding standards validation.
This commit is contained in:
committed by
Nathan Rice
parent
6e40c921ab
commit
fd504d41ac
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
<?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\AbstractArrayAssignmentRestrictionsSniff;
|
||||
|
||||
/**
|
||||
* Restricts array assignment of certain keys.
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.3.0
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*
|
||||
* @deprecated 0.10.0 The functionality which used to be contained in this class has been moved to
|
||||
* the WordPress_AbstractArrayAssignmentRestrictionsSniff class.
|
||||
* This class is left here to prevent backward-compatibility breaks for
|
||||
* custom sniffs extending the old class and references to this
|
||||
* sniff from custom phpcs.xml files.
|
||||
* This file is also still used to unit test the abstract class.
|
||||
* @see \WordPress\AbstractArrayAssignmentRestrictionsSniff
|
||||
*/
|
||||
class ArrayAssignmentRestrictionsSniff extends AbstractArrayAssignmentRestrictionsSniff {
|
||||
|
||||
/**
|
||||
* Groups of variables to restrict.
|
||||
*
|
||||
* Example: groups => array(
|
||||
* 'groupname' => array(
|
||||
* 'type' => 'error' | 'warning',
|
||||
* 'message' => 'Dont use this one please!',
|
||||
* 'keys' => array( 'key1', 'another_key' ),
|
||||
* 'callback' => array( 'class', 'method' ), // Optional.
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process each confirmed key, to check value.
|
||||
*
|
||||
* @param string $key Array index / key.
|
||||
* @param mixed $val Assigned value.
|
||||
* @param int $line Token line.
|
||||
* @param array $group Group definition.
|
||||
* @return mixed FALSE if no match, TRUE if matches, STRING if matches
|
||||
* with custom error message passed to ->process().
|
||||
*/
|
||||
public function callback( $key, $val, $line, $group ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?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 PHP_CodeSniffer_File as File;
|
||||
|
||||
/**
|
||||
* Enforces WordPress array format, based upon Squiz code.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.5.0 Now extends `Squiz_Sniffs_Arrays_ArrayDeclarationSniff`.
|
||||
* @since 0.11.0 The additional single-line array checks have been moved to their own
|
||||
* sniff WordPress.Arrays.ArrayDeclarationSpacing.
|
||||
* This class now only contains a slimmed down version of the upstream sniff.
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*
|
||||
* @deprecated 0.13.0 This sniff has now been deprecated. Most checks which were previously
|
||||
* contained herein had recently been excluded in favour of dedicated
|
||||
* sniffs with higher precision. The last remaining checks which were not
|
||||
* already covered elsewhere have been moved to the `ArrayDeclarationSpacing`
|
||||
* sniff.
|
||||
* This class is left here to prevent breaking custom rulesets which refer
|
||||
* to this sniff.
|
||||
*/
|
||||
class ArrayDeclarationSniff {
|
||||
|
||||
/**
|
||||
* Don't use.
|
||||
*
|
||||
* @deprecated 0.13.0
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function register() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use.
|
||||
*
|
||||
* @deprecated 0.13.0
|
||||
*
|
||||
* @param \PHP_CodeSniffer\Files\File $phpcsFile A PHP_CodeSniffer file.
|
||||
* @param int $stackPtr The position of the token.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process( File $phpcsFile, $stackPtr ) {}
|
||||
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
<?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 PHP_CodeSniffer_Tokens as Tokens;
|
||||
|
||||
/**
|
||||
* Enforces WordPress array spacing format.
|
||||
*
|
||||
* - Check for no space between array keyword and array opener.
|
||||
* - Check for no space between the parentheses of an empty array.
|
||||
* - Checks for one space after the array opener / before the array closer in single-line arrays.
|
||||
* - Checks that associative arrays are multi-line.
|
||||
* - Checks that each array item in a multi-line array starts on a new line.
|
||||
* - Checks that the array closer in a multi-line array is on a new line.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.11.0 - The WordPress specific additional checks have now been split off
|
||||
* from the WordPress_Sniffs_Arrays_ArrayDeclaration sniff into
|
||||
* this sniff.
|
||||
* - Added sniffing & fixing for associative arrays.
|
||||
* @since 0.12.0 Decoupled this sniff from the upstream sniff completely.
|
||||
* This sniff now extends the `WordPress_Sniff` instead.
|
||||
* @since 0.13.0 Added the last remaining checks from the `ArrayDeclaration` sniff
|
||||
* which were not covered elsewhere. The `ArrayDeclaration` sniff has
|
||||
* now been deprecated.
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
* @since 0.14.0 Single item associative arrays are now by default exempt from the
|
||||
* "must be multi-line" rule. This behaviour can be changed using the
|
||||
* `allow_single_item_single_line_associative_arrays` property.
|
||||
*/
|
||||
class ArrayDeclarationSpacingSniff extends Sniff {
|
||||
|
||||
/**
|
||||
* Whether or not to allow single item associative arrays to be single line.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var bool Defaults to true.
|
||||
*/
|
||||
public $allow_single_item_single_line_associative_arrays = true;
|
||||
|
||||
/**
|
||||
* Token this sniff targets.
|
||||
*
|
||||
* Also used for distinguishing between the array and an array value
|
||||
* which is also an array.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $targets = array(
|
||||
\T_ARRAY => \T_ARRAY,
|
||||
\T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @since 0.12.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
return $this->targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes this test, when one of its tokens is encountered.
|
||||
*
|
||||
* @since 0.12.0 The actual checks contained in this method used to
|
||||
* be in the `processSingleLineArray()` method.
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_token( $stackPtr ) {
|
||||
/*
|
||||
* 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'];
|
||||
unset( $array_open_close );
|
||||
|
||||
/*
|
||||
* Long arrays only: Check for space between the array keyword and the open parenthesis.
|
||||
*/
|
||||
if ( \T_ARRAY === $this->tokens[ $stackPtr ]['code'] ) {
|
||||
|
||||
if ( ( $stackPtr + 1 ) !== $opener ) {
|
||||
$error = 'There must be no space between the "array" keyword and the opening parenthesis';
|
||||
$error_code = 'SpaceAfterKeyword';
|
||||
|
||||
$nextNonWhitespace = $this->phpcsFile->findNext( \T_WHITESPACE, ( $stackPtr + 1 ), ( $opener + 1 ), true );
|
||||
if ( $nextNonWhitespace !== $opener ) {
|
||||
// Don't auto-fix: Something other than whitespace found between keyword and open parenthesis.
|
||||
$this->phpcsFile->addError( $error, $stackPtr, $error_code );
|
||||
} else {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError( $error, $stackPtr, $error_code );
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
for ( $i = ( $stackPtr + 1 ); $i < $opener; $i++ ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $i, '' );
|
||||
}
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
unset( $i );
|
||||
}
|
||||
}
|
||||
unset( $error, $error_code, $nextNonWhitespace, $fix );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for empty arrays.
|
||||
*/
|
||||
$nextNonWhitespace = $this->phpcsFile->findNext( \T_WHITESPACE, ( $opener + 1 ), ( $closer + 1 ), true );
|
||||
if ( $nextNonWhitespace === $closer ) {
|
||||
|
||||
if ( ( $opener + 1 ) !== $closer ) {
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Empty array declaration must have no space between the parentheses',
|
||||
$stackPtr,
|
||||
'SpaceInEmptyArray'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
for ( $i = ( $opener + 1 ); $i < $closer; $i++ ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $i, '' );
|
||||
}
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
unset( $i );
|
||||
}
|
||||
}
|
||||
|
||||
// This array is empty, so the below checks aren't necessary.
|
||||
return;
|
||||
}
|
||||
unset( $nextNonWhitespace );
|
||||
|
||||
// Pass off to either the single line or multi-line array analysis.
|
||||
if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) {
|
||||
$this->process_single_line_array( $stackPtr, $opener, $closer );
|
||||
} else {
|
||||
$this->process_multi_line_array( $stackPtr, $opener, $closer );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single-line array.
|
||||
*
|
||||
* @since 0.13.0 The actual checks contained in this method used to
|
||||
* be in the `process()` method.
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
* @param int $opener The position of the array opener.
|
||||
* @param int $closer The position of the array closer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function process_single_line_array( $stackPtr, $opener, $closer ) {
|
||||
/*
|
||||
* Check that associative arrays are always multi-line.
|
||||
*/
|
||||
$array_has_keys = $this->phpcsFile->findNext( \T_DOUBLE_ARROW, $opener, $closer );
|
||||
if ( false !== $array_has_keys ) {
|
||||
|
||||
$array_items = $this->get_function_call_parameters( $stackPtr );
|
||||
|
||||
if ( ( false === $this->allow_single_item_single_line_associative_arrays
|
||||
&& ! empty( $array_items ) )
|
||||
|| ( true === $this->allow_single_item_single_line_associative_arrays
|
||||
&& \count( $array_items ) > 1 )
|
||||
) {
|
||||
/*
|
||||
* Make sure the double arrow is for *this* array, not for a nested one.
|
||||
*/
|
||||
$array_has_keys = false; // Reset before doing more detailed check.
|
||||
foreach ( $array_items as $item ) {
|
||||
for ( $ptr = $item['start']; $ptr <= $item['end']; $ptr++ ) {
|
||||
if ( \T_DOUBLE_ARROW === $this->tokens[ $ptr ]['code'] ) {
|
||||
$array_has_keys = true;
|
||||
break 2;
|
||||
}
|
||||
|
||||
// Skip passed any nested arrays.
|
||||
if ( isset( $this->targets[ $this->tokens[ $ptr ]['code'] ] ) ) {
|
||||
$nested_array_open_close = $this->find_array_open_close( $ptr );
|
||||
if ( false === $nested_array_open_close ) {
|
||||
// Nested array open/close could not be determined.
|
||||
continue;
|
||||
}
|
||||
|
||||
$ptr = $nested_array_open_close['closer'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( true === $array_has_keys ) {
|
||||
|
||||
$phrase = 'an';
|
||||
if ( true === $this->allow_single_item_single_line_associative_arrays ) {
|
||||
$phrase = 'a multi-item';
|
||||
}
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'When %s array uses associative keys, each value should start on a new line.',
|
||||
$closer,
|
||||
'AssociativeArrayFound',
|
||||
array( $phrase )
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
foreach ( $array_items as $item ) {
|
||||
/*
|
||||
* Add a line break before the first non-empty token in the array item.
|
||||
* Prevents extraneous whitespace at the start of the line which could be
|
||||
* interpreted as alignment whitespace.
|
||||
*/
|
||||
$first_non_empty = $this->phpcsFile->findNext(
|
||||
Tokens::$emptyTokens,
|
||||
$item['start'],
|
||||
( $item['end'] + 1 ),
|
||||
true
|
||||
);
|
||||
if ( false === $first_non_empty ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $item['start'] <= ( $first_non_empty - 1 )
|
||||
&& \T_WHITESPACE === $this->tokens[ ( $first_non_empty - 1 ) ]['code']
|
||||
) {
|
||||
// Remove whitespace which would otherwise becoming trailing
|
||||
// (as it gives problems with the fixed file).
|
||||
$this->phpcsFile->fixer->replaceToken( ( $first_non_empty - 1 ), '' );
|
||||
}
|
||||
|
||||
$this->phpcsFile->fixer->addNewlineBefore( $first_non_empty );
|
||||
}
|
||||
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
|
||||
// No need to check for spacing around opener/closer as this array should be multi-line.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that there is a single space after the array opener and before the array closer.
|
||||
*/
|
||||
if ( \T_WHITESPACE !== $this->tokens[ ( $opener + 1 ) ]['code'] ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Missing space after array opener.',
|
||||
$opener,
|
||||
'NoSpaceAfterArrayOpener'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->addContent( $opener, ' ' );
|
||||
}
|
||||
} elseif ( ' ' !== $this->tokens[ ( $opener + 1 ) ]['content'] ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Expected 1 space after array opener, found %s.',
|
||||
$opener,
|
||||
'SpaceAfterArrayOpener',
|
||||
array( \strlen( $this->tokens[ ( $opener + 1 ) ]['content'] ) )
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $opener + 1 ), ' ' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \T_WHITESPACE !== $this->tokens[ ( $closer - 1 ) ]['code'] ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Missing space before array closer.',
|
||||
$closer,
|
||||
'NoSpaceBeforeArrayCloser'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->addContentBefore( $closer, ' ' );
|
||||
}
|
||||
} elseif ( ' ' !== $this->tokens[ ( $closer - 1 ) ]['content'] ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Expected 1 space before array closer, found %s.',
|
||||
$closer,
|
||||
'SpaceBeforeArrayCloser',
|
||||
array( \strlen( $this->tokens[ ( $closer - 1 ) ]['content'] ) )
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $closer - 1 ), ' ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a multi-line array.
|
||||
*
|
||||
* @since 0.13.0 The actual checks contained in this method used to
|
||||
* be in the `ArrayDeclaration` sniff.
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
* @param int $opener The position of the array opener.
|
||||
* @param int $closer The position of the array closer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function process_multi_line_array( $stackPtr, $opener, $closer ) {
|
||||
/*
|
||||
* Check that the closing bracket is on a new line.
|
||||
*/
|
||||
$last_content = $this->phpcsFile->findPrevious( \T_WHITESPACE, ( $closer - 1 ), $opener, true );
|
||||
if ( false !== $last_content
|
||||
&& $this->tokens[ $last_content ]['line'] === $this->tokens[ $closer ]['line']
|
||||
) {
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Closing parenthesis of array declaration must be on a new line',
|
||||
$closer,
|
||||
'CloseBraceNewLine'
|
||||
);
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
if ( $last_content < ( $closer - 1 )
|
||||
&& \T_WHITESPACE === $this->tokens[ ( $closer - 1 ) ]['code']
|
||||
) {
|
||||
// Remove whitespace which would otherwise becoming trailing
|
||||
// (as it gives problems with the fixed file).
|
||||
$this->phpcsFile->fixer->replaceToken( ( $closer - 1 ), '' );
|
||||
}
|
||||
|
||||
$this->phpcsFile->fixer->addNewlineBefore( $closer );
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that each array item starts on a new line.
|
||||
*/
|
||||
$array_items = $this->get_function_call_parameters( $stackPtr );
|
||||
$end_of_last_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
|
||||
);
|
||||
|
||||
// Ignore comments after array items if the next real content starts on a new line.
|
||||
if ( \T_COMMENT === $this->tokens[ $first_content ]['code']
|
||||
|| isset( $this->phpcsCommentTokens[ $this->tokens[ $first_content ]['type'] ] )
|
||||
) {
|
||||
$next = $this->phpcsFile->findNext(
|
||||
array( \T_WHITESPACE, \T_DOC_COMMENT_WHITESPACE ),
|
||||
( $first_content + 1 ),
|
||||
$end_of_this_item,
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $next ) {
|
||||
// Shouldn't happen, but just in case.
|
||||
$end_of_last_item = $end_of_this_item;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->tokens[ $next ]['line'] !== $this->tokens[ $first_content ]['line'] ) {
|
||||
$first_content = $next;
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $first_content ) {
|
||||
// Shouldn't happen, but just in case.
|
||||
$end_of_last_item = $end_of_this_item;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->tokens[ $end_of_last_item ]['line'] === $this->tokens[ $first_content ]['line'] ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Each item in a multi-line array must be on a new line',
|
||||
$first_content,
|
||||
'ArrayItemNoNewLine'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
if ( $item['start'] <= ( $first_content - 1 )
|
||||
&& \T_WHITESPACE === $this->tokens[ ( $first_content - 1 ) ]['code']
|
||||
) {
|
||||
// Remove whitespace which would otherwise becoming trailing
|
||||
// (as it gives problems with the fixed file).
|
||||
$this->phpcsFile->fixer->replaceToken( ( $first_content - 1 ), '' );
|
||||
}
|
||||
|
||||
$this->phpcsFile->fixer->addNewlineBefore( $first_content );
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
}
|
||||
|
||||
$end_of_last_item = $end_of_this_item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Check for proper spacing in array key references.
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#space-usage
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.3.0
|
||||
* @since 0.7.0 This sniff now has the ability to fix a number of the issues it flags.
|
||||
* @since 0.12.0 This class now extends WordPress_Sniff.
|
||||
* @since 0.13.0 Class name changed: this class is now namespaced.
|
||||
*/
|
||||
class ArrayKeySpacingRestrictionsSniff extends Sniff {
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
return array(
|
||||
\T_OPEN_SQUARE_BRACKET,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
|
||||
$token = $this->tokens[ $stackPtr ];
|
||||
if ( ! isset( $token['bracket_closer'] ) ) {
|
||||
$this->phpcsFile->addWarning( 'Missing bracket closer.', $stackPtr, 'MissingBracketCloser' );
|
||||
return;
|
||||
}
|
||||
|
||||
$need_spaces = $this->phpcsFile->findNext(
|
||||
array( \T_CONSTANT_ENCAPSED_STRING, \T_LNUMBER, \T_WHITESPACE, \T_MINUS ),
|
||||
( $stackPtr + 1 ),
|
||||
$token['bracket_closer'],
|
||||
true
|
||||
);
|
||||
|
||||
$spaced1 = ( \T_WHITESPACE === $this->tokens[ ( $stackPtr + 1 ) ]['code'] );
|
||||
$spaced2 = ( \T_WHITESPACE === $this->tokens[ ( $token['bracket_closer'] - 1 ) ]['code'] );
|
||||
|
||||
// It should have spaces unless if it only has strings or numbers as the key.
|
||||
if ( false !== $need_spaces && ! ( $spaced1 && $spaced2 ) ) {
|
||||
$error = 'Array keys must be surrounded by spaces unless they contain a string or an integer.';
|
||||
$fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'NoSpacesAroundArrayKeys' );
|
||||
if ( true === $fix ) {
|
||||
if ( ! $spaced1 ) {
|
||||
$this->phpcsFile->fixer->addContentBefore( ( $stackPtr + 1 ), ' ' );
|
||||
}
|
||||
if ( ! $spaced2 ) {
|
||||
$this->phpcsFile->fixer->addContentBefore( $token['bracket_closer'], ' ' );
|
||||
}
|
||||
}
|
||||
} elseif ( false === $need_spaces && ( $spaced1 || $spaced2 ) ) {
|
||||
$error = 'Array keys must NOT be surrounded by spaces if they only contain a string or an integer.';
|
||||
$fix = $this->phpcsFile->addFixableError( $error, $stackPtr, 'SpacesAroundArrayKeys' );
|
||||
if ( true === $fix ) {
|
||||
if ( $spaced1 ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $stackPtr + 1 ), '' );
|
||||
}
|
||||
if ( $spaced2 ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $token['bracket_closer'] - 1 ), '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
<?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 PHP_CodeSniffer_Tokens as Tokens;
|
||||
|
||||
/**
|
||||
* Enforces a comma after each array item and the spacing around it.
|
||||
*
|
||||
* Rules:
|
||||
* - For multi-line arrays, a comma is needed after each array item.
|
||||
* - Same for single-line arrays, but no comma is allowed after the last array item.
|
||||
* - There should be no space between the comma and the end of the array item.
|
||||
* - There should be exactly one space between the comma and the start of the
|
||||
* next array item for single-line items.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
class CommaAfterArrayItemSniff extends Sniff {
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
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 ) {
|
||||
/*
|
||||
* 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'];
|
||||
unset( $array_open_close );
|
||||
|
||||
// This array is empty, so the below checks aren't necessary.
|
||||
if ( ( $opener + 1 ) === $closer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$single_line = true;
|
||||
if ( $this->tokens[ $opener ]['line'] !== $this->tokens[ $closer ]['line'] ) {
|
||||
$single_line = false;
|
||||
}
|
||||
|
||||
$array_items = $this->get_function_call_parameters( $stackPtr );
|
||||
if ( empty( $array_items ) ) {
|
||||
// Strange, no array items found.
|
||||
return;
|
||||
}
|
||||
|
||||
$array_item_count = \count( $array_items );
|
||||
|
||||
// Note: $item_index is 1-based and the array items are split on the commas!
|
||||
foreach ( $array_items as $item_index => $item ) {
|
||||
$maybe_comma = ( $item['end'] + 1 );
|
||||
$is_comma = false;
|
||||
if ( isset( $this->tokens[ $maybe_comma ] ) && \T_COMMA === $this->tokens[ $maybe_comma ]['code'] ) {
|
||||
$is_comma = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if this is a comma at the end of the last item in a single line array.
|
||||
*/
|
||||
if ( true === $single_line && $item_index === $array_item_count ) {
|
||||
|
||||
if ( true === $is_comma ) {
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Comma not allowed after last value in single-line array declaration',
|
||||
$maybe_comma,
|
||||
'CommaAfterLast'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $maybe_comma, '' );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to do the spacing checks for the last item in a single line array.
|
||||
* This is handled by another sniff checking the spacing before the array closer.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
$last_content = $this->phpcsFile->findPrevious(
|
||||
Tokens::$emptyTokens,
|
||||
$item['end'],
|
||||
$item['start'],
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $last_content ) {
|
||||
// Shouldn't be able to happen, but just in case, ignore this array item.
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure every item in a multi-line array has a comma at the end.
|
||||
*
|
||||
* Should in reality only be triggered by the last item in a multi-line array
|
||||
* as otherwise we'd have a parse error already.
|
||||
*/
|
||||
if ( false === $is_comma && false === $single_line ) {
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Each array item in a multi-line array declaration must end in a comma',
|
||||
$last_content,
|
||||
'NoComma'
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->addContent( $last_content, ',' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $is_comma ) {
|
||||
// Can't check spacing around the comma if there is no comma.
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for whitespace at the end of the array item.
|
||||
*/
|
||||
if ( $last_content !== $item['end']
|
||||
// Ignore whitespace at the end of a multi-line item if it is the end of a heredoc/nowdoc.
|
||||
&& ( true === $single_line
|
||||
|| ! isset( Tokens::$heredocTokens[ $this->tokens[ $last_content ]['code'] ] ) )
|
||||
) {
|
||||
$newlines = 0;
|
||||
$spaces = 0;
|
||||
for ( $i = $item['end']; $i > $last_content; $i-- ) {
|
||||
|
||||
if ( \T_WHITESPACE === $this->tokens[ $i ]['code'] ) {
|
||||
if ( $this->tokens[ $i ]['content'] === $this->phpcsFile->eolChar ) {
|
||||
$newlines++;
|
||||
} else {
|
||||
$spaces += $this->tokens[ $i ]['length'];
|
||||
}
|
||||
} elseif ( \T_COMMENT === $this->tokens[ $i ]['code']
|
||||
|| isset( $this->phpcsCommentTokens[ $this->tokens[ $i ]['type'] ] )
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$space_phrases = array();
|
||||
if ( $spaces > 0 ) {
|
||||
$space_phrases[] = $spaces . ' spaces';
|
||||
}
|
||||
if ( $newlines > 0 ) {
|
||||
$space_phrases[] = $newlines . ' newlines';
|
||||
}
|
||||
unset( $newlines, $spaces );
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Expected 0 spaces between "%s" and comma; %s found',
|
||||
$maybe_comma,
|
||||
'SpaceBeforeComma',
|
||||
array(
|
||||
$this->tokens[ $last_content ]['content'],
|
||||
implode( ' and ', $space_phrases ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
for ( $i = $item['end']; $i > $last_content; $i-- ) {
|
||||
|
||||
if ( \T_WHITESPACE === $this->tokens[ $i ]['code'] ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $i, '' );
|
||||
|
||||
} elseif ( \T_COMMENT === $this->tokens[ $i ]['code']
|
||||
|| isset( $this->phpcsCommentTokens[ $this->tokens[ $i ]['type'] ] )
|
||||
) {
|
||||
// We need to move the comma to before the comment.
|
||||
$this->phpcsFile->fixer->addContent( $last_content, ',' );
|
||||
$this->phpcsFile->fixer->replaceToken( $maybe_comma, '' );
|
||||
|
||||
/*
|
||||
* No need to worry about removing too much whitespace in
|
||||
* combination with a `//` comment as in that case, the newline
|
||||
* is part of the comment, so we're good.
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $this->tokens[ ( $maybe_comma + 1 ) ] ) ) {
|
||||
// Shouldn't be able to happen, but just in case.
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whitespace after the comma.
|
||||
*/
|
||||
$next_token = $this->tokens[ ( $maybe_comma + 1 ) ];
|
||||
|
||||
if ( \T_WHITESPACE === $next_token['code'] ) {
|
||||
|
||||
if ( false === $single_line && $this->phpcsFile->eolChar === $next_token['content'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$next_non_whitespace = $this->phpcsFile->findNext(
|
||||
\T_WHITESPACE,
|
||||
( $maybe_comma + 1 ),
|
||||
$closer,
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $next_non_whitespace
|
||||
|| ( false === $single_line
|
||||
&& $this->tokens[ $next_non_whitespace ]['line'] === $this->tokens[ $maybe_comma ]['line']
|
||||
&& ( \T_COMMENT === $this->tokens[ $next_non_whitespace ]['code']
|
||||
|| isset( $this->phpcsCommentTokens[ $this->tokens[ $next_non_whitespace ]['type'] ] ) ) )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$space_length = $next_token['length'];
|
||||
if ( 1 === $space_length ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Expected 1 space between comma and "%s"; %s found',
|
||||
$maybe_comma,
|
||||
'SpaceAfterComma',
|
||||
array(
|
||||
$this->tokens[ $next_non_whitespace ]['content'],
|
||||
$space_length,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $maybe_comma + 1 ), ' ' );
|
||||
}
|
||||
} else {
|
||||
// This is either a comment or a mixed single/multi-line array.
|
||||
// Just add a space and let other sniffs sort out the array layout.
|
||||
$fix = $this->phpcsFile->addFixableError(
|
||||
'Expected 1 space between comma and "%s"; 0 found',
|
||||
$maybe_comma,
|
||||
'NoSpaceAfterComma',
|
||||
array( $next_token['content'] )
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->addContent( $maybe_comma, ' ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+609
@@ -0,0 +1,609 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Enforces alignment of the double arrow assignment operator for multi-item, multi-line arrays.
|
||||
*
|
||||
* - Align the double arrow operator to the same column for each item in a multi-item array.
|
||||
* - Allows for setting a maxColumn property to aid in managing line-length.
|
||||
* - Allows for new line(s) before a double arrow (configurable).
|
||||
* - Allows for handling multi-line array items differently if so desired (configurable).
|
||||
*
|
||||
* @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/#indentation
|
||||
*
|
||||
* @package WPCS\WordPressCodingStandards
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* {@internal This sniff should eventually be pulled upstream as part of a solution
|
||||
* for https://github.com/squizlabs/PHP_CodeSniffer/issues/582 }}
|
||||
*/
|
||||
class MultipleStatementAlignmentSniff extends Sniff {
|
||||
|
||||
/**
|
||||
* Whether or not to ignore an array item for the purpose of alignment
|
||||
* when a new line is found between the array key and the double arrow.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $ignoreNewlines = true;
|
||||
|
||||
/**
|
||||
* Whether the alignment should be exact.
|
||||
*
|
||||
* Exact in this context means "largest index key + 1 space".
|
||||
* When `false`, that is seen as the minimum alignment.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $exact = true;
|
||||
|
||||
/**
|
||||
* The maximum column on which the double arrow alignment should be set.
|
||||
*
|
||||
* This property allows for limiting the whitespace padding to prevent
|
||||
* overly long lines.
|
||||
*
|
||||
* If this value is set to, for instance, 60, it will:
|
||||
* - if the expected column < 60, align at the expected column.
|
||||
* - if the expected column >= 60, align at column 60.
|
||||
* - for the outliers, i.e. the array indexes where the end position
|
||||
* goes past column 60, it will not align the arrow, the sniff will
|
||||
* just make sure there is only one space between the end of the
|
||||
* array index and the double arrow.
|
||||
*
|
||||
* The column value is regarded as a hard value, i.e. includes indentation,
|
||||
* so setting it very low is not a good idea.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxColumn = 1000;
|
||||
|
||||
/**
|
||||
* Whether or not to align the arrow operator for multi-line array items.
|
||||
*
|
||||
* Whether or not an item is regarded as multi-line is based on the **value**
|
||||
* of the item, not the key.
|
||||
*
|
||||
* Valid values are:
|
||||
* - 'always': Default. Align all arrays items regardless of single/multi-line.
|
||||
* - 'never': Never align array items which span multiple lines.
|
||||
* This will enforce one space between the array index and the
|
||||
* double arrow operator for multi-line array items, independently
|
||||
* of the alignment of the rest of the array items.
|
||||
* Multi-line items where the arrow is already aligned with the
|
||||
* "expected" alignment, however, will be left alone.
|
||||
* - operator : Only align the operator for multi-line arrays items if the
|
||||
* + number percentage of multi-line items passes the comparison.
|
||||
* - As it is a percentage, the number has to be between 0 and 100.
|
||||
* - Supported operators: <, <=, >, >=, ==, =, !=, <>
|
||||
* - The percentage is calculated against all array items
|
||||
* (with and without assignment operator).
|
||||
* - The (new) expected alignment will be calculated based only
|
||||
* on the items being aligned.
|
||||
* - Multi-line items where the arrow is already aligned with the
|
||||
* (new) "expected" alignment, however, will be left alone.
|
||||
* Examples:
|
||||
* * Setting this to `!=100` or `<100` means that alignment will
|
||||
* be enforced, unless *all* array items are multi-line.
|
||||
* This is probably the most commonly desired situation.
|
||||
* * Setting this to `=100` means that alignment will only
|
||||
* be enforced, if *all* array items are multi-line.
|
||||
* * Setting this to `<50` means that the majority of array items
|
||||
* need to be single line before alignment is enforced for
|
||||
* multi-line items in the array.
|
||||
* * Setting this to `=0` is useless as in that case there are
|
||||
* no multi-line items in the array anyway.
|
||||
*
|
||||
* This setting will respect the `ignoreNewlines` and `maxColumnn` settings.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var string|int
|
||||
*/
|
||||
public $alignMultilineItems = 'always';
|
||||
|
||||
/**
|
||||
* Storage for parsed $alignMultilineItems operator part.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $operator;
|
||||
|
||||
/**
|
||||
* Storage for parsed $alignMultilineItems numeric part.
|
||||
*
|
||||
* Stored as a string as the comparison will be done string based.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $number;
|
||||
|
||||
/**
|
||||
* Returns an array of tokens this test wants to listen for.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register() {
|
||||
return array(
|
||||
\T_ARRAY,
|
||||
\T_OPEN_SHORT_ARRAY,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes this test, when one of its tokens is encountered.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
*
|
||||
* @return int|void Integer stack pointer to skip forward or void to continue
|
||||
* normal file processing.
|
||||
*/
|
||||
public function process_token( $stackPtr ) {
|
||||
/*
|
||||
* 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'];
|
||||
|
||||
$array_items = $this->get_function_call_parameters( $stackPtr );
|
||||
if ( empty( $array_items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass off to either the single line or multi-line array analysis.
|
||||
if ( $this->tokens[ $opener ]['line'] === $this->tokens[ $closer ]['line'] ) {
|
||||
return $this->process_single_line_array( $stackPtr, $array_items, $opener, $closer );
|
||||
} else {
|
||||
return $this->process_multi_line_array( $stackPtr, $array_items, $opener, $closer );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single-line array.
|
||||
*
|
||||
* While the WP standard does not allow single line multi-item associative arrays,
|
||||
* this sniff should function independently of that.
|
||||
*
|
||||
* The `WordPress.WhiteSpace.OperatorSpacing` sniff already covers checking that
|
||||
* there is a space between the array key and the double arrow, but doesn't
|
||||
* enforce it to be exactly one space for single line arrays.
|
||||
* That is what this method covers.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
* @param array $items Info array containing information on each array item.
|
||||
* @param int $opener The position of the array opener.
|
||||
* @param int $closer The position of the array closer.
|
||||
*
|
||||
* @return int|void Integer stack pointer to skip forward or void to continue
|
||||
* normal file processing.
|
||||
*/
|
||||
protected function process_single_line_array( $stackPtr, $items, $opener, $closer ) {
|
||||
/*
|
||||
* For single line arrays, we don't care about what level the arrow is from.
|
||||
* Just find and fix them all.
|
||||
*/
|
||||
$next_arrow = $this->phpcsFile->findNext(
|
||||
\T_DOUBLE_ARROW,
|
||||
( $opener + 1 ),
|
||||
$closer
|
||||
);
|
||||
|
||||
while ( false !== $next_arrow ) {
|
||||
if ( \T_WHITESPACE === $this->tokens[ ( $next_arrow - 1 ) ]['code'] ) {
|
||||
$space_length = $this->tokens[ ( $next_arrow - 1 ) ]['length'];
|
||||
if ( 1 !== $space_length ) {
|
||||
$error = 'Expected 1 space between "%s" and double arrow; %s found';
|
||||
$data = array(
|
||||
$this->tokens[ ( $next_arrow - 2 ) ]['content'],
|
||||
$space_length,
|
||||
);
|
||||
|
||||
$fix = $this->phpcsFile->addFixableWarning( $error, $next_arrow, 'SpaceBeforeDoubleArrow', $data );
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->replaceToken( ( $next_arrow - 1 ), ' ' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the position of the next double arrow.
|
||||
$next_arrow = $this->phpcsFile->findNext(
|
||||
\T_DOUBLE_ARROW,
|
||||
( $next_arrow + 1 ),
|
||||
$closer
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore any child-arrays as the double arrows in these will already have been handled.
|
||||
return ( $closer + 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a multi-line array.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*
|
||||
* @param int $stackPtr The position of the current token in the stack.
|
||||
* @param array $items Info array containing information on each array item.
|
||||
* @param int $opener The position of the array opener.
|
||||
* @param int $closer The position of the array closer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function process_multi_line_array( $stackPtr, $items, $opener, $closer ) {
|
||||
|
||||
$this->maxColumn = (int) $this->maxColumn;
|
||||
$this->validate_align_multiline_items();
|
||||
|
||||
/*
|
||||
* Determine what the spacing before the arrow should be.
|
||||
*
|
||||
* Will unset any array items without double arrow and with new line whitespace
|
||||
* if newlines are to be ignored, so the second foreach loop only has to deal
|
||||
* with items which need attention.
|
||||
*
|
||||
* This sniff does not take incorrect indentation of array keys into account.
|
||||
* That's for the `WordPress.Arrays.ArrayIndentation` sniff to fix.
|
||||
* If that would affect the alignment, a second (or third) loop of the fixer
|
||||
* will correct it (again) after the indentation has been fixed.
|
||||
*/
|
||||
$index_end_cols = array(); // Keep track of the end column position of index keys.
|
||||
$double_arrow_cols = array(); // Keep track of arrow column position and count.
|
||||
$multi_line_count = 0;
|
||||
$total_items = \count( $items );
|
||||
|
||||
foreach ( $items as $key => $item ) {
|
||||
if ( strpos( $item['raw'], '=>' ) === false ) {
|
||||
// Ignore items without assignment operators.
|
||||
unset( $items[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the position of the first double arrow.
|
||||
$double_arrow = $this->phpcsFile->findNext(
|
||||
\T_DOUBLE_ARROW,
|
||||
$item['start'],
|
||||
( $item['end'] + 1 )
|
||||
);
|
||||
|
||||
if ( false === $double_arrow ) {
|
||||
// Shouldn't happen, just in case.
|
||||
unset( $items[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the arrow is for this item and not for a nested array value assignment.
|
||||
$has_array_opener = $this->phpcsFile->findNext(
|
||||
$this->register(),
|
||||
$item['start'],
|
||||
$double_arrow
|
||||
);
|
||||
|
||||
if ( false !== $has_array_opener ) {
|
||||
// Double arrow is for a nested array.
|
||||
unset( $items[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the end of the array key.
|
||||
$last_index_token = $this->phpcsFile->findPrevious(
|
||||
\T_WHITESPACE,
|
||||
( $double_arrow - 1 ),
|
||||
$item['start'],
|
||||
true
|
||||
);
|
||||
|
||||
if ( false === $last_index_token ) {
|
||||
// Shouldn't happen, but just in case.
|
||||
unset( $items[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( true === $this->ignoreNewlines
|
||||
&& $this->tokens[ $last_index_token ]['line'] !== $this->tokens[ $double_arrow ]['line']
|
||||
) {
|
||||
// Ignore this item as it has a new line between the item key and the double arrow.
|
||||
unset( $items[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$index_end_position = ( $this->tokens[ $last_index_token ]['column'] + ( $this->tokens[ $last_index_token ]['length'] - 1 ) );
|
||||
$items[ $key ]['operatorPtr'] = $double_arrow;
|
||||
$items[ $key ]['last_index_token'] = $last_index_token;
|
||||
$items[ $key ]['last_index_col'] = $index_end_position;
|
||||
|
||||
if ( $this->tokens[ $last_index_token ]['line'] === $this->tokens[ $item['end'] ]['line'] ) {
|
||||
$items[ $key ]['single_line'] = true;
|
||||
} else {
|
||||
$items[ $key ]['single_line'] = false;
|
||||
$multi_line_count++;
|
||||
}
|
||||
|
||||
if ( ( $index_end_position + 2 ) <= $this->maxColumn ) {
|
||||
$index_end_cols[] = $index_end_position;
|
||||
}
|
||||
|
||||
if ( ! isset( $double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] ) ) {
|
||||
$double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ] = 1;
|
||||
} else {
|
||||
$double_arrow_cols[ $this->tokens[ $double_arrow ]['column'] ]++;
|
||||
}
|
||||
}
|
||||
unset( $key, $item, $double_arrow, $has_array_opener, $last_index_token );
|
||||
|
||||
if ( empty( $items ) || empty( $index_end_cols ) ) {
|
||||
// No actionable array items found.
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine whether the operators for multi-line items should be aligned.
|
||||
*/
|
||||
if ( 'always' === $this->alignMultilineItems ) {
|
||||
$alignMultilineItems = true;
|
||||
} elseif ( 'never' === $this->alignMultilineItems ) {
|
||||
$alignMultilineItems = false;
|
||||
} else {
|
||||
$percentage = (string) round( ( $multi_line_count / $total_items ) * 100, 0 );
|
||||
|
||||
// Bit hacky, but this is the only comparison function in PHP which allows to
|
||||
// pass the comparison operator. And hey, it works ;-).
|
||||
$alignMultilineItems = version_compare( $percentage, $this->number, $this->operator );
|
||||
}
|
||||
|
||||
/*
|
||||
* If necessary, rebuild the $index_end_cols and $double_arrow_cols arrays
|
||||
* excluding multi-line items.
|
||||
*/
|
||||
if ( false === $alignMultilineItems ) {
|
||||
$select_index_end_cols = array();
|
||||
$double_arrow_cols = array();
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
if ( false === $item['single_line'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ( $item['last_index_col'] + 2 ) <= $this->maxColumn ) {
|
||||
$select_index_end_cols[] = $item['last_index_col'];
|
||||
}
|
||||
|
||||
if ( ! isset( $double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] ) ) {
|
||||
$double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ] = 1;
|
||||
} else {
|
||||
$double_arrow_cols[ $this->tokens[ $item['operatorPtr'] ]['column'] ]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the expected position of the double arrows.
|
||||
*/
|
||||
if ( ! empty( $select_index_end_cols ) ) {
|
||||
$max_index_width = max( $select_index_end_cols );
|
||||
} else {
|
||||
$max_index_width = max( $index_end_cols );
|
||||
}
|
||||
|
||||
$expected_col = ( $max_index_width + 2 );
|
||||
|
||||
if ( false === $this->exact && ! empty( $double_arrow_cols ) ) {
|
||||
/*
|
||||
* If the alignment does not have to be exact, see if a majority
|
||||
* group of the arrows is already at an acceptable position.
|
||||
*/
|
||||
arsort( $double_arrow_cols, \SORT_NUMERIC );
|
||||
reset( $double_arrow_cols );
|
||||
$count = current( $double_arrow_cols );
|
||||
|
||||
if ( $count > 1 || ( 1 === $count && \count( $items ) === 1 ) ) {
|
||||
// Allow for several groups of arrows having the same $count.
|
||||
$filtered_double_arrow_cols = array_keys( $double_arrow_cols, $count, true );
|
||||
|
||||
foreach ( $filtered_double_arrow_cols as $col ) {
|
||||
if ( $col > $expected_col && $col <= $this->maxColumn ) {
|
||||
$expected_col = $col;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $max_index_width, $count, $filtered_double_arrow_cols, $col );
|
||||
|
||||
/*
|
||||
* Verify and correct the spacing around the double arrows.
|
||||
*/
|
||||
foreach ( $items as $item ) {
|
||||
if ( $this->tokens[ $item['operatorPtr'] ]['column'] === $expected_col
|
||||
&& $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line']
|
||||
) {
|
||||
// Already correctly aligned.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( \T_WHITESPACE !== $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['code'] ) {
|
||||
$before = 0;
|
||||
} else {
|
||||
if ( $this->tokens[ $item['last_index_token'] ]['line'] !== $this->tokens[ $item['operatorPtr'] ]['line'] ) {
|
||||
$before = 'newline';
|
||||
} else {
|
||||
$before = $this->tokens[ ( $item['operatorPtr'] - 1 ) ]['length'];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Deal with index sizes larger than maxColumn and with multi-line
|
||||
* array items which should not be aligned.
|
||||
*/
|
||||
if ( ( $item['last_index_col'] + 2 ) > $this->maxColumn
|
||||
|| ( false === $alignMultilineItems && false === $item['single_line'] )
|
||||
) {
|
||||
|
||||
if ( ( $item['last_index_col'] + 2 ) === $this->tokens[ $item['operatorPtr'] ]['column']
|
||||
&& $this->tokens[ $item['operatorPtr'] ]['line'] === $this->tokens[ $item['last_index_token'] ]['line']
|
||||
) {
|
||||
// MaxColumn/Multi-line item exception, already correctly aligned.
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefix = 'LongIndex';
|
||||
if ( false === $alignMultilineItems && false === $item['single_line'] ) {
|
||||
$prefix = 'MultilineItem';
|
||||
}
|
||||
|
||||
$error_code = $prefix . 'SpaceBeforeDoubleArrow';
|
||||
if ( 0 === $before ) {
|
||||
$error_code = $prefix . 'NoSpaceBeforeDoubleArrow';
|
||||
}
|
||||
|
||||
$fix = $this->phpcsFile->addFixableWarning(
|
||||
'Expected 1 space between "%s" and double arrow; %s found.',
|
||||
$item['operatorPtr'],
|
||||
$error_code,
|
||||
array(
|
||||
$this->tokens[ $item['last_index_token'] ]['content'],
|
||||
$before,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
// Remove whitespace tokens between the end of the index and the arrow, if any.
|
||||
for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $i, '' );
|
||||
}
|
||||
|
||||
// Add the correct whitespace.
|
||||
$this->phpcsFile->fixer->addContent( $item['last_index_token'], ' ' );
|
||||
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deal with the space before double arrows in all other cases.
|
||||
*/
|
||||
$expected_whitespace = $expected_col - ( $this->tokens[ $item['last_index_token'] ]['column'] + $this->tokens[ $item['last_index_token'] ]['length'] );
|
||||
|
||||
$fix = $this->phpcsFile->addFixableWarning(
|
||||
'Array double arrow not aligned correctly; expected %s space(s) between "%s" and double arrow, but found %s.',
|
||||
$item['operatorPtr'],
|
||||
'DoubleArrowNotAligned',
|
||||
array(
|
||||
$expected_whitespace,
|
||||
$this->tokens[ $item['last_index_token'] ]['content'],
|
||||
$before,
|
||||
)
|
||||
);
|
||||
|
||||
if ( true === $fix ) {
|
||||
if ( 0 === $before || 'newline' === $before ) {
|
||||
$this->phpcsFile->fixer->beginChangeset();
|
||||
|
||||
// Remove whitespace tokens between the end of the index and the arrow, if any.
|
||||
for ( $i = ( $item['last_index_token'] + 1 ); $i < $item['operatorPtr']; $i++ ) {
|
||||
$this->phpcsFile->fixer->replaceToken( $i, '' );
|
||||
}
|
||||
|
||||
// Add the correct whitespace.
|
||||
$this->phpcsFile->fixer->addContent(
|
||||
$item['last_index_token'],
|
||||
str_repeat( ' ', $expected_whitespace )
|
||||
);
|
||||
|
||||
$this->phpcsFile->fixer->endChangeset();
|
||||
} elseif ( $expected_whitespace > $before ) {
|
||||
// Add to the existing whitespace to prevent replacing tabs with spaces.
|
||||
// That's the concern of another sniff.
|
||||
$this->phpcsFile->fixer->addContent(
|
||||
( $item['operatorPtr'] - 1 ),
|
||||
str_repeat( ' ', ( $expected_whitespace - $before ) )
|
||||
);
|
||||
} else {
|
||||
// Too much whitespace found.
|
||||
$this->phpcsFile->fixer->replaceToken(
|
||||
( $item['operatorPtr'] - 1 ),
|
||||
str_repeat( ' ', $expected_whitespace )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a valid value has been received for the alignMultilineItems property.
|
||||
*
|
||||
* This message may be thrown more than once if the property is being changed inline in a file.
|
||||
*
|
||||
* @since 0.14.0
|
||||
*/
|
||||
protected function validate_align_multiline_items() {
|
||||
$alignMultilineItems = $this->alignMultilineItems;
|
||||
|
||||
if ( 'always' === $alignMultilineItems || 'never' === $alignMultilineItems ) {
|
||||
return;
|
||||
} else {
|
||||
// Correct for a potentially added % sign.
|
||||
$alignMultilineItems = rtrim( $alignMultilineItems, '%' );
|
||||
|
||||
if ( preg_match( '`^([=<>!]{1,2})(100|[0-9]{1,2})$`', $alignMultilineItems, $matches ) > 0 ) {
|
||||
$operator = $matches[1];
|
||||
$number = (int) $matches[2];
|
||||
|
||||
if ( \in_array( $operator, array( '<', '<=', '>', '>=', '==', '=', '!=', '<>' ), true ) === true
|
||||
&& ( $number >= 0 && $number <= 100 )
|
||||
) {
|
||||
$this->alignMultilineItems = $alignMultilineItems;
|
||||
$this->number = (string) $number;
|
||||
$this->operator = $operator;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->phpcsFile->addError(
|
||||
'Invalid property value passed: "%s". The value for the "alignMultilineItems" property for the "WordPress.Arrays.MultipleStatementAlignment" sniff should be either "always", "never" or an comparison operator + a number between 0 and 100.',
|
||||
0,
|
||||
'InvalidPropertyPassed',
|
||||
array( $this->alignMultilineItems )
|
||||
);
|
||||
|
||||
// Reset to the default if an invalid value was received.
|
||||
$this->alignMultilineItems = 'always';
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user