Skip to content

Commit

Permalink
✨ New Utils\MessageHelper class
Browse files Browse the repository at this point in the history
This class initially introduces four new utility methods for working with error/warning messages.

The class currently contains the following methods:
* `addMessage()` - simple method to add either an error or a warning to PHPCS based on an `$isError` parameter. Returns boolean (same as PHPCS natively).
    Supports all optional parameters supported by PHPCS.
* `addFixableMessage()` - simple method to add either a fixable error or a fixable warning to PHPCS based on an `$isError` parameter.  Returns boolean (same as PHPCS natively).
    Supports all optional parameters supported by PHPCS.
* `stringToErrorcode()` - to convert an arbitrary text string to an alphanumeric string with underscores. Returns the adjusted text string.
    This method is intended to pre-empt issues in XML and PHP when arbitrary text strings are used as (part of) an error code.
* `hasNewLineSupport()` - to check whether PHPCS can properly handle new lines in violation messages.
    Prior to PHPCS 3.3.1, new line support in error messages was buggy.
    Ref: squizlabs/PHP_CodeSniffer#2093

Includes dedicated unit tests for each method.
  • Loading branch information
jrfnl committed Apr 5, 2021
1 parent 602d7a3 commit db7c60b
Show file tree
Hide file tree
Showing 6 changed files with 603 additions and 0 deletions.
130 changes: 130 additions & 0 deletions PHPCSUtils/Utils/MessageHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Utils;

use PHP_CodeSniffer\Files\File;
use PHPCSUtils\BackCompat\Helper;

/**
* Helper functions for creating PHPCS error/warning messages.
*
* @since 1.0.0
*/
class MessageHelper
{

/**
* Add a PHPCS message to the output stack as either a warning or an error.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param string $message The message.
* @param int $stackPtr The position of the token
* the message relates to.
* @param bool $isError Whether to report the message as an
* 'error' or 'warning'.
* Defaults to true (error).
* @param string $code The error code for the message.
* Defaults to 'Found'.
* @param array $data Optional input for the data replacements.
* @param int $severity Optional. Severity level. Defaults to 0 which will
* translate to the PHPCS default severity level.
*
* @return bool
*/
public static function addMessage(
File $phpcsFile,
$message,
$stackPtr,
$isError = true,
$code = 'Found',
$data = [],
$severity = 0
) {
if ($isError === true) {
return $phpcsFile->addError($message, $stackPtr, $code, $data, $severity);
}

return $phpcsFile->addWarning($message, $stackPtr, $code, $data, $severity);
}

/**
* Add a PHPCS message to the output stack as either a fixable warning or a fixable error.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param string $message The message.
* @param int $stackPtr The position of the token
* the message relates to.
* @param bool $isError Whether to report the message as an
* 'error' or 'warning'.
* Defaults to true (error).
* @param string $code The error code for the message.
* Defaults to 'Found'.
* @param array $data Optional input for the data replacements.
* @param int $severity Optional. Severity level. Defaults to 0 which will
* translate to the PHPCS default severity level.
*
* @return bool
*/
public static function addFixableMessage(
File $phpcsFile,
$message,
$stackPtr,
$isError = true,
$code = 'Found',
$data = [],
$severity = 0
) {
if ($isError === true) {
return $phpcsFile->addFixableError($message, $stackPtr, $code, $data, $severity);
}

return $phpcsFile->addFixableWarning($message, $stackPtr, $code, $data, $severity);
}

/**
* Convert an arbitrary text string to an alphanumeric string with underscores.
*
* Pre-empt issues in XML and PHP when arbitrary strings are being used as error codes.
*
* @since 1.0.0-alpha4
*
* @param string $text Arbitrary text string intended to be used in an error code.
*
* @return string
*/
public static function stringToErrorcode($text)
{
return \preg_replace('`[^a-z0-9_]`i', '_', $text);
}

/**
* Check whether PHPCS can properly handle new lines in violation messages.
*
* @link https://github.com/squizlabs/PHP_CodeSniffer/pull/2093
*
* @since 1.0.0-alpha4
*
* @return bool
*/
public static function hasNewLineSupport()
{
static $supported;
if (isset($supported) === false) {
$supported = \version_compare(Helper::getVersion(), '3.3.1', '>=');
}

return $supported;
}
}
13 changes: 13 additions & 0 deletions Tests/Utils/MessageHelper/AddMessageTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

/* testAddErrorMessage */
echo 'test 1';

/* testAddWarningMessage */
echo 'test 2';

/* testAddFixableErrorMessage */
echo 'test 3';

/* testAddFixableWarningMessage */
echo 'test 4';
243 changes: 243 additions & 0 deletions Tests/Utils/MessageHelper/AddMessageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2021 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\Utils\MessageHelper;

use PHPCSUtils\TestUtils\UtilityMethodTestCase;
use PHPCSUtils\Utils\MessageHelper;

