Skip to content

Commit

Permalink
feat(ValidHookName): Add sniff for hook name in PHP attributes (#3499…
Browse files Browse the repository at this point in the history
…291 by nikolay shapovalov)
  • Loading branch information
RuZniki authored Feb 3, 2025
1 parent d18eeb1 commit d8c10bc
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 0 deletions.
82 changes: 82 additions & 0 deletions coder_sniffer/Drupal/Sniffs/Attributes/ValidHookNameSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
/**
* \Drupal\Sniffs\Attributes\ValidHookNameSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/

namespace Drupal\Sniffs\Attributes;

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

/**
* Checks that Hook attribute argument name not starts with "hook_" prefix.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ValidHookNameSniff implements Sniff
{


/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_ATTRIBUTE];

}//end register()


/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
* token was found.
* @param int $stackPtr The position in the PHP_CodeSniffer
* file's token stack where the token
* was found.
*
* @return void|int Optionally returns a stack pointer. The sniff will not be
* called again on the current file until the returned stack
* pointer is reached. Return $phpcsFile->numTokens + 1 to skip
* the rest of the file.
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$attributeName = $phpcsFile->findNext(T_STRING, ($stackPtr + 1));
if ($attributeName !== false
&& $tokens[$attributeName]['content'] === 'Hook'
) {
$hookName = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($attributeName + 2));
if ($hookName !== false) {
// Remove outer quotes.
$hookNameValue = trim($tokens[$hookName]['content'], '"\'');

if (strpos($hookNameValue, 'hook_') === 0 && $hookNameValue !== 'hook_') {
// Remove "hook_" prefix.
$hookNameValueFixed = substr($hookNameValue, 5);
$message = sprintf("The hook name should not start with 'hook_', expected '%s' but found '%s'", $hookNameValueFixed, $hookNameValue);

$fix = $phpcsFile->addFixableWarning($message, $hookName, 'HookPrefix');
if ($fix === true) {
// Return outer quotes.
$hookNameValueFixed = str_replace($hookNameValue, $hookNameValueFixed, $tokens[$hookName]['content']);
$phpcsFile->fixer->replaceToken($hookName, $hookNameValueFixed);
}
}
}
}//end if

}//end process()


}//end class
117 changes: 117 additions & 0 deletions tests/Drupal/Attributes/ValidHookNameUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

/**
* @file
* Contains Test.
*/

/**
* Valid hook.
*/
#[Hook('valid')]
function module_valid() {

}

/**
* Single quotes.
*/
#[Hook('hook_info')]
function module_info() {

}

/**
* Double quotes.
*/
#[Hook("hook_node_load")]
function module_node_load() {

}

/**
* Not finished hook name. No warning
*/
#[Hook('hook_')]
function module_system() {

}

/**
* Attribute named argument.
*/
#[Hook(hook: 'hook_node_delete')]
function module_node_delete() {

}

/**
* Attribute named arguments.
*/
#[Hook(hook: 'hook_node_alter', module: 'custom_module')]
function module_node_alter() {

}

/**
* "hook" is a part of hook name.
*/
#[Hook('hook_piratehook_view')]
function module_piratehook_view() {

}

/**
* Implements hook_hookpirate_view().
*
* "hook" is a part of hook name.
*/
#[Hook('hook_hookpirate_view')]
function module_hookpirate_view() {

}

/**
* Valid hook.
*/
#[Hook('valid', 'validMethod', 'module')]
class ValidHooks {

/**
*
*/
public function validMethod() {

}

}

/**
*
*/
#[Hook('hook_user_cancel', 'userCancel', 'custom')]
class Hooks {

/**
*
*/
public function userCancel() {

}

}

/**
* Named arguments, double quotes.
*/
#[Hook(hook: "hook_user_login", method: "userLogin", module: "views")]
class MyHooks {

/**
*
*/
public function userLogin() {

}

}
117 changes: 117 additions & 0 deletions tests/Drupal/Attributes/ValidHookNameUnitTest.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

/**
* @file
* Contains Test.
*/

/**
* Valid hook.
*/
#[Hook('valid')]
function module_valid() {

}

/**
* Single quotes.
*/
#[Hook('info')]
function module_info() {

}

/**
* Double quotes.
*/
#[Hook("node_load")]
function module_node_load() {

}

/**
* Not finished hook name. No warning.
*/
#[Hook('hook_')]
function module_system() {

}

/**
* Attribute named argument.
*/
#[Hook(hook: 'node_delete')]
function module_node_delete() {

}

/**
* Attribute named arguments.
*/
#[Hook(hook: 'node_alter', module: 'custom_module')]
function module_node_alter() {

}

/**
* "hook" is a part of hook name.
*/
#[Hook('piratehook_view')]
function module_piratehook_view() {

}

/**
* Implements hook_hookpirate_view().
*
* "hook" is a part of hook name.
*/
#[Hook('hookpirate_view')]
function module_hookpirate_view() {

}

/**
* Valid hook.
*/
#[Hook('valid', 'validMethod', 'module')]
class ValidHooks {

/**
*
*/
public function validMethod() {

}

}

/**
*
*/
#[Hook('user_cancel', 'userCancel', 'custom')]
class Hooks {

/**
*
*/
public function userCancel() {

}

}

/**
* Named arguments, double quotes.
*/
#[Hook(hook: "user_login", method: "userLogin", module: "views")]
class MyHooks {

/**
*
*/
public function userLogin() {

}

}
54 changes: 54 additions & 0 deletions tests/Drupal/Attributes/ValidHookNameUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Drupal\Test\Attributes;

use Drupal\Test\CoderSniffUnitTest;

class ValidHookNameUnitTest 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.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int, int>
*/
protected function getErrorList(string $testFile): array
{
return [];

}//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.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int, int>
*/
protected function getWarningList(string $testFile): array
{
return [
19 => 1,
27 => 1,
43 => 1,
51 => 1,
59 => 1,
69 => 1,
92 => 1,
107 => 1,
];

}//end getWarningList()


}//end class

0 comments on commit d8c10bc

Please sign in to comment.