clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
<?php
/**
* \DrupalPractice\Project
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice;
use PHP_CodeSniffer\Files\File;
use \Drupal\Sniffs\InfoFiles\ClassFilesSniff;
use Symfony\Component\Yaml\Yaml;
use PHP_CodeSniffer\Config;
/**
* Helper class to retrieve project information like module/theme name for a file.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class Project
{
/**
* Determines the project short name a file might be associated with.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
*
* @return string|false Returns the project machine name or false if it could not
* be derived.
*/
public static function getName(File $phpcsFile)
{
// Cache the project name per file as this might get called often.
static $cache;
if (isset($cache[$phpcsFile->getFilename()]) === true) {
return $cache[$phpcsFile->getFilename()];
}
$pathParts = pathinfo($phpcsFile->getFilename());
// Module and install files are easy: they contain the project name in the
// file name.
if (isset($pathParts['extension']) === true && in_array($pathParts['extension'], ['install', 'module', 'profile', 'theme']) === true) {
$cache[$phpcsFile->getFilename()] = $pathParts['filename'];
return $pathParts['filename'];
}
$infoFile = static::getInfoFile($phpcsFile);
if ($infoFile === false) {
return false;
}
$pathParts = pathinfo($infoFile);
// Info files end in *.info.yml on Drupal 8 and *.info on Drupal 7.
$filename = $pathParts['filename'];
$filename = preg_replace('/\.info$/', '', $filename);
$cache[$phpcsFile->getFilename()] = $filename;
return $filename;
}//end getName()
/**
* Determines the info file a file might be associated with.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
*
* @return string|false The project info file name or false if it could not
* be derived.
*/
public static function getInfoFile(File $phpcsFile)
{
// Cache the project name per file as this might get called often.
static $cache;
if (isset($cache[$phpcsFile->getFilename()]) === true) {
return $cache[$phpcsFile->getFilename()];
}
$pathParts = pathinfo($phpcsFile->getFilename());
// Search for an info file.
$dir = $pathParts['dirname'];
do {
$infoFiles = glob("$dir/*.info.yml");
if (empty($infoFiles) === true) {
$infoFiles = glob("$dir/*.info");
}
// Filter out directories.
$infoFiles = array_filter($infoFiles, 'is_file');
// Go one directory up if we do not find an info file here.
$dir = dirname($dir);
} while (empty($infoFiles) === true && $dir !== dirname($dir));
// No info file found, so we give up.
if (empty($infoFiles) === true) {
$cache[$phpcsFile->getFilename()] = false;
return false;
}
// Sort the info file names and take the shortest info file.
usort($infoFiles, [__NAMESPACE__.'\Project', 'compareLength']);
$infoFile = $infoFiles[0];
$cache[$phpcsFile->getFilename()] = $infoFile;
return $infoFile;
}//end getInfoFile()
/**
* Determines the *.services.yml file in a module.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
*
* @return string|false The Services YML file name or false if it could not
* be derived.
*/
public static function getServicesYmlFile(File $phpcsFile)
{
// Cache the services file per file as this might get called often.
static $cache;
if (isset($cache[$phpcsFile->getFilename()]) === true) {
return $cache[$phpcsFile->getFilename()];
}
$pathParts = pathinfo($phpcsFile->getFilename());
// Search for an info file.
$dir = $pathParts['dirname'];
do {
$ymlFiles = glob("$dir/*.services.yml");
// Go one directory up if we do not find an info file here.
$dir = dirname($dir);
} while (empty($ymlFiles) === true && $dir !== dirname($dir));
// No YML file found, so we give up.
if (empty($ymlFiles) === true) {
$cache[$phpcsFile->getFilename()] = false;
return false;
}
// Sort the YML file names and take the shortest info file.
usort($ymlFiles, [__NAMESPACE__.'\Project', 'compareLength']);
$ymlFile = $ymlFiles[0];
$cache[$phpcsFile->getFilename()] = $ymlFile;
return $ymlFile;
}//end getServicesYmlFile()
/**
* Return true if the given class is a Drupal service registered in *.services.yml.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $classPtr The position of the class declaration
* in the token stack.
*
* @return bool
*/
public static function isServiceClass(File $phpcsFile, $classPtr)
{
// Cache the information per file as this might get called often.
static $cache;
if (isset($cache[$phpcsFile->getFilename()]) === true) {
return $cache[$phpcsFile->getFilename()];
}
// Get the namespace of the class if there is one.
$namespacePtr = $phpcsFile->findPrevious(T_NAMESPACE, ($classPtr - 1));
if ($namespacePtr === false) {
$cache[$phpcsFile->getFilename()] = false;
return false;
}
$ymlFile = static::getServicesYmlFile($phpcsFile);
if ($ymlFile === false) {
$cache[$phpcsFile->getFilename()] = false;
return false;
}
$services = Yaml::parse(file_get_contents($ymlFile), Yaml::PARSE_CUSTOM_TAGS);
if (isset($services['services']) === false) {
$cache[$phpcsFile->getFilename()] = false;
return false;
}
$nsEnd = $phpcsFile->findNext(
[
T_NS_SEPARATOR,
T_STRING,
T_WHITESPACE,
],
($namespacePtr + 1),
null,
true
);
$namespace = trim($phpcsFile->getTokensAsString(($namespacePtr + 1), ($nsEnd - $namespacePtr - 1)));
$classNameSpaced = ltrim($namespace.'\\'.$phpcsFile->getDeclarationName($classPtr), '\\');
foreach ($services['services'] as $service) {
if (isset($service['class']) === true
&& $classNameSpaced === ltrim($service['class'], '\\')
) {
$cache[$phpcsFile->getFilename()] = true;
return true;
}
}
return false;
}//end isServiceClass()
/**
* Helper method to sort array values by string length with usort().
*
* @param string $a First string.
* @param string $b Second string.
*
* @return int
*/
public static function compareLength($a, $b)
{
return (strlen($a) - strlen($b));
}//end compareLength()
/**
* Determines the Drupal core version a file might be associated with.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
*
* @return int The core version number. Returns 8 by default.
*/
public static function getCoreVersion(File $phpcsFile)
{
// First check if a config option was passed.
$coreVersion = Config::getConfigData('drupal_core_version');
if (empty($coreVersion) === false) {
return (int) $coreVersion;
}
// Try to guess the core version from info files in the file path.
$infoFile = static::getInfoFile($phpcsFile);
if ($infoFile === false) {
// Default to Drupal 8.
return 8;
}
$pathParts = pathinfo($infoFile);
// Drupal 6 and 7 use the .info file extension.
if ($pathParts['extension'] === 'info') {
$infoSettings = ClassFilesSniff::drupalParseInfoFormat(file_get_contents($infoFile));
if (isset($infoSettings['core']) === true
&& is_string($infoSettings['core']) === true
) {
return (int) $infoSettings['core'][0];
}
// Default to Drupal 7 if there is an info file.
return 7;
}
// Drupal 8 uses the .yml file extension.
// @todo Revisit for Drupal 9, but I don't want to do YAML parsing
// for now.
return 8;
}//end getCoreVersion()
}//end class

View File

