Commit cf5db6e3 authored by Jonathan Smith's avatar Jonathan Smith Committed by Klaus Purer
Browse files

feat(DeprecatedSniff): Add sniffs for @trigger_error and @deprecated doc tags (#2908391 #24)

parent 17017dc9
Loading
Loading
Loading
Loading
+133 −0
Original line number Diff line number Diff line
<?php
/**
 * \Drupal\Sniffs\Commenting\DeprecatedSniff.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */

namespace Drupal\Sniffs\Commenting;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;

/**
 * Ensures standard format of a @deprecated text.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */
class DeprecatedSniff implements Sniff
{


    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @return array
     */
    public function register()
    {
        return array(T_DOC_COMMENT_TAG);

    }//end register()


    /**
     * 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();

        // Only process @deprecated tags.
        if (strcasecmp($tokens[$stackPtr]['content'], '@deprecated') !== 0) {
            return;
        }

        // Get the end point of the comment block which has the deprecated tag.
        $commentEnd = $phpcsFile->findNext(T_DOC_COMMENT_CLOSE_TAG, ($stackPtr + 1));

        // Get the full @deprecated text which may cover multiple lines.
        $depText = '';
        $depEnd  = ($stackPtr + 1);
        for ($i = ($stackPtr + 1); $i < $commentEnd; $i++) {
            if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
                if ($tokens[$i]['line'] <= ($tokens[$depEnd]['line'] + 1)) {
                    $depText .= ' '.$tokens[$i]['content'];
                    $depEnd   = $i;
                } else {
                    break;
                }
            }

            // Found another tag, so we have all the deprecation text.
            if ($tokens[$i]['code'] === T_DOC_COMMENT_TAG) {
                break;
            }
        }

        $depText = trim($depText);

        // The standard format for the deprecation text is:
        // @deprecated in %in-version% and will be removed from %removal-version%. %extra-info%.
        // Use (?U) 'ungreedy' before the version so that only the text up to
        // the first '. ' is matched, as there may be more than one sentence in
        // the extra-info part.
        $matches = array();
        preg_match('/in (.+) and will be removed from (?U)(.+)\. (.+)$/', $depText, $matches);
        // There should be 4 items in $matches: 0 is full text, 1 = in-version,
        // 2 = removal-version, 3 = extra-info.
        if (count($matches) !== 4) {
            $error = "The deprecation text '@deprecated %s' does not match the standard format: @deprecated in %%in-version%% and will be removed from %%removal-version%%. %%extra-info%%.";
            $phpcsFile->addError($error, $stackPtr, 'IncorrectTextLayout', array($depText));
        } else {
            // The text follows the basic layout. Now check that the versions
            // match drupal:n.n.n or project:n.x-n.n. The text must be all lower
            // case and numbers can be one or two digits.
            foreach (array('in-version' => $matches[1], 'removal-version' => $matches[2]) as $name => $version) {
                if (preg_match('/^drupal:\d{1,2}\.\d{1,2}\.\d{1,2}$/', $version) === 0
                    && preg_match('/^[a-z\d_]:\d{1,2}\.x\-\d{1,2}\.\d{1,2}$/', $version) === 0
                ) {
                    $error = "The deprecation %s '%s' does not match the standard: drupal:n.n.n or project:n.x-n.n";
                    $phpcsFile->addWarning($error, $stackPtr, 'DeprecatedVersionFormat', array($name, $version));
                }
            }
        }

        // The next tag in this comment block after @deprecated must be @see.
        $seeTag = $phpcsFile->findNext(T_DOC_COMMENT_TAG, ($stackPtr + 1), $commentEnd, false, '@see');
        if ($seeTag === false) {
            $error = 'Each @deprecated tag must have a @see tag immediately following it.';
            $phpcsFile->addError($error, $stackPtr, 'DeprecatedMissingSeeTag');
            return;
        }

        // Check the format of the @see url.
        $string  = $phpcsFile->findNext(T_DOC_COMMENT_STRING, ($seeTag + 1), $commentEnd);
        $cr_link = $tokens[$string]['content'];
        // Allow for the alternative 'node' or 'project/aaa/issues' format.
        preg_match('[^http(s*)://www.drupal.org/(node|project/\w+/issues)/(\d+)(\.*)$]', $cr_link, $matches);
        // If matches[4] is not blank it means that the url is correct but it
        // ends with a period. As this can be a common mistake give a specific
        // message to assist in fixing.
        if (isset($matches[4]) === true && empty($matches[4]) === false) {
            $error = "The @see url '%s' should not end with a period.";
            $phpcsFile->addWarning($error, $seeTag, 'DeprecatedPeriodAfterSeeUrl', array($cr_link));
        } else if (empty($matches) === true) {
            $error = "The @see url '%s' does not match the standard: http(s)://www.drupal.org/node/n or http(s)://www.drupal.org/project/aaa/issues/n";
            $phpcsFile->addWarning($error, $seeTag, 'DeprecatedWrongSeeUrlFormat', array($cr_link));
        }

    }//end process()


}//end class
+122 −0
Original line number Diff line number Diff line
<?php
/**
 * \Drupal\Sniffs\Semanitcs\FunctionTriggerErrorSniff.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */

namespace Drupal\Sniffs\Semantics;

use PHP_CodeSniffer\Files\File;

/**
 * Checks that the trigger_error deprecation text message adheres to standards.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */
class FunctionTriggerErrorSniff extends FunctionCall
{


    /**
     * Returns an array of function names this test wants to listen for.
     *
     * @return array
     */
    public function registerFunctionNames()
    {
        return array('trigger_error');

    }//end registerFunctionNames()


    /**
     * Processes this function call.
     *
     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
     * @param int                  $stackPtr     The position of the function call in
     *                                           the stack.
     * @param int                  $openBracket  The position of the opening
     *                                           parenthesis in the stack.
     * @param int                  $closeBracket The position of the closing
     *                                           parenthesis in the stack.
     *
     * @return void
     */
    public function processFunctionCall(
        file $phpcsFile,
        $stackPtr,
        $openBracket,
        $closeBracket
    ) {

        $tokens = $phpcsFile->getTokens();

        // If no second argument then quit.
        if ($this->getArgument(2) === false) {
            return;
        }

        // Only check deprecation messages.
        if (strcasecmp($tokens[$this->getArgument(2)['start']]['content'], 'E_USER_DEPRECATED') !== 0) {
            return;
        }

        // Get the first argument passed to trigger_error().
        $argument = $this->getArgument(1);

        // Apart from an optional __NAMESPACE__ concatenated at the start of the
        // message, the text should be in one string without any further
        // concatenations. This means that in all cases the 'end' content will
        // contain the message text to be checked.
        $message_text = $tokens[$argument['end']]['content'];

        // The standard format for @trigger_error() is:
        // %thing% is deprecated in %in-version%. %extra-info%. See %cr-link%
        // Use (?U) 'ungreedy' before the version so that only the text up to
        // the first '. ' is matched, as there may be more than one sentence in
        // the extra-info part.
        $matches = array();
        preg_match('/[\'\"](.+) is deprecated in (?U)(.+)\. (.+)\. See (.+)[\'\"]/', $message_text, $matches);

        // There should be 5 items in $matches: 0 is full text, 1 = thing,
        // 2 = in-version, 3 = extra-info, 4 = cr-link.
        if (count($matches) !== 5) {
            $error = "The deprecation message %s does not match the standard format: %%thing%% is deprecated in %%in-version%%. %%extra-info%%. See %%cr-link%%";
            $phpcsFile->addError($error, $argument['start'], 'TriggerErrorTextLayout', array($message_text));
        } else {
            // The text follows the basic layout. Now check that the version
            // matches drupal:n.n.n or project:n.x-n.n. The text must be all
            // lower case and numbers can be one or two digits.
            $in_version = $matches[2];
            if (preg_match('/^drupal:\d{1,2}\.\d{1,2}\.\d{1,2}$/', $in_version) === 0
                && preg_match('/^[a-z\d_]+:\d{1,2}\.x\-\d{1,2}\.\d{1,2}$/', $in_version) === 0
            ) {
                $error = "The deprecation version '%s' does not match the standard: drupal:n.n.n or project:n.x-n.n";
                $phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorVersion', array($in_version));
            }

            // Check the 'See' link.
            $cr_link = $matches[4];
            // Allow for the alternative 'node' or 'project/aaa/issues' format.
            preg_match('[^http(s*)://www.drupal.org/(node|project/\w+/issues)/(\d+)(\.*)$]', $cr_link, $matches);
            // If matches[4] is not blank it means that the url is correct but it
            // ends with a period. As this can be a common mistake give a specific
            // message to assist in fixing.
            if (isset($matches[4]) === true && empty($matches[4]) === false) {
                $error = "The 'See' url '%s' should not end with a period.";
                $phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorPeriodAfterSeeUrl', array($cr_link));
            } else if (empty($matches) === true) {
                $error = "The 'See' url '%s' does not match the standard: http(s)://www.drupal.org/node/n or http(s)://www.drupal.org/project/aaa/issues/n";
                $phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorSeeUrlFormat', array($cr_link));
            }
        }//end if

    }//end processFunctionCall()


}//end class
+52 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 *
 * @deprecated in drupal:8.4.0 and will be removed from drupal:8.6.0.
 * Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead.
 * This file doc block passes the 'Deprecated' standards checks.
 *
 * @see https://www.drupal.org/node/1234
 */

/**
 * This doc block will also pass the 'Deprecated' standards checks.
 *
 * @deprecated in drupal:8.6.0 and will be removed from drupal:9.0.0.
 *   This function block is OK.
 * @see https://www.drupal.org/project/drupal/issues/5678
 */
function x() {
}

/**
 * @deprecated in Drupal 8.4. This will fail basic layout.
 *   Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead.
 *   It also has no 'see' url.
 */

/**
 * This is here to ensure that the 'see' url has to be in the same block.
 * @see https://www.drupal.org/node/5678
 */

/**
 * This function does something.
 *
 * @Deprecated in drupal 8.4.0 and will be removed from drupal:9.0.
 *   Both version formats are incorrect, and the see url is wrong.
 * @see http:drupal.org
 */
function y() {
}

/**
 * This function does something.
 *
 * @deprecated in My_Proj:8.x-1.8 and will be removed from my_Proj:8.x-2.
 *   Both version formats are incorrect, and the see url ends with a period.
 * @see http://www.drupal.org/node/7890.
 */
function z() {
}
+47 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Sniffs\Commenting;

use Drupal\Test\CoderSniffUnitTest;

class DeprecatedUnitTest extends CoderSniffUnitTest
{

    /**
     * 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.
     *
     * @return array(int => int)
     */
    public function getErrorList($testFile = NULL)
    {
        return array(
                24 => 2,  // basic layout is wrong.
               );

    }//end getErrorList()


    /**
     * Returns the lines where warnings should occur.
     *
     * The key of the array should represent the line number and the value
     * should represent the number of warnings that should occur on that line.
     *
     * @return array(int => int)
     */
    public function getWarningList($testFile = NULL)
    {
        return array(
                37 => 2,  // version x 2.
                39 => 1,  // see url.
                47 => 2,  // version x 2.
                49 => 1,  // see url.
               );

    }//end getWarningList()


}//end class
+66 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Example code lines for FunctionTriggerError sniff test.
 */

// Passes.
// No second parameter, so cannot fail it.
@trigger_error('CommentTestBase is deprecated in drupal 8.4.0');
// Not E_USER_DEPRECATED, so cannot fail it.
@trigger_error('CommentTestBase is deprecated in drupal 8.4.0', E_USER_SOMETHING_ELSE);
// All correct.
@trigger_error('\Drupal\Some\Class\Foo is deprecated in drupal:8.5.0. Use \Drupal\Foo\Bar instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Alternative using __NAMESPACE__.
@trigger_error(__NAMESPACE__ . '\Foo is deprecated in drupal:8.5.12. It will be removed from Drupal 9.0. Use \Drupal\Foo\Bar instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Alternative https url and cater for E_User_Deprecated in non-upper case.
@trigger_error("\Drupal\Some\Class\Foo is deprecated in drupal:8.11.0. Use \Drupal\Foo. See https://www.drupal.org/project/drupal/issues/2908490", E_User_Deprecated);
// Alternative syntax for contrib project and issue url.
@trigger_error('Function foo_bar() is deprecated in my_module:8.x-2.0. Use bar_baz() instead. See https://www.drupal.org/project/my_module/issues/2908490', E_USER_DEPRECATED);

// Fails on general layout.
// Wrong first part - missing word.
@trigger_error('drupal_set_message() is deprecated Drupal:8.5.0. It will be removed from Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);
// Wrong first part - bad spacing.
@trigger_error('drupal_set_message() is   deprecated in Drupal:8.5.0. It will be removed from Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);
// Wrong first part - no final dot.
@trigger_error('drupal_set_message() is deprecated in Drupal:8.5.0 Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);
// Wrong first part - nothing before 'is deprecated'.
@trigger_error(__NAMESPACE__ . ' is deprecated in Drupal:8.5.0. It will be removed from Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);
// Missing 'extra info' part.
@trigger_error('drupal_set_message() is deprecated in Drupal:8.5.0. See https://www.drupal.org/node/2774931', E_User_Deprecated);
// String would be OK but is in more than one part.
@trigger_error('drupal_set_message() is deprecated in Drupal:8.5.0. ' . 'It will be removed from Drupal 9.0.0. Use \Drupal\Core\Messenger\MessengerInterface::addMessage() instead. See https://www.drupal.org/node/2774931', E_USER_DEPRECATED);

// Fails on version.
// Version with only one component.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Version with only two components.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Version without :
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal 8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Wrong case for Drupal.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in Drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Non digit in core format.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.x. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// No 'x' in contrib format.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in project:8.6-2.x. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Version more than 2 digits.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in project:182.x-2.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Wrong case for Project.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in Project:8.6-2.x. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490', E_USER_DEPRECATED);

// Fails on 'see' url.
// Incorrect see url - no http.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Incorrect see url - extra text before http.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See this http://www.drupal.org/node/2908490', E_USER_DEPRECATED);
// Incorrect see url - extra text after link.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/2908490 for details', E_USER_DEPRECATED);
// Incorrect see url - no node id.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See http://www.drupal.org/node/', E_USER_DEPRECATED);
// Incorrect see url - non-numeric node id.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See https://www.drupal.org/node/123a', E_USER_DEPRECATED);
// Do not want period after url.
@trigger_error(__NAMESPACE__ . '\CommentTestBase is deprecated in drupal:8.6.0. Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead. See https://www.drupal.org/node/123.', E_USER_DEPRECATED);
Loading