/**
* Tests for the \PHPCSUtils\Utils\MessageHelper::addMessage() and the
* \PHPCSUtils\Utils\MessageHelper::addFixableMessage() methods.
*
* {@internal Note: this is largely testing PHPCS native functionality, but as PHPCS doesn't
* have any unit tests in place for this functionality, that's not a bad thing.}
*
* @group messagehelper
*
* @since 1.0.0
*/
class AddMessageTest extends UtilityMethodTestCase
{

/**
* Dummy error code to use for the test.
*
* Using the dummy full error code to force it to record.
*
* @var string
*/
const CODE = 'PHPCSUtils.MessageHelper.AddMessageTest.Found';

/**
* Set the name of a sniff to pass to PHPCS to limit the run (and force it to record errors).
*
* @var array
*/
protected static $selectedSniff = ['PHPCSUtils.MessageHelper.AddMessageTest'];

/**
* Test the addMessage wrapper.
*
* @dataProvider dataAddMessage
* @covers \PHPCSUtils\Utils\MessageHelper::addMessage
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param bool $isError Whether to test adding an error or a warning.
* @param array $expected Expected error details.
*
* @return void
*/
public function testAddMessage($testMarker, $isError, $expected)
{
$tokens = self::$phpcsFile->getTokens();
$stackPtr = $this->getTargetToken($testMarker, \T_CONSTANT_ENCAPSED_STRING);
$severity = \mt_rand(5, 10);
$expected['severity'] = $severity;

$return = MessageHelper::addMessage(
self::$phpcsFile,
'Message added. Text: %s',
$stackPtr,
$isError,
static::CODE,
[$tokens[$stackPtr]['content']],
$severity
);

$this->assertTrue($return);

$this->verifyRecordedMessages($stackPtr, $isError, $expected);
}

/**
* Data Provider.
*
* @see testAddMessage() For the array format.
*
* @return array
*/
public function dataAddMessage()
{
return [
'add-error' => [
'/* testAddErrorMessage */',
true,
[
'message' => "Message added. Text: 'test 1'",
'source' => static::CODE,
'fixable' => false,
],
],
'add-warning' => [
'/* testAddWarningMessage */',
false,
[
'message' => "Message added. Text: 'test 2'",
'source' => static::CODE,
'fixable' => false,
],
],
];
}

/**
* Test the addFixableMessage wrapper.
*
* @dataProvider dataAddFixableMessage
* @covers \PHPCSUtils\Utils\MessageHelper::addFixableMessage
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param bool $isError Whether to test adding an error or a warning.
* @param array $expected Expected error details.
*
* @return void
*/
public function testAddFixableMessage($testMarker, $isError, $expected)
{
$tokens = self::$phpcsFile->getTokens();
$stackPtr = $this->getTargetToken($testMarker, \T_CONSTANT_ENCAPSED_STRING);
$severity = \mt_rand(5, 10);
$expected['severity'] = $severity;

$return = MessageHelper::addFixableMessage(
self::$phpcsFile,
'Message added. Text: %s',
$stackPtr,
$isError,
static::CODE,
[$tokens[$stackPtr]['content']],
$severity
);

// Fixable message recording only returns true when the fixer is enabled (=phpcbf).
$this->assertFalse($return);

$this->verifyRecordedMessages($stackPtr, $isError, $expected);
}

/**
* Data Provider.
*
* @see testAddFixableMessage() For the array format.
*
* @return array
*/
public function dataAddFixableMessage()
{
return [
'add-fixable-error' => [
'/* testAddFixableErrorMessage */',
true,
[
'message' => "Message added. Text: 'test 3'",
'source' => static::CODE,
'fixable' => true,
],
],
'add-fixable-warning' => [
'/* testAddFixableWarningMessage */',
false,
[
'message' => "Message added. Text: 'test 4'",
'source' => static::CODE,
'fixable' => true,
],
],
];
}

/**
* Helper method to verify the expected message details.
*
* @param int $stackPtr The stack pointer on which the error/warning is expected.
* @param bool $isError Whether to test adding an error or a warning.
* @param array $expected Expected error details.
*
* @return void
*/
protected function verifyRecordedMessages($stackPtr, $isError, $expected)
{
$tokens = self::$phpcsFile->getTokens();
$errors = self::$phpcsFile->getErrors();
$warnings = self::$phpcsFile->getWarnings();
$result = ($isError === true) ? $errors : $warnings;

/*
* Make sure that no errors/warnings were recorded when the other type is set to be expected.
*/
if ($isError === true) {
$this->assertArrayNotHasKey(
$tokens[$stackPtr]['line'],
$warnings,
'Expected no warnings on line ' . $tokens[$stackPtr]['line'] . '. At least one found.'
);
} else {
$this->assertArrayNotHasKey(
$tokens[$stackPtr]['line'],
$errors,
'Expected no errors on line ' . $tokens[$stackPtr]['line'] . '. At least one found.'
);
}

/*
* Make sure the expected array keys for the the errors/warnings are available.
*/
$this->assertArrayHasKey(
$tokens[$stackPtr]['line'],
$result,
'Expected a violation on line ' . $tokens[$stackPtr]['line'] . '. None found.'
);

$this->assertArrayHasKey(
$tokens[$stackPtr]['column'],
$result[$tokens[$stackPtr]['line']],
'Expected a violation on line ' . $tokens[$stackPtr]['line'] . ', column '
. $tokens[$stackPtr]['column'] . '. None found.'
);

$messages = $result[$tokens[$stackPtr]['line']][$tokens[$stackPtr]['column']];

// Expect one violation.
$this->assertCount(1, $messages, 'Expected 1 violation, found: ' . \count($messages));

$violation = $messages[0];

// PHPCS 2.x places `unknownSniff.` before the actual error code for utility tests with a dummy error code.
$violation['source'] = \str_replace('unknownSniff.', '', $violation['source']);

/*
* Test the violation details.
*/
foreach ($expected as $key => $value) {
$this->assertSame($value, $violation[$key], \ucfirst($key) . ' comparison failed');
}
}
}
4 changes: 4 additions & 0 deletions Tests/Utils/MessageHelper/HasNewLineSupportTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

/* testMessageWithNewLine */
echo 'test 1';
Loading

0 comments on commit db7c60b

Please sign in to comment.