@@ -0,0 +1,26 @@
<?php
/**
* \DrupalPractice\Sniffs\CodeAnalysis\VariableAnalysisSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\CodeAnalysis;
use VariableAnalysis\Sniffs\CodeAnalysis\VariableAnalysisSniff as VendorVariableAnalysisSniff;
/**
* Checks for variable usage using the sirbrillig/phpcs-variable-analysis sniff.
*
* This class exists to make the VariableAnalysis sniff available to DrupalPractice.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class VariableAnalysisSniff extends VendorVariableAnalysisSniff
{
}//end class

View File

@@ -0,0 +1,60 @@
<?php
/**
* \DrupalPractice\Sniffs\Commenting\AuthorTagSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Commenting;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks the usage of @author tags.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class AuthorTagSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [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();
$content = $tokens[$stackPtr]['content'];
if ($content === '@author' || $content === '@author:') {
$warning = '@author tags are not usually used in Drupal, because over time multiple contributors will touch the code anyway';
$phpcsFile->addWarning($warning, $stackPtr, 'AuthorFound');
}
}//end process()
}//end class

View File

@@ -0,0 +1,76 @@
<?php
/**
* \DrupalPractice\Sniffs\Commenting\CommentEmptyLineSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Commenting;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Throws a warning if there is a blank line after an inline comment.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class CommentEmptyLineSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_COMMENT];
}//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();
$comment = rtrim($tokens[$stackPtr]['content']);
// Only want inline comments.
if (substr($comment, 0, 2) !== '//') {
return;
}
// The line below the last comment cannot be empty.
for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) {
if ($tokens[$i]['code'] !== T_WHITESPACE) {
return;
}
} else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) {
break;
}
}
$warning = 'There must be no blank line following an inline comment';
$phpcsFile->addWarning($warning, $stackPtr, 'SpacingAfter');
}//end process()
}//end class

View File

@@ -0,0 +1,65 @@
<?php
/**
* \DrupalPractice\Sniffs\Commenting\ExpectedExceptionSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Commenting;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that the PHPunit @expectedException tags are not used.
*
* See https://thephp.cc/news/2016/02/questioning-phpunit-best-practices .
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ExpectedExceptionSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [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();
$content = $tokens[$stackPtr]['content'];
if ($content === '@expectedException' || $content === '@expectedExceptionCode'
|| $content === '@expectedExceptionMessage'
|| $content === '@expectedExceptionMessageRegExp'
) {
$warning = '%s tags should not be used, use $this->setExpectedException() or $this->expectException() instead';
$phpcsFile->addWarning($warning, $stackPtr, 'TagFound', [$content]);
}
}//end process()
}//end class

View File

@@ -0,0 +1,83 @@
<?php
/**
* \DrupalPractice\Sniffs\Constants\GlobalConstantSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Constants;
use DrupalPractice\Project;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that globally defined constants are not used in Drupal 8 and higher.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GlobalConstantSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_CONST];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void|int
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Only check constants in the global scope.
if (empty($tokens[$stackPtr]['conditions']) === false) {
return;
}
$coreVersion = Project::getCoreVersion($phpcsFile);
if ($coreVersion < 8) {
// No need to check this file again, mark it as done.
return ($phpcsFile->numTokens + 1);
}
// Allow constants if they are deprecated.
$commentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if ($commentEnd !== null && $tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
// Go through all comment tags and check if one is @deprecated.
$commentTag = $commentEnd;
while ($commentTag !== null && $commentTag > $tokens[$commentEnd]['comment_opener']) {
if ($tokens[$commentTag]['content'] === '@deprecated') {
return;
}
$commentTag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($commentTag - 1), $tokens[$commentEnd]['comment_opener']);
}
}
$warning = 'Global constants should not be used, move it to a class or interface';
$phpcsFile->addWarning($warning, $stackPtr, 'GlobalConstant');
}//end process()
}//end class

View File

@@ -0,0 +1,91 @@
<?php
/**
* \DrupalPractice\Sniffs\Constants\GlobalDefineSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Constants;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
use DrupalPractice\Project;
/**
* Checks that global define() constants are not used in modules in Drupal 8.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GlobalDefineSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['define'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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|int
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
// Only check constants in the global scope in module files.
if (empty($tokens[$stackPtr]['conditions']) === false || substr($phpcsFile->getFilename(), -7) !== '.module') {
return;
}
$coreVersion = Project::getCoreVersion($phpcsFile);
if ($coreVersion < 8) {
// No need to check this file again, mark it as done.
return ($phpcsFile->numTokens + 1);
}
// Allow constants if they are deprecated.
$commentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if ($commentEnd !== null && $tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
// Go through all comment tags and check if one is @deprecated.
$commentTag = $commentEnd;
while ($commentTag !== null && $commentTag > $tokens[$commentEnd]['comment_opener']) {
if ($tokens[$commentTag]['content'] === '@deprecated') {
return;
}
$commentTag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, ($commentTag - 1), $tokens[$commentEnd]['comment_opener']);
}
}
$warning = 'Global constants should not be used, move it to a class or interface';
$phpcsFile->addWarning($warning, $stackPtr, 'GlobalConstant');
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,67 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\CheckPlainSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Check that check_plain() is not used on literal strings.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class CheckPlainSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['check_plain'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(1);
if ($argument['start'] === $argument['end'] && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$warning = 'Do not use check_plain() on string literals, because they cannot contain user provided text';
$phpcsFile->addWarning($warning, $argument['start'], 'CheckPlainLiteral');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,73 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\CurlSslVerifierSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Make sure that CURLOPT_SSL_VERIFYPEER is not disabled, since that is a
* security issue.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class CurlSslVerifierSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['curl_setopt'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$option = $this->getArgument(2);
if ($tokens[$option['start']]['content'] !== 'CURLOPT_SSL_VERIFYPEER') {
return;
}
$value = $this->getArgument(3);
if ($tokens[$value['start']]['content'] === 'FALSE' || $tokens[$value['start']]['content'] === '0') {
$warning = 'Potential security problem: SSL peer verification must not be disabled';
$phpcsFile->addWarning($warning, $value['start'], 'SslPeerVerificationDisabled');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,83 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\DbQuerySniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
use DrupalPractice\Project;
/**
* Check that UPDATE/DELETE queries are not used in db_query() in Drupal 7.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DbQuerySniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['db_query'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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|int
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
// This check only applies to Drupal 7, not Drupal 6.
if (Project::getCoreVersion($phpcsFile) !== 7) {
return ($phpcsFile->numTokens + 1);
}
$tokens = $phpcsFile->getTokens();
$argument = $this->getArgument(1);
$queryStart = '';
for ($start = $argument['start']; $tokens[$start]['code'] === T_CONSTANT_ENCAPSED_STRING && empty($queryStart) === true; $start++) {
// Remove quote and white space from the beginning.
$queryStart = trim(substr($tokens[$start]['content'], 1));
// Just look at the first word.
$parts = explode(' ', $queryStart);
$queryStart = $parts[0];
if (in_array(strtoupper($queryStart), ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE']) === true) {
$warning = 'Do not use %s queries with db_query(), use %s instead';
$phpcsFile->addWarning($warning, $start, 'DbQuery', [$queryStart, 'db_'.strtolower($queryStart).'()']);
}
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,70 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\DbSelectBracesSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Check that db_select() calls do not use {} braces for the table name.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DbSelectBracesSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['db_select'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(1);
if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING
&& strpos($tokens[$argument['start']]['content'], '{') !== false
) {
$warning = 'Do not use {} curly brackets in db_select() table names';
$phpcsFile->addWarning($warning, $argument['start'], 'DbSelectBrace');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,89 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\DefaultValueSanitizeSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check that sanitization functions such as check_plain() are not used on Form
* API #default_value elements.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DefaultValueSanitizeSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return [
'check_markup',
'check_plain',
'check_url',
'filter_xss',
'filter_xss_admin',
];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
// We assume that the sequence '#default_value' => check_plain(...) is
// wrong because the Form API already sanitizes #default_value.
$arrow = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($arrow === false || $tokens[$arrow]['code'] !== T_DOUBLE_ARROW) {
return;
}
$arrayKey = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrow - 1), null, true);
if ($arrayKey === false
|| $tokens[$arrayKey]['code'] !== T_CONSTANT_ENCAPSED_STRING
|| substr($tokens[$arrayKey]['content'], 1, -1) !== '#default_value'
) {
return;
}
$warning = 'Do not use the %s() sanitization function on Form API #default_value elements, they get escaped automatically';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addWarning($warning, $stackPtr, 'DefaultValue', $data);
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,70 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\FormErrorTSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Verifies that messages passed to form_set_error() run through t().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FormErrorTSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return [
'form_set_error',
'form_error',
];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(2);
if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$warning = 'Form error messages are user facing text and must run through t() for translation';
$phpcsFile->addWarning($warning, $argument['start'], 'ErrorMessage');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,103 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\InsecureUnserializeSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Check that unserialize() limits classes that may be unserialized.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class InsecureUnserializeSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['unserialize'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(2);
if ($argument === false) {
$this->fail($phpcsFile, $closeBracket);
return;
}
$allowedClassesKeyStart = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $argument['start'], $argument['end'], false, '\'allowed_classes\'');
if ($allowedClassesKeyStart === false) {
$allowedClassesKeyStart = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $argument['start'], $argument['end'], false, '"allowed_classes"');
}
if ($allowedClassesKeyStart === false) {
$this->fail($phpcsFile, $argument['end']);
return;
}
$allowedClassesArrow = $phpcsFile->findNext(T_DOUBLE_ARROW, $allowedClassesKeyStart, $argument['end'], false);
if ($allowedClassesArrow === false) {
$this->fail($phpcsFile, $argument['end']);
return;
}
$allowedClassesValue = $phpcsFile->findNext(T_WHITESPACE, ($allowedClassesArrow + 1), $argument['end'], true);
if ($tokens[$allowedClassesValue]['code'] === T_TRUE) {
$this->fail($phpcsFile, $allowedClassesValue);
}
}//end processFunctionCall()
/**
* Record a violation of the standard.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $position The stack position of the violation.
*
* @return void
*/
protected function fail(File $phpcsFile, int $position)
{
$phpcsFile->addError('unserialize() is insecure unless allowed classes are limited. Use a safe format like JSON or use the allowed_classes option.', $position, 'InsecureUnserialize');
}//end fail()
}//end class

