Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions BigBite/Docs/Classes/StringableStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<documentation title="Stringable">
<standard>
<![CDATA[
Classes that implement the "__toString" magic method should implement the Stringable interface.
]]>
</standard>
<code_comparison>
<code title="Valid: Class implementing __toString implements the Stringable interface.">
<![CDATA[
class Foo<em> implements Stringable</em> {
public function __toString(): string {
return __CLASS__;
}
}
]]>
</code>
<code title="Invalid: Class implementing __toString doesn't implement the Stringable interface.">
<![CDATA[
class Foo<em></em> {
public function __toString(): string {
return __CLASS__;
}
}
]]>
</code>
</code_comparison>
</documentation>
107 changes: 107 additions & 0 deletions BigBite/Sniffs/Classes/StringableSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* BigBite Coding Standards.
*
* @package BigBiteCS\BigBite
* @link https://github.com/bigbite/phpcs-config
* @license https://opensource.org/licenses/MIT MIT
*/

namespace BigBiteCS\BigBite\Sniffs\Classes;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHPCSUtils\Utils\ObjectDeclarations;

/**
* Ensures classes that define a __toString method also implement the Stringable interface
*/
final class StringableSniff implements Sniff {

/**
* A list of tokenizers this sniff supports.
*
* @var array<int,string>
*/
public $supportedTokenizers = array(
'PHP',
);

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int,int>
*/
public function register() {
if ( version_compare( phpversion(), '8.0.0', '<' ) ) {
return array();
}

return array( \T_FUNCTION );
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$fnName = $phpcsFile->findNext( T_STRING, $stackPtr, ( $stackPtr + 5 ), false, null, true );

if ( false === $fnName || '__toString' !== $tokens[ $fnName ]['content'] ) {
return;
}

$classDecl = $phpcsFile->findPrevious( T_CLASS, $stackPtr, 0 );

// this __toString function isn't a class member?
if ( false === $classDecl ) {
return;
}

$interfaces = ObjectDeclarations::findImplementedInterfaceNames( $phpcsFile, $classDecl );

// we're good - class containing __toString implements the interface.
if ( is_array( $interfaces ) && in_array( 'Stringable', $interfaces, true ) ) {
return;
}

$message = 'Classes that declare "__toString" should implement the Stringable interface.';
$doWeFix = $phpcsFile->addFixableError( $message, $classDecl, 'NotImplemented', array(), 0 );

if ( true !== $doWeFix ) {
return;
}

$openingCurly = $tokens[ $classDecl ]['scope_opener'];

if ( $tokens[ $openingCurly ]['line'] === $tokens[ $classDecl ]['line'] ) {
$prevToken = $phpcsFile->findPrevious( T_STRING, $openingCurly, $classDecl );

if ( false === $prevToken ) {
return;
}

$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContent( $prevToken, ' implements Stringable' );
$phpcsFile->fixer->endChangeset();

return;
}

$endOfLine = $phpcsFile->findPrevious( T_WHITESPACE, $openingCurly, $classDecl );

if ( false === $endOfLine ) {
return;
}

$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContentBefore( $endOfLine, ' implements Stringable' );
$phpcsFile->fixer->endChangeset();
}
}
29 changes: 29 additions & 0 deletions BigBite/Tests/Classes/StringableUnitTest.1.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

class DoesNotImplementToStringMagicMethod {
}

class CorrectlyImplementsStringable implements Stringable {
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithWhitespace {
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithNoWhitespace{
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithNewline
{
public function __toString(): string {
return __CLASS__;
}
}
29 changes: 29 additions & 0 deletions BigBite/Tests/Classes/StringableUnitTest.1.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

class DoesNotImplementToStringMagicMethod {
}

class CorrectlyImplementsStringable implements Stringable {
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithWhitespace implements Stringable {
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithNoWhitespace implements Stringable{
public function __toString(): string {
return __CLASS__;
}
}

class IncorrectWithNewline implements Stringable
{
public function __toString(): string {
return __CLASS__;
}
}
61 changes: 61 additions & 0 deletions BigBite/Tests/Classes/StringableUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Unit test class for BigBite Coding Standard.
*
* @package BigBiteCS\BigBite
* @link https://github.com/bigbite/phpcs-config
* @license https://opensource.org/licenses/MIT MIT
*/

namespace BigBiteCS\BigBite\Tests\Classes;

use BigBiteCS\BigBite\Tests\AbstractSniffUnitTest;

/**
* Unit test class for the Stringable sniff.
*
* @package BigBiteCS\BigBite
*/
final class StringableUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of errors that should occur on that line.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int,int>
*/
public function getErrorList( $testFile = '' ) {
if ( version_compare( phpversion(), '8.0.0', '<' ) ) {
return array();
}

switch ( $testFile ) {
case 'StringableUnitTest.1.inc':
return array(
12 => 1,
18 => 1,
24 => 1,
);
default:
return array();
}
}

/**
* Returns the lines where warnings should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of warnings that should occur on that line.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int,int>
*/
public function getWarningList( $testFile = '' ) {
return array();
}
}