View File

@@ -0,0 +1,67 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\Drupal_Sniffs_FunctionCalls_LCheckPlainSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* The first argument of the l() function should not be check_plain()'ed.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class LCheckPlainSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['l'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(1);
if ($tokens[$argument['start']]['content'] === 'check_plain') {
$warning = 'Do not use check_plain() on the first argument of l(), because l() will sanitize it for you by default';
$phpcsFile->addWarning($warning, $argument['start'], 'LCheckPlain');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,67 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\MessageTSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Verifies that messages passed to drupal_set_message() run through t().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class MessageTSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['drupal_set_message'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(1);
if ($argument !== false && $tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$warning = 'Messages are user facing text and must run through t() for translation';
$phpcsFile->addWarning($warning, $argument['start'], 'ErrorMessage');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,93 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\TCheckPlainSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Check that "@" and "%" placeholders in t()/watchdog() are not escaped twice
* with check_plain().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class TCheckPlainSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return [
't',
'watchdog',
];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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 ($tokens[$stackPtr]['content'] === 't') {
$argument = $this->getArgument(2);
} else {
// For watchdog() the placeholders are in the third argument.
$argument = $this->getArgument(3);
}
if ($argument === false) {
return;
}
if ($tokens[$argument['start']]['code'] !== T_ARRAY) {
return;
}
$checkPlain = $argument['start'];
while (($checkPlain = $phpcsFile->findNext(T_STRING, ($checkPlain + 1), $tokens[$argument['start']]['parenthesis_closer'])) !== false) {
if ($tokens[$checkPlain]['content'] === 'check_plain') {
// The check_plain() could be embedded with string concatenation,
// which we want to allow.
$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($checkPlain - 1), $argument['start'], true);
if ($previous === false || $tokens[$previous]['code'] !== T_STRING_CONCAT) {
$warning = 'The extra check_plain() is not necessary for placeholders, "@" and "%" will automatically run check_plain()';
$phpcsFile->addWarning($warning, $checkPlain, 'CheckPlain');
}
}
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,67 @@
<?php
/**
* ThemeSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* \DrupalPractice\Sniffs\FunctionCalls\Checks that theme functions are not directly called.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ThemeSniff extends FunctionCall
{
/**
* List of functions starting with "theme_" that don't generate theme output.
*
* @var array<string>
*/
protected $reservedFunctions = [
'theme_get_registry',
'theme_get_setting',
'theme_render_template',
'theme_enable',
'theme_disable',
'theme_get_suggestions',
];
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$functionName = $tokens[$stackPtr]['content'];
if (strpos($functionName, 'theme_') !== 0
|| in_array($functionName, $this->reservedFunctions) === true
|| $this->isFunctionCall($phpcsFile, $stackPtr) === false
) {
return;
}
$themeName = substr($functionName, 6);
$warning = "Do not call theme functions directly, use theme('%s', ...) instead";
$phpcsFile->addWarning($warning, $stackPtr, 'ThemeFunctionDirect', [$themeName]);
}//end process()
}//end class

View File

@@ -0,0 +1,80 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionCalls\VariableSetSanitizeSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionCalls;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Check that variable_set() calls do not run check_plain() or other
* sanitization functions on the value.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class VariableSetSanitizeSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['variable_set'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
$argument = $this->getArgument(2);
if ($argument !== false && in_array(
$tokens[$argument['start']]['content'],
[
'check_markup',
'check_plain',
'check_url',
'filter_xss',
'filter_xss_admin',
]
) === true
) {
$warning = 'Do not use the %s() sanitization function when writing values to the database, use it on output to HTML instead';
$data = [$tokens[$argument['start']]['content']];
$phpcsFile->addWarning($warning, $argument['start'], 'VariableSet', $data);
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,102 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionDefinitions\AccessHookMenuSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionDefinitions;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionDefinition;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that there are no undocumented open access callbacks in hook_menu().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class AccessHookMenuSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name
* in the stack.
* @param int $functionPtr The position of the function keyword
* in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -6));
// Only check in *.module files.
if ($fileExtension !== 'module') {
return;
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -7);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== ($fileName.'_menu')) {
return;
}
// Search for 'access callback' => TRUE in the function body.
$string = $phpcsFile->findNext(
T_CONSTANT_ENCAPSED_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if (substr($tokens[$string]['content'], 1, -1) === 'access callback') {
$arrayOperator = $phpcsFile->findNext(
Tokens::$emptyTokens,
($string + 1),
null,
true
);
if ($arrayOperator !== false
&& $tokens[$arrayOperator]['code'] === T_DOUBLE_ARROW
) {
$callback = $phpcsFile->findNext(
Tokens::$emptyTokens,
($arrayOperator + 1),
null,
true
);
if ($callback !== false && $tokens[$callback]['code'] === T_TRUE) {
// Check if there is a comment before the line that might
// explain stuff.
$commentBefore = $phpcsFile->findPrevious(
T_WHITESPACE,
($string - 1),
$tokens[$functionPtr]['scope_opener'],
true
);
if ($commentBefore !== false && in_array($tokens[$commentBefore]['code'], Tokens::$commentTokens) === false) {
$warning = 'Open page callback found, please add a comment before the line why there is no access restriction';
$phpcsFile->addWarning($warning, $callback, 'OpenCallback');
}
}
}//end if
}//end if
$string = $phpcsFile->findNext(
T_CONSTANT_ENCAPSED_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,81 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionDefinitions\FormAlterDocSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionDefinitions;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionDefinition;
use DrupalPractice\Project;
/**
* Checks that the comment "Implements hook_form_alter()." actually matches the
* function signature.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FormAlterDocSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name
* in the stack.
* @param int $functionPtr The position of the function keyword
* in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$tokens = $phpcsFile->getTokens();
$docCommentEnd = $phpcsFile->findPrevious(T_WHITESPACE, ($functionPtr - 1), null, true);
// If there is no doc comment there is nothing we can check.
if ($docCommentEnd === false || $tokens[$docCommentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
return;
}
$commentLine = ($docCommentEnd - 1);
$commentFound = false;
while ($tokens[$commentLine]['code'] !== T_DOC_COMMENT_OPEN_TAG) {
if (strpos($tokens[$commentLine]['content'], 'Implements hook_form_alter().') === 0) {
$commentFound = true;
break;
}
$commentLine--;
}
if ($commentFound === false) {
return;
}
$projectName = Project::getName($phpcsFile);
if ($projectName === false) {
return;
}
if ($tokens[$stackPtr]['content'] !== $projectName.'_form_alter') {
$warning = 'Doc comment indicates hook_form_alter() but function signature is "%s" instead of "%s". Did you mean hook_form_FORM_ID_alter()?';
$data = [
$tokens[$stackPtr]['content'],
$projectName.'_form_alter',
];
$phpcsFile->addWarning($warning, $commentLine, 'Different', $data);
}
}//end processFunction()
}//end class

View File

@@ -0,0 +1,95 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionDefinitions\HookInitCssSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionDefinitions;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionDefinition;
use DrupalPractice\Project;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that drupal_add_css() is not used in hook_init().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class HookInitCssSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name
* in the stack.
* @param int $functionPtr The position of the function keyword
* in the stack.
*
* @return void|int
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -6));
// Only check in *.module files.
if ($fileExtension !== 'module') {
return ($phpcsFile->numTokens + 1);
}
// This check only applies to Drupal 7, not Drupal 6.
if (Project::getCoreVersion($phpcsFile) !== 7) {
return ($phpcsFile->numTokens + 1);
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -7);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== ($fileName.'_init') && $tokens[$stackPtr]['content'] !== ($fileName.'_page_build')) {
return;
}
// Search in the function body for drupal_add_css() calls.
$string = $phpcsFile->findNext(
T_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if ($tokens[$string]['content'] === 'drupal_add_css' || $tokens[$string]['content'] === 'drupal_add_js') {
$opener = $phpcsFile->findNext(
Tokens::$emptyTokens,
($string + 1),
null,
true
);
if ($opener !== false
&& $tokens[$opener]['code'] === T_OPEN_PARENTHESIS
) {
if ($tokens[$stackPtr]['content'] === ($fileName.'_init')) {
$warning = 'Do not use %s() in hook_init(), use #attached for CSS and JS in your page/form callback or in hook_page_build() instead';
$phpcsFile->addWarning($warning, $string, 'AddFunctionFound', [$tokens[$string]['content']]);
} else {
$warning = 'Do not use %s() in hook_page_build(), use #attached for CSS and JS on the $page render array instead';
$phpcsFile->addWarning($warning, $string, 'AddFunctionFoundPageBuild', [$tokens[$string]['content']]);
}
}
}
$string = $phpcsFile->findNext(
T_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,92 @@
<?php
/**
* \DrupalPractice\Sniffs\FunctionDefinitions\InstallTSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\FunctionDefinitions;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionDefinition;
use DrupalPractice\Project;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that t() and st() are not used in hook_install() and hook_requirements().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class InstallTSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name
* in the stack.
* @param int $functionPtr The position of the function keyword
* in the stack.
*
* @return void|int
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -7));
// Only check in *.install files.
if ($fileExtension !== 'install') {
return ($phpcsFile->numTokens + 1);
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -8);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== ($fileName.'_install')
&& $tokens[$stackPtr]['content'] !== ($fileName.'_requirements')
) {
return;
}
// This check only applies to Drupal 7, not Drupal 8.
if (Project::getCoreVersion($phpcsFile) !== 7) {
return ($phpcsFile->numTokens + 1);
}
// Search in the function body for t() calls.
$string = $phpcsFile->findNext(
T_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if ($tokens[$string]['content'] === 't' || $tokens[$string]['content'] === 'st') {
$opener = $phpcsFile->findNext(
Tokens::$emptyTokens,
($string + 1),
null,
true
);
if ($opener !== false
&& $tokens[$opener]['code'] === T_OPEN_PARENTHESIS
) {
$error = 'Do not use t() or st() in installation phase hooks, use $t = get_t() to retrieve the appropriate localization function name';
$phpcsFile->addError($error, $string, 'TranslationFound');
}
}
$string = $phpcsFile->findNext(
T_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,76 @@
<?php
/**
* \DrupalPractice\Sniffs\General\AccessAdminPagesSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionDefinition;
/**
* Throws a warning if the "access administration pages" string is found in
* hook_menu().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class AccessAdminPagesSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function
* name in the stack.
* @param int $functionPtr The position of the function
* keyword in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -6));
// Only check in *.module files.
if ($fileExtension !== 'module') {
return;
}
$tokens = $phpcsFile->getTokens();
$fileName = substr(basename($phpcsFile->getFilename()), 0, -7);
if ($tokens[$stackPtr]['content'] !== ($fileName.'_menu')) {
return;
}
// Search in the function body for "access administration pages" strings.
$string = $phpcsFile->findNext(
T_CONSTANT_ENCAPSED_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if (substr($tokens[$string]['content'], 1, -1) === 'access administration pages') {
$warning = 'The administration menu callback should probably use "administer site configuration" - which implies the user can change something - rather than "access administration pages" which is about viewing but not changing configurations.';
$phpcsFile->addWarning($warning, $string, 'PermissionFound');
}
$string = $phpcsFile->findNext(
T_CONSTANT_ENCAPSED_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,96 @@
<?php
/**
* \DrupalPractice\Sniffs\General\ClassNameSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use DrupalPractice\Project;
/**
* Checks that classes without namespaces are properly prefixed with the module
* name.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ClassNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [
T_CLASS,
T_INTERFACE,
];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// If there is a PHP 5.3 namespace declaration in the file we return
// immediately as classes can be named arbitrary within a namespace.
$namespace = $phpcsFile->findPrevious(T_NAMESPACE, ($stackPtr - 1));
if ($namespace !== false) {
return;
}
$moduleName = Project::getName($phpcsFile);
if ($moduleName === false) {
return;
}
$tokens = $phpcsFile->getTokens();
$className = $phpcsFile->findNext(T_STRING, $stackPtr);
$name = trim($tokens[$className]['content']);
// Underscores are omitted in class names. Also convert all characters
// to lower case to compare them later.
$classPrefix = strtolower(str_replace('_', '', $moduleName));
// Views classes might have underscores in the name, which is also fine.
$viewsPrefix = strtolower($moduleName);
$name = strtolower($name);
if (strpos($name, $classPrefix) !== 0 && strpos($name, $viewsPrefix) !== 0) {
$warning = '%s name must be prefixed with the project name "%s"';
$nameParts = explode('_', $moduleName);
$camelName = '';
foreach ($nameParts as &$part) {
$camelName .= ucfirst($part);
}
$errorData = [
ucfirst($tokens[$stackPtr]['content']),
$camelName,
];
$phpcsFile->addWarning($warning, $className, 'ClassPrefix', $errorData);
}
}//end process()
}//end class

View File

@@ -0,0 +1,78 @@
<?php
/**
* \DrupalPractice\Sniffs\General\DescriptionTSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that string values for #description in render arrays are translated.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DescriptionTSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_CONSTANT_ENCAPSED_STRING];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// Look for the string "#description".
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== '"#description"' && $tokens[$stackPtr]['content'] !== "'#description'") {
return;
}
// Look for an array pattern that starts to define #description values.
$statementEnd = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1));
$arrayString = $phpcsFile->getTokensAsString(($stackPtr + 1), ($statementEnd - $stackPtr));
// Cut out all the white space.
$arrayString = preg_replace('/\s+/', '', $arrayString);
if (strpos($arrayString, '=>"') !== 0 && strpos($arrayString, ']="') !== 0
&& strpos($arrayString, "=>'") !== 0 && strpos($arrayString, "]='") !== 0
) {
return;
}
$stringToken = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($stackPtr + 1));
$content = strip_tags($tokens[$stringToken]['content']);
if (strlen($content) > 5) {
$warning = '#description values usually have to run through t() for translation';
$phpcsFile->addWarning($warning, $stringToken, 'DescriptionT');
}
}//end process()
}//end class

View File

@@ -0,0 +1,71 @@
<?php
/**
* \DrupalPractice\Sniffs\General\ExceptionTSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that exceptions aren't translated.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ExceptionTSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_THROW];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$endPtr = $phpcsFile->findEndOfStatement($stackPtr);
$newPtr = $phpcsFile->findNext(T_NEW, ($stackPtr + 1), $endPtr);
if ($newPtr !== false) {
$openPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($newPtr + 1), $endPtr);
if ($openPtr !== false) {
for ($i = ($openPtr + 1); $i < $tokens[$openPtr]['parenthesis_closer']; $i++) {
if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === 't') {
$warning = 'Exceptions should not be translated';
$phpcsFile->addWarning($warning, $stackPtr, 'ExceptionT');
return;
}
}
}
}
}//end process()
}//end class

View File

@@ -0,0 +1,60 @@
<?php
/**
* \DrupalPractice\Sniffs\General\FormStateInputSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Throws a message whenever $form_state['input'] is used. $form_state['values']
* is preferred.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FormStateInputSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_VARIABLE];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($phpcsFile->getTokensAsString($stackPtr, 4) === '$form_state[\'input\']'
|| $phpcsFile->getTokensAsString($stackPtr, 4) === '$form_state["input"]'
) {
$warning = 'Do not use the raw $form_state[\'input\'], use $form_state[\'values\'] instead where possible';
$phpcsFile->addWarning($warning, $stackPtr, 'Input');
}
}//end process()
}//end class

View File

@@ -0,0 +1,61 @@
<?php
/**
* \DrupalPractice\Sniffs\General\LanguageNoneSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that ['und'] is not used, should be LANGUAGE_NONE.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class LanguageNoneSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [
T_OPEN_SQUARE_BRACKET,
T_OPEN_SHORT_ARRAY,
];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$sequence = $phpcsFile->getTokensAsString($stackPtr, 3);
if ($sequence === "['und']" || $sequence === '["und"]') {
$warning = "Are you accessing field values here? Then you should use LANGUAGE_NONE instead of 'und'";
$phpcsFile->addWarning($warning, ($stackPtr + 1), 'Und');
}
}//end process()
}//end class

View File

@@ -0,0 +1,141 @@
<?php
/**
* \DrupalPractice\Sniffs\General\OptionsTSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that values in #options form arrays are translated.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class OptionsTSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_CONSTANT_ENCAPSED_STRING];
}//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 function
* name in the stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// Look for the string "#options".
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== '"#options"' && $tokens[$stackPtr]['content'] !== "'#options'") {
return;
}
// Look for an opening array pattern that starts to define #options
// values.
$statementEnd = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1));
$arrayString = $phpcsFile->getTokensAsString(($stackPtr + 1), ($statementEnd - $stackPtr));
// Cut out all the white space.
$arrayString = preg_replace('/\s+/', '', $arrayString);
if (strpos($arrayString, '=>array(') !== 0
&& strpos($arrayString, ']=array(') !== 0
&& strpos($arrayString, '=>[') !== 0
&& strpos($arrayString, ']=[') !== 0
) {
return;
}
// We only search within the #options array.
$arrayToken = $phpcsFile->findNext([T_ARRAY, T_OPEN_SHORT_ARRAY], ($stackPtr + 1));
$nestedParenthesis = [];
if (isset($tokens[$arrayToken]['nested_parenthesis']) === true) {
$nestedParenthesis = $tokens[$arrayToken]['nested_parenthesis'];
}
if ($tokens[$arrayToken]['code'] === T_ARRAY) {
$statementEnd = $tokens[$arrayToken]['parenthesis_closer'];
$nestedParenthesis[$tokens[$arrayToken]['parenthesis_opener']] = $tokens[$arrayToken]['parenthesis_closer'];
} else {
$statementEnd = $tokens[$arrayToken]['bracket_closer'];
}
// We want to find if the element "#options" belongs to a form element.
// Array with selectable options for a form element.
$formElements = [
"'checkboxes'",
"'radios'",
"'select'",
"'tableselect'",
];
// Find beginning of the array containing "#options" element.
$startArray = $phpcsFile->findStartOfStatement($stackPtr, [T_DOUBLE_ARROW, T_OPEN_SHORT_ARRAY, T_OPEN_PARENTHESIS, T_COMMA]);
// Find next element on array of "#type".
$findType = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($startArray + 1), $statementEnd, false, "'#type'");
// Stop checking the array if its #type cannot be determined.
if ($findType === false) {
return;
}
// Get the value of "#type".
$valueType = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, ($findType + 1), null, false);
// Go through the array by examining stuff after "=>".
$arrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($arrayToken + 1), $statementEnd, false, null, true);
while ($arrow !== false) {
$arrayValue = $phpcsFile->findNext(T_WHITESPACE, ($arrow + 1), $statementEnd, true);
$valueNestedParenthesis = [];
if (isset($tokens[$arrayValue]['nested_parenthesis']) === true) {
$valueNestedParenthesis = $tokens[$arrayValue]['nested_parenthesis'];
}
// We are only interested in string literals that are not numbers
// and more than 3 characters long.
if ($tokens[$arrayValue]['code'] === T_CONSTANT_ENCAPSED_STRING
&& is_numeric(substr($tokens[$arrayValue]['content'], 1, -1)) === false
&& strlen($tokens[$arrayValue]['content']) > 5 && in_array($tokens[$valueType]['content'], $formElements) === true
// Make sure that we don't check stuff in nested arrays within
// t() for example.
&& $valueNestedParenthesis === $nestedParenthesis
) {
// We need to make sure that the string is the one and only part
// of the array value.
$afterValue = $phpcsFile->findNext(T_WHITESPACE, ($arrayValue + 1), $statementEnd, true);
if ($tokens[$afterValue]['code'] === T_COMMA || $tokens[$afterValue]['code'] === T_CLOSE_PARENTHESIS) {
$warning = '#options values usually have to run through t() for translation';
// cspell:ignore TforValue
$phpcsFile->addWarning($warning, $arrayValue, 'TforValue');
}
}
$arrow = $phpcsFile->findNext(T_DOUBLE_ARROW, ($arrow + 1), $statementEnd, false, null, true);
}//end while
}//end process()
}//end class

View File

@@ -0,0 +1,101 @@
<?php
/**
* \DrupalPractice\Sniffs\General\VariableNameSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\General;
use PHP_CodeSniffer\Files\File;
use Drupal\Sniffs\Semantics\FunctionCall;
use DrupalPractice\Project;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks the usage of variable_get() in forms and the variable name.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class VariableNameSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['variable_get'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\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();
// We assume that the sequence '#default_value' => variable_get(...)
// indicates a variable that the module owns.
$arrow = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($arrow === false || $tokens[$arrow]['code'] !== T_DOUBLE_ARROW) {
return;
}
$arrayKey = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($arrow - 1), null, true);
if ($arrayKey === false
|| $tokens[$arrayKey]['code'] !== T_CONSTANT_ENCAPSED_STRING
|| substr($tokens[$arrayKey]['content'], 1, -1) !== '#default_value'
) {
return;
}
$argument = $this->getArgument(1);
// Variable name is not a literal string, so we return early.
if ($argument === false || $tokens[$argument['start']]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
return;
}
$moduleName = Project::getName($phpcsFile);
if ($moduleName === false) {
return;
}
$variableName = substr($tokens[$argument['start']]['content'], 1, -1);
if (strpos($variableName, $moduleName) !== 0) {
$warning = 'All variables defined by your module must be prefixed with your module\'s name to avoid name collisions with others. Expected start with "%s" but found "%s"';
$data = [
$moduleName,
$variableName,
];
$phpcsFile->addWarning($warning, $argument['start'], 'VariableName', $data);
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,92 @@
<?php
/**
* \DrupalPractice\Sniffs\InfoFiles\CoreVersionRequirementSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\InfoFiles;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Checks if the *.info.yml file contains core_version_requirement.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class CoreVersionRequirementSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_INLINE_HTML];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int
*/
public function process(File $phpcsFile, $stackPtr)
{
$filename = $phpcsFile->getFilename();
$fileExtension = strtolower(substr($filename, -9));
if ($fileExtension !== '.info.yml') {
return ($phpcsFile->numTokens + 1);
}
// Exclude config files which might contain the info.yml extension.
$filenameWithoutExtension = substr($filename, 0, -9);
if (strpos($filenameWithoutExtension, '.') !== false) {
return ($phpcsFile->numTokens + 1);
}
$contents = file_get_contents($phpcsFile->getFilename());
try {
$info = Yaml::parse($contents);
} catch (ParseException $e) {
// If the YAML is invalid we ignore this file.
return ($phpcsFile->numTokens + 1);
}
// Check if the type key is set, to verify if we're inside a project info.yml file.
if (isset($info['type']) === false) {
return ($phpcsFile->numTokens + 1);
}
// Test modules can omit the core_version_requirement key.
if (isset($info['package']) === true && $info['package'] === 'Testing') {
return ($phpcsFile->numTokens + 1);
}
if (isset($info['core_version_requirement']) === false) {
$warning = '"core_version_requirement" property is missing in the info.yml file';
$phpcsFile->addWarning($warning, $stackPtr, 'CoreVersionRequirement');
}
return ($phpcsFile->numTokens + 1);
}//end process()
}//end class

View File

@@ -0,0 +1,89 @@
<?php
/**
* \DrupalPractice\Sniffs\InfoFiles\DescriptionSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\InfoFiles;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Checks if the *.info.yml file contains a description.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DescriptionSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_INLINE_HTML];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int
*/
public function process(File $phpcsFile, $stackPtr)
{
$filename = $phpcsFile->getFilename();
$fileExtension = strtolower(substr($filename, -9));
if ($fileExtension !== '.info.yml') {
return ($phpcsFile->numTokens + 1);
}
// Exclude config files which might contain the info.yml extension.
$filenameWithoutExtension = substr($filename, 0, -9);
if (strpos($filenameWithoutExtension, '.') !== false) {
return ($phpcsFile->numTokens + 1);
}
try {
$info = Yaml::parseFile($phpcsFile->getFilename());
} catch (ParseException $e) {
// If the YAML is invalid we ignore this file.
return ($phpcsFile->numTokens + 1);
}
// Check if the type key is set, to verify if we're inside a project info.yml file.
if (isset($info['type']) === false) {
return ($phpcsFile->numTokens + 1);
}
if (isset($info['description']) === false) {
$warning = '"Description" property is missing in the info.yml file';
$phpcsFile->addWarning($warning, $stackPtr, 'Missing');
} else if ($info['description'] === '') {
$warning = '"Description" should not be empty';
$phpcsFile->addWarning($warning, $stackPtr, 'Empty');
}
return ($phpcsFile->numTokens + 1);
}//end process()
}//end class

View File

@@ -0,0 +1,96 @@
<?php
/**
* \DrupalPractice\Sniffs\InfoFiles\NamespacedDependencySniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\InfoFiles;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Checks that all declared dependencies are namespaced.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class NamespacedDependencySniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_INLINE_HTML];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int|void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -9));
if ($fileExtension !== '.info.yml') {
return ($phpcsFile->numTokens + 1);
}
$contents = file_get_contents($phpcsFile->getFilename());
try {
$info = Yaml::parse($contents);
// Themes are allowed to have not namespaced dependencies, see
// https://www.drupal.org/project/drupal/issues/474684.
if (isset($info['type']) === true && $info['type'] === 'theme') {
return ($phpcsFile->numTokens + 1);
}
} catch (ParseException $e) {
// If the YAML is invalid we ignore this file.
return ($phpcsFile->numTokens + 1);
}
if (preg_match('/^dependencies:/', $tokens[$stackPtr]['content']) === 0) {
return;
}
$nextLine = ($stackPtr + 1);
while (isset($tokens[$nextLine]) === true) {
// Dependency line without namespace.
if (preg_match('/^[\s]+- [^:]+[\s]*$/', $tokens[$nextLine]['content']) === 1) {
$error = 'All dependencies must be prefixed with the project name, for example "drupal:"';
$phpcsFile->addWarning($error, $nextLine, 'NonNamespaced');
} else if (preg_match('/^[\s]+- [^:]+:[^:]+[\s]*$/', $tokens[$nextLine]['content']) === 0
&& preg_match('/^[\s]*#.*$/', $tokens[$nextLine]['content']) === 0
) {
// Not a dependency line with namespace or comment - stop.
return $nextLine;
}
$nextLine++;
}
}//end process()
}//end class

View File

@@ -0,0 +1,221 @@
<?php
/**
* \DrupalPractice\Sniffs\Objects\GlobalClassSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Objects;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use DrupalPractice\Project;
/**
* Checks that Node::load() calls and friends are not used in forms, controllers or
* services.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GlobalClassSniff implements Sniff
{
/**
* Core class names that should not be called statically, mostly entity
* classes.
*
* @var string[]
*/
protected $coreClasses = [
'Drupal\Core\Datetime\Entity\DateFormat',
'Drupal\Core\Entity\Entity\EntityFormDisplay',
'Drupal\Core\Entity\Entity\EntityFormMode',
'Drupal\Core\Entity\Entity\EntityViewDisplay',
'Drupal\Core\Entity\Entity\EntityViewMode',
'Drupal\Core\Field\Entity\BaseFieldOverride',
'Drupal\aggregator\Entity\Feed',
'Drupal\aggregator\Entity\Item',
'Drupal\block\Entity\Block',
'Drupal\block_content\Entity\BlockContent',
'Drupal\block_content\Entity\BlockContentType',
'Drupal\comment\Entity\Comment',
'Drupal\comment\Entity\CommentType',
'Drupal\contact\Entity\ContactForm',
'Drupal\contact\Entity\Message',
'Drupal\content_moderation\Entity\ContentModerationState',
'Drupal\editor\Entity\Editor',
'Drupal\field\Entity\FieldConfig',
'Drupal\field\Entity\FieldStorageConfig',
'Drupal\file\Entity\File',
'Drupal\filter\Entity\FilterFormat',
'Drupal\image\Entity\ImageStyle',
'Drupal\language\Entity\ConfigurableLanguage',
'Drupal\language\Entity\ContentLanguageSettings',
'Drupal\media\Entity\Media',
'Drupal\media\Entity\MediaType',
'Drupal\menu_link_content\Entity\MenuLinkContent',
'Drupal\node\Entity\Node',
'Drupal\node\Entity\NodeType',
'Drupal\path_alias\Entity\PathAlias',
'Drupal\rdf\Entity\RdfMapping',
'Drupal\responsive_image\Entity\ResponsiveImageStyle',
'Drupal\rest\Entity\RestResourceConfig',
'Drupal\search\Entity\SearchPage',
'Drupal\shortcut\Entity\Shortcut',
'Drupal\shortcut\Entity\ShortcutSet',
'Drupal\system\Entity\Action',
'Drupal\system\Entity\Menu',
'Drupal\taxonomy\Entity\Term',
'Drupal\taxonomy\Entity\Vocabulary',
'Drupal\tour\Entity\Tour',
'Drupal\user\Entity\Role',
'Drupal\user\Entity\User',
'Drupal\views\Entity\View',
'Drupal\workflows\Entity\Workflow',
'Drupal\workspaces\Entity\Workspace',
];
/**
* Class names that should not be called statically.
*
* @var string[]
*/
public $classes = [];
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_STRING];
}//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|int
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// We are only interested in static class method calls, not in the global
// scope.
if ($tokens[($stackPtr + 1)]['code'] !== T_DOUBLE_COLON
|| isset($tokens[($stackPtr + 2)]) === false
|| $tokens[($stackPtr + 2)]['code'] !== T_STRING
|| in_array($tokens[($stackPtr + 2)]['content'], ['load', 'loadMultiple']) === false
|| isset($tokens[($stackPtr + 3)]) === false
|| $tokens[($stackPtr + 3)]['code'] !== T_OPEN_PARENTHESIS
|| empty($tokens[$stackPtr]['conditions']) === true
) {
return;
}
// Check that this statement is not in a static function.
foreach ($tokens[$stackPtr]['conditions'] as $conditionPtr => $conditionCode) {
if ($conditionCode === T_FUNCTION && $phpcsFile->getMethodProperties($conditionPtr)['is_static'] === true) {
return;
}
}
$fullName = $this->getFullyQualifiedName($phpcsFile, $tokens[$stackPtr]['content']);
if (in_array($fullName, $this->coreClasses) === false && in_array($fullName, $this->classes) === false) {
return;
}
// Check if the class extends another class and get the name of the class
// that is extended.
$classPtr = key($tokens[$stackPtr]['conditions']);
$extendsName = $phpcsFile->findExtendedClassName($classPtr);
// Check if the class implements a container injection interface.
$containerInterfaces = [
'ContainerInjectionInterface',
'ContainerFactoryPluginInterface',
'ContainerDeriverInterface',
];
$implementedInterfaceNames = $phpcsFile->findImplementedInterfaceNames($classPtr);
$canAccessContainer = !empty($implementedInterfaceNames) && !empty(array_intersect($containerInterfaces, $implementedInterfaceNames));
if (($extendsName === false
|| in_array($extendsName, GlobalDrupalSniff::$baseClasses) === false)
&& Project::isServiceClass($phpcsFile, $classPtr) === false
&& $canAccessContainer === false
) {
return ($phpcsFile->numTokens + 1);
}
$warning = '%s::%s calls should be avoided in classes, use dependency injection instead';
$data = [
$tokens[$stackPtr]['content'],
$tokens[($stackPtr + 2)]['content'],
];
$phpcsFile->addWarning($warning, $stackPtr, 'GlobalClass', $data);
}//end process()
/**
* Retrieve the fully qualified name of the given classname.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param string $className The classname for which to retrieve the FQN.
*
* @return string
*/
protected function getFullyQualifiedName(File $phpcsFile, $className)
{
$useStatement = $phpcsFile->findNext(T_USE, 0);
while ($useStatement !== false) {
$endPtr = $phpcsFile->findEndOfStatement($useStatement);
$useEnd = ($phpcsFile->findNext([T_STRING, T_NS_SEPARATOR, T_WHITESPACE], ($useStatement + 1), null, true) - 1);
$useFullName = trim($phpcsFile->getTokensAsString(($useStatement + 1), ($useEnd - $useStatement)), '\\ ');
// Check if use statement contains an alias.
$asPtr = $phpcsFile->findNext(T_AS, ($useEnd + 1), $endPtr);
if ($asPtr !== false) {
$aliasName = trim($phpcsFile->getTokensAsString(($asPtr + 1), ($endPtr - 1 - $asPtr)));
if ($aliasName === $className) {
return $useFullName;
}
}
$parts = explode('\\', $useFullName);
$useClassName = end($parts);
// Check if the resulting classname is the classname we're looking
// for.
if ($useClassName === $className) {
return $useFullName;
}
// Check if we're currently in a multi-use statement.
$tokens = $phpcsFile->getTokens();
if ($tokens[$endPtr]['code'] === T_COMMA) {
$useStatement = $endPtr;
continue;
}
$useStatement = $phpcsFile->findNext(T_USE, ($useStatement + 1));
}//end while
return $className;
}//end getFullyQualifiedName()
}//end class

View File

@@ -0,0 +1,114 @@
<?php
/**
* \DrupalPractice\Sniffs\Objects\GlobalDrupalSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Objects;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use DrupalPractice\Project;
/**
* Checks that \Drupal::service() and friends is not used in forms, controllers, services.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GlobalDrupalSniff implements Sniff
{
/**
* List of base classes where \Drupal should not be used in an extending class.
*
* @var string[]
*/
public static $baseClasses = [
'BlockBase',
'ConfigFormBase',
'ContentEntityForm',
'ControllerBase',
'EntityForm',
'EntityReferenceFormatterBase',
'FileFormatterBase',
'FormatterBase',
'FormBase',
'ImageFormatter',
'ImageFormatterBase',
'WidgetBase',
];
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_STRING];
}//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();
// We are only interested in Drupal:: static method calls, not in the global
// scope.
if ($tokens[$stackPtr]['content'] !== 'Drupal'
|| $tokens[($stackPtr + 1)]['code'] !== T_DOUBLE_COLON
|| isset($tokens[($stackPtr + 2)]) === false
|| $tokens[($stackPtr + 2)]['code'] !== T_STRING
|| isset($tokens[($stackPtr + 3)]) === false
|| $tokens[($stackPtr + 3)]['code'] !== T_OPEN_PARENTHESIS
|| empty($tokens[$stackPtr]['conditions']) === true
) {
return;
}
// Check that this statement is not in a static function.
foreach ($tokens[$stackPtr]['conditions'] as $conditionPtr => $conditionCode) {
if ($conditionCode === T_FUNCTION && $phpcsFile->getMethodProperties($conditionPtr)['is_static'] === true) {
return;
}
}
// Check if the class extends another class and get the name of the class
// that is extended.
$classPtr = key($tokens[$stackPtr]['conditions']);
$extendsName = $phpcsFile->findExtendedClassName($classPtr);
// Check if the class implements ContainerInjectionInterface.
$implementedInterfaceNames = $phpcsFile->findImplementedInterfaceNames($classPtr);
$canAccessContainer = !empty($implementedInterfaceNames) && in_array('ContainerInjectionInterface', $implementedInterfaceNames);
if (($extendsName === false || in_array($extendsName, static::$baseClasses) === false)
&& Project::isServiceClass($phpcsFile, $classPtr) === false
&& $canAccessContainer === false
) {
return;
}
$warning = '\Drupal calls should be avoided in classes, use dependency injection instead';
$phpcsFile->addWarning($warning, $stackPtr, 'GlobalDrupal');
}//end process()
}//end class

View File

@@ -0,0 +1,135 @@
<?php
/**
* \DrupalPractice\Sniffs\Objects\GlobalFunctionSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Objects;
use PHP_CodeSniffer\Files\File;
use DrupalPractice\Project;
use Drupal\Sniffs\Semantics\FunctionCall;
/**
* Checks that global functions like t() are not used in forms or controllers.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GlobalFunctionSniff extends FunctionCall
{
/**
* List of global functions that should not be called.
*
* @var string[]
*/
protected $functions = [
'drupal_get_destination' => 'the "redirect.destination" service',
'drupal_render' => 'the "renderer" service',
'entity_load' => 'the "entity_type.manager" service',
'file_load' => 'the "entity_type.manager" service',
'format_date' => 'the "date.formatter" service',
'node_load' => 'the "entity_type.manager" service',
'node_load_multiple' => 'the "entity_type.manager" service',
'node_type_load' => 'the "entity_type.manager" service',
't' => '$this->t()',
'taxonomy_term_load' => 'the "entity_type.manager" service',
'taxonomy_vocabulary_load' => 'the "entity_type.manager" service',
'user_load' => 'the "entity_type.manager" service',
'user_role_load' => 'the "entity_type.manager" service',
];
/**
* List of global functions that are covered by traits.
*
* This is a subset of the global functions list. These functions can be
* replaced by methods that are provided by the listed trait.
*
* @var string[]
*/
protected $traitFunctions = ['t' => '\Drupal\Core\StringTranslation\StringTranslationTrait'];
/**
* 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|int
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Only run this sniff on Drupal 8+.
if (Project::getCoreVersion($phpcsFile) < 8) {
// No need to check this file again, mark it as done.
return ($phpcsFile->numTokens + 1);
}
// We just want to listen on function calls, nothing else.
if ($this->isFunctionCall($phpcsFile, $stackPtr) === false
// Ignore function calls in the global scope.
|| empty($tokens[$stackPtr]['conditions']) === true
// Only our list of function names.
|| isset($this->functions[$tokens[$stackPtr]['content']]) === false
) {
return;
}
// Check that this statement is not in a static function.
foreach ($tokens[$stackPtr]['conditions'] as $conditionPtr => $conditionCode) {
if ($conditionCode === T_FUNCTION && $phpcsFile->getMethodProperties($conditionPtr)['is_static'] === true) {
return;
}
}
// Check if the class extends another class and get the name of the class
// that is extended.
$classPtr = key($tokens[$stackPtr]['conditions']);
if ($tokens[$classPtr]['code'] !== T_CLASS) {
return;
}
if (isset($this->traitFunctions[$tokens[$stackPtr]['content']]) === false) {
$extendsName = $phpcsFile->findExtendedClassName($classPtr);
// Check if the class implements ContainerInjectionInterface.
$implementedInterfaceNames = $phpcsFile->findImplementedInterfaceNames($classPtr);
$canAccessContainer = !empty($implementedInterfaceNames) && in_array('ContainerInjectionInterface', $implementedInterfaceNames);
if (($extendsName === false
|| in_array($extendsName, GlobalDrupalSniff::$baseClasses) === false)
&& Project::isServiceClass($phpcsFile, $classPtr) === false
&& $canAccessContainer === false
) {
return;
}
$warning = '%s() calls should be avoided in classes, use dependency injection and %s instead';
$data = [
$tokens[$stackPtr]['content'],
$this->functions[$tokens[$stackPtr]['content']],
];
} else {
$warning = '%s() calls should be avoided in classes, use %s and %s instead';
$data = [
$tokens[$stackPtr]['content'],
$this->traitFunctions[$tokens[$stackPtr]['content']],
$this->functions[$tokens[$stackPtr]['content']],
];
}//end if
$phpcsFile->addWarning($warning, $stackPtr, 'GlobalFunction', $data);
}//end process()
}//end class

View File

@@ -0,0 +1,130 @@
<?php
/**
* DrupalPractice_Sniffs_Objects_StrictSchemaDisabledSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Objects;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
/**
* Checks that $strictConfigSchema is not set to FALSE in test classes.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class StrictSchemaDisabledSniff extends AbstractVariableSniff
{
/**
* The name of the variable in the test base class to disable config schema checking.
*/
const STRICT_CONFIG_SCHEMA_NAME = '$strictConfigSchema';
/**
* 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 processMemberVar(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (($tokens[$stackPtr]['content'] === static::STRICT_CONFIG_SCHEMA_NAME) && ($this->isTestClass($phpcsFile, $stackPtr) === true)) {
$find = [
T_FALSE,
T_TRUE,
T_NULL,
T_SEMICOLON,
];
$next = $phpcsFile->findNext($find, ($stackPtr + 1));
// If this variable is being set, the only allowed value is TRUE.
// Otherwise if FALSE or NULL, schema checking is disabled.
if ($tokens[$next]['code'] !== T_TRUE) {
$error = 'Do not disable strict config schema checking in tests. Instead ensure your module properly declares its schema for configurations.';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addError(
$error,
$stackPtr,
'StrictConfigSchema',
$data
);
}
}//end if
}//end processMemberVar()
/**
* Determine if this class is a test class.
*
* @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 bool
* Returns TRUE if the current class is a test class.
*/
protected function isTestClass(File $phpcsFile, $stackPtr)
{
// Only applies to test classes, which have Test in the name.
$tokens = $phpcsFile->getTokens();
$classPtr = key($tokens[$stackPtr]['conditions']);
$name = $phpcsFile->findNext([T_STRING], $classPtr);
return strpos($tokens[$name]['content'], 'Test') !== false;
}//end isTestClass()
/**
* Called to process normal member vars.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position 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.
*/
protected function processVariable(File $phpcsFile, $stackPtr)
{
}//end processVariable()
/**
* Called to process variables found in double quoted strings or heredocs.
*
* Note that there may be more than one variable in the string, which will
* result only in one call for the string or one call per line for heredocs.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this
* token was found.
* @param int $stackPtr The position where the double quoted
* string 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.
*/
protected function processVariableInString(File $phpcsFile, $stackPtr)
{
}//end processVariableInString()
}//end class

View File

@@ -0,0 +1,127 @@
<?php
/**
* \DrupalPractice\Sniffs\Objects\UnusedPrivateMethodSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Objects;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that private methods are actually used in a class.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class UnusedPrivateMethodSniff extends AbstractScopeSniff
{
/**
* Constructor.
*/
public function __construct()
{
parent::__construct([T_CLASS], [T_FUNCTION], false);
}//end __construct()
/**
* Processes the tokens within the scope.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
* @param int $stackPtr The position where this token was
* found.
* @param int $currScope The position of the current scope.
*
* @return void
*/
protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
{
// Only check private methods.
$methodProperties = $phpcsFile->getMethodProperties($stackPtr);
if ($methodProperties['scope'] !== 'private' || $methodProperties['is_static'] === true) {
return;
}
$tokens = $phpcsFile->getTokens();
$methodName = $phpcsFile->getDeclarationName($stackPtr);
if ($methodName === '__construct') {
return;
}
$classPtr = key($tokens[$stackPtr]['conditions']);
// Search for direct $this->methodCall() or indirect callbacks [$this,
// 'methodCall'].
$current = $tokens[$classPtr]['scope_opener'];
$end = $tokens[$classPtr]['scope_closer'];
while (($current = $phpcsFile->findNext(T_VARIABLE, ($current + 1), $end)) !== false) {
if ($tokens[$current]['content'] !== '$this') {
continue;
}
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), null, true);
if ($next === false) {
continue;
}
if ($tokens[$next]['code'] === T_OBJECT_OPERATOR) {
$call = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true);
// PHP method calls are case insensitive.
if ($call === false || strcasecmp($tokens[$call]['content'], $methodName) !== 0) {
continue;
}
$parenthesis = $phpcsFile->findNext(Tokens::$emptyTokens, ($call + 1), null, true);
if ($parenthesis === false || $tokens[$parenthesis]['code'] !== T_OPEN_PARENTHESIS) {
continue;
}
// At this point this is a method call to the private method, so we
// can stop.
return;
} else if ($tokens[$next]['code'] === T_COMMA) {
$call = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true);
if ($call === false || substr($tokens[$call]['content'], 1, -1) !== $methodName) {
continue;
}
// At this point this is likely the private method as callback on a
// function such as array_filter().
return;
}//end if
}//end while
$warning = 'Unused private method %s()';
$data = [$methodName];
$phpcsFile->addWarning($warning, $stackPtr, 'UnusedMethod', $data);
}//end processTokenWithinScope()
/**
* Process tokens outside of scope.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being processed.
* @param int $stackPtr The position where this token was
* found.
*
* @return void
*/
protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
{
}//end processTokenOutsideScope()
}//end class

View File

@@ -0,0 +1,107 @@
<?php
/**
* \DrupalPractice\Sniffs\Variables\GetRequestDataSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Variables;
use DrupalPractice\Project;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Ensures that Symfony request object is used to access super globals.
*
* Inspired by GetRequestDataSniff.php from Squiz Labs.
*
* @see https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/MySource/Sniffs/PHP/GetRequestDataSniff.php
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class GetRequestDataSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_VARIABLE];
}//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|int
*/
public function process(File $phpcsFile, $stackPtr)
{
if (Project::getCoreVersion($phpcsFile) < 8) {
// No need to check this file again, mark it as done.
return ($phpcsFile->numTokens + 1);
}
$tokens = $phpcsFile->getTokens();
$varName = $tokens[$stackPtr]['content'];
if ($varName !== '$_REQUEST'
&& $varName !== '$_GET'
&& $varName !== '$_POST'
&& $varName !== '$_FILES'
&& $varName !== '$_COOKIE'
) {
return;
}
// If we get to here, the super global was used incorrectly.
// First find out how it is being used.
$usedVar = '';
$openBracket = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
if ($tokens[$openBracket]['code'] === T_OPEN_SQUARE_BRACKET) {
$closeBracket = $tokens[$openBracket]['bracket_closer'];
$usedVar = $phpcsFile->getTokensAsString(($openBracket + 1), ($closeBracket - $openBracket - 1));
}
$requestPropertyMap = [
'$_REQUEST' => 'request',
'$_GET' => 'query',
'$_POST' => 'request',
'$_FILES' => 'files',
'$_COOKIE' => 'cookies',
];
$type = 'SuperglobalAccessed';
$error = 'The %s super global must not be accessed directly; inject the request_stack service and use $stack->getCurrentRequest()->%s';
$data = [
$varName,
$requestPropertyMap[$varName],
];
if ($usedVar !== '') {
$type .= 'WithVar';
$error .= '->get(%s)';
$data[] = $usedVar;
}
$error .= ' instead';
$phpcsFile->addError($error, $stackPtr, $type, $data);
}//end process()
}//end class

View File

@@ -0,0 +1,75 @@
<?php
/**
* \DrupalPractice\Sniffs\Yaml\RoutingAccessSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace DrupalPractice\Sniffs\Yaml;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that there are no undocumented open access callbacks in *.routing.yml files.
*
* Also adds a warning if the permission "access administration pages" is used.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class RoutingAccessSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_INLINE_HTML];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void|int
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -12));
if ($fileExtension !== '.routing.yml') {
return ($phpcsFile->numTokens + 1);
}
if (preg_match('/^[\s]+_access: \'TRUE\'/', $tokens[$stackPtr]['content']) === 1
&& isset($tokens[($stackPtr - 1)]) === true
&& preg_match('/^[\s]*#/', $tokens[($stackPtr - 1)]['content']) === 0
) {
$warning = 'Open page callback found, please add a comment before the line why there is no access restriction';
$phpcsFile->addWarning($warning, $stackPtr, 'OpenCallback');
}
if (preg_match('/^[\s]+_permission: \'access administration pages\'/', $tokens[$stackPtr]['content']) === 1) {
$warning = 'The administration page callback should probably use "administer site configuration" - which implies the user can change something - rather than "access administration pages" which is about viewing but not changing configurations.';
$phpcsFile->addWarning($warning, $stackPtr, 'PermissionFound');
}
}//end process()
}//end class

View File

@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<ruleset name="DrupalPractice">
<description>Drupal best practice checks</description>
<!-- All Drupal code files must be UTF-8 encoded and we treat them as such. -->
<arg name="encoding" value="utf-8"/>
<autoload>../Drupal/coder_unique_autoload_phpcs_bug_2751.php</autoload>
<rule ref="Internal.NoCodeFound">
<!-- Empty files are fine, might be used for testing. -->
<exclude-pattern>*</exclude-pattern>
</rule>
<rule ref="DrupalPractice.CodeAnalysis.VariableAnalysis">
<!-- Do not run this sniff on template files. -->
<exclude-pattern>*.tpl.php</exclude-pattern>
<properties>
<property name="allowUnusedFunctionParameters" value="true"/>
</properties>
</rule>
<rule ref="DrupalPractice.CodeAnalysis.VariableAnalysis.UndefinedVariable">
<severity>0</severity>
</rule>
<!-- Ignore various version control directories. -->
<exclude-pattern>*/\.git/*</exclude-pattern>
<exclude-pattern>*/\.svn/*</exclude-pattern>
<exclude-pattern>*/\.hg/*</exclude-pattern>
<exclude-pattern>*/\.bzr/*</exclude-pattern>
</ruleset>