Skip to content

Commit

Permalink
Merge pull request #765 from frankdekker/class-name-length-rule
Browse files Browse the repository at this point in the history
Class name length rule
  • Loading branch information
kylekatarnls committed Jul 22, 2020
2 parents e90ce77 + b34aba2 commit 2823caf
Show file tree
Hide file tree
Showing 23 changed files with 967 additions and 44 deletions.
70 changes: 70 additions & 0 deletions src/main/php/PHPMD/Rule/Naming/LongClassName.php
@@ -0,0 +1,70 @@
<?php
/**
* This file is part of PHP Mess Detector.
*
* Copyright (c) Manuel Pichler <mapi@phpmd.org>.
* All rights reserved.
*
* Licensed under BSD License
* For full copyright and license information, please see the LICENSE file.
* Redistributions of files must retain the above copyright notice.
*
* @author Manuel Pichler <mapi@phpmd.org>
* @copyright Manuel Pichler. All rights reserved.
* @license https://opensource.org/licenses/bsd-license.php BSD License
* @link http://phpmd.org/
*/

namespace PHPMD\Rule\Naming;

use PHPMD\AbstractNode;
use PHPMD\AbstractRule;
use PHPMD\Rule\ClassAware;
use PHPMD\Rule\InterfaceAware;
use PHPMD\Utility\Strings;

/**
* This rule checks if an interface or class name exceeds the configured length excluding certain configured suffixes
*/
class LongClassName extends AbstractRule implements ClassAware, InterfaceAware
{
/**
* Temporary cache of configured suffixes to subtract
*
* @var string[]|null
*/
private $subtractSuffixes;

/**
* Check if a class name exceeds the configured maximum length and emit a rule violation
*
* @param \PHPMD\AbstractNode $node
* @return void
*/
public function apply(AbstractNode $node)
{
$threshold = $this->getIntProperty('maximum');
$classOrInterfaceName = $node->getName();
if (Strings::lengthWithoutSuffixes($classOrInterfaceName, $this->getSubtractSuffixList()) <= $threshold) {
return;
}
$this->addViolation($node, array($classOrInterfaceName, $threshold));
}

/**
* Gets array of suffixes from property
*
* @return string[]
*/
private function getSubtractSuffixList()
{
if ($this->subtractSuffixes === null) {
$this->subtractSuffixes = Strings::splitToList(
$this->getStringProperty('subtract-suffixes', ''),
','
);
}

return $this->subtractSuffixes;
}
}
51 changes: 9 additions & 42 deletions src/main/php/PHPMD/Rule/Naming/LongVariable.php
Expand Up @@ -22,6 +22,7 @@
use PHPMD\Rule\ClassAware;
use PHPMD\Rule\FunctionAware;
use PHPMD\Rule\MethodAware;
use PHPMD\Utility\Strings;

/**
* This rule class will detect variables, parameters and properties with really
Expand Down Expand Up @@ -101,17 +102,20 @@ protected function checkNodeImage(AbstractNode $node)
*
* @param \PHPMD\AbstractNode $node
* @return void
* @SuppressWarnings(PHPMD.LongVariable)
*/
protected function checkMaximumLength(AbstractNode $node)
{
$threshold = $this->getIntProperty('maximum');
if ($threshold >= $this->getStringLength($node->getImage(), $this->getSubtractSuffixList()) - 1) {
$variableName = $node->getImage();
$lengthWithoutDollarSign = Strings::lengthWithoutSuffixes($variableName, $this->getSubtractSuffixList()) - 1;
if ($lengthWithoutDollarSign <= $threshold) {
return;
}
if ($this->isNameAllowedInContext($node)) {
return;
}
$this->addViolation($node, array($node->getImage(), $threshold));
$this->addViolation($node, array($variableName, $threshold));
}

/**
Expand All @@ -126,29 +130,6 @@ private function isNameAllowedInContext(AbstractNode $node)
return $this->isChildOf($node, 'MemberPrimaryPrefix');
}

/**
* Returns the length of the variable name, excluding at most one suffix.
*
* @param string $variableName Variable name to calculate the length for.
* @param array $subtractSuffixes Optional list of suffixes to exclude from the calculated length.
* @return int The length of the string, without suffix, if applicable.
*/
private function getStringLength($variableName, array $subtractSuffixes)
{
$variableNameLength = strlen($variableName);

foreach ($subtractSuffixes as $suffix) {
$suffixLength = strlen($suffix);
if (substr($variableName, -$suffixLength) === $suffix) {
$variableName = substr($variableName, 0, $variableNameLength - $suffixLength);

return strlen($variableName);
}
}

return $variableNameLength;
}

/**
* Checks if the given node is a direct or indirect child of a node with
* the given type.
Expand Down Expand Up @@ -209,24 +190,10 @@ protected function isNotProcessed(AbstractNode $node)
*/
private function getSubtractSuffixList()
{
if ($this->subtractSuffixes !== null) {
return $this->subtractSuffixes;
if ($this->subtractSuffixes === null) {
$this->subtractSuffixes = Strings::splitToList($this->getStringProperty('subtract-suffixes', ''), ',');
}

try {
$suffixes = $this->getStringProperty('subtract-suffixes');
} catch (\OutOfBoundsException $e) {
return $this->subtractSuffixes = array();
}

return $this->subtractSuffixes = array_filter(
array_map(
'trim',
explode(',', $suffixes)
),
function ($value) {
return $value !== '';
}
);
return $this->subtractSuffixes;
}
}
75 changes: 75 additions & 0 deletions src/main/php/PHPMD/Rule/Naming/ShortClassName.php
@@ -0,0 +1,75 @@
<?php
/**
* This file is part of PHP Mess Detector.
*
* Copyright (c) Manuel Pichler <mapi@phpmd.org>.
* All rights reserved.
*
* Licensed under BSD License
* For full copyright and license information, please see the LICENSE file.
* Redistributions of files must retain the above copyright notice.
*
* @author Manuel Pichler <mapi@phpmd.org>
* @copyright Manuel Pichler. All rights reserved.
* @license https://opensource.org/licenses/bsd-license.php BSD License
* @link http://phpmd.org/
*/

namespace PHPMD\Rule\Naming;

use PHPMD\AbstractNode;
use PHPMD\AbstractRule;
use PHPMD\Rule\ClassAware;
use PHPMD\Rule\InterfaceAware;
use PHPMD\Utility\Strings;

/**
* This rule will detect classes and interfaces with names that are too short.
*/
class ShortClassName extends AbstractRule implements ClassAware, InterfaceAware
{
/**
* Temporary cache of configured exceptions. Have name as key
*
* @var array<string, int>|null
*/
private $exceptions;

/**
* Check if a class or interface name is below the minimum configured length and emit a rule violation
*
* @param \PHPMD\AbstractNode $node
* @return void
*/
public function apply(AbstractNode $node)
{
$threshold = $this->getIntProperty('minimum');
$classOrInterfaceName = $node->getName();
if (strlen($classOrInterfaceName) >= $threshold) {
return;
}

$exceptions = $this->getExceptionsList();
if (isset($exceptions[$classOrInterfaceName])) {
return;
}

$this->addViolation($node, array($classOrInterfaceName, $threshold));
}

/**
* Gets array of exceptions from property
*
* @return array<string, int>
*/
private function getExceptionsList()
{
if ($this->exceptions === null) {
$this->exceptions = array_flip(
Strings::splitToList($this->getStringProperty('exceptions', ''), ',')
);
}

return $this->exceptions;
}
}
70 changes: 70 additions & 0 deletions src/main/php/PHPMD/Utility/Strings.php
@@ -0,0 +1,70 @@
<?php
/**
* This file is part of PHP Mess Detector.
*
* Copyright (c) Manuel Pichler <mapi@phpmd.org>.
* All rights reserved.
*
* Licensed under BSD License
* For full copyright and license information, please see the LICENSE file.
* Redistributions of files must retain the above copyright notice.
*
* @author Manuel Pichler <mapi@phpmd.org>
* @copyright Manuel Pichler. All rights reserved.
* @license https://opensource.org/licenses/bsd-license.php BSD License
* @link http://phpmd.org/
*/

namespace PHPMD\Utility;

use InvalidArgumentException;

/**
* Utility class to provide string checks and manipulations
*/
class Strings
{
/**
* Returns the length of the given string, excluding at most one suffix
*
* @param string $stringName String to calculate the length for.
* @param array $subtractSuffixes List of suffixes to exclude from the calculated length.
* @return int The length of the string, without suffix, if applicable.
*/
public static function lengthWithoutSuffixes($stringName, array $subtractSuffixes)
{
$stringLength = strlen($stringName);

foreach ($subtractSuffixes as $suffix) {
$suffixLength = strlen($suffix);
if (substr($stringName, -$suffixLength) === $suffix) {
$stringLength -= $suffixLength;
break;
}
}

return $stringLength;
}

/**
* Split a string with the given separator, trim whitespaces around the parts and remove any empty strings
*
* @param string $listAsString The string to split.
* @param string $separator The separator to split the string with, similar to explode.
* @return array The list of trimmed and filtered parts of the string.
* @throws InvalidArgumentException When the separator is an empty string.
*/
public static function splitToList($listAsString, $separator)
{
if ($separator === '') {
throw new InvalidArgumentException("Separator can't be empty string");
}

return array_filter(
array_map('trim', explode($separator, $listAsString)),
function ($value) {
return $value !== '';
}
);
}
}
52 changes: 52 additions & 0 deletions src/main/resources/rulesets/naming.xml
Expand Up @@ -8,6 +8,58 @@
The Naming Ruleset contains a collection of rules about names - too long, too short, and so forth.
</description>

<rule name="LongClassName"
since="2.9"
message="Avoid excessively long class names like {0}. Keep class name length under {1}."
class="PHPMD\Rule\Naming\LongClassName"
externalInfoUrl="https://phpmd.org/rules/naming.html#longclassname">
<description>
Detects when classes or interfaces are declared with excessively long names.
</description>
<priority>3</priority>
<properties>
<property name="maximum" description="The class name length reporting threshold" value="40"/>
<property name="subtract-suffixes" description="Comma-separated list of suffixes that will not count in the length of the class name. Only the first matching suffix will be subtracted." value=""/>
</properties>
<example>
<![CDATA[
class ATooLongClassNameThatHintsAtADesignProblem {
}
interface ATooLongInterfaceNameThatHintsAtADesignProblem {
}
]]>
</example>
</rule>

<rule name="ShortClassName"
since="2.9"
message="Avoid classes with short names like {0}. Configured minimum length is {1}."
class="PHPMD\Rule\Naming\ShortClassName"
externalInfoUrl="https://phpmd.org/rules/naming.html#shortclassname">
<description>
Detects when classes or interfaces have a very short name.
</description>
<priority>3</priority>
<properties>
<property name="minimum" description="The class name length reporting threshold" value="3"/>
<property name="exceptions" description="Comma-separated list of exceptions. Example: Log,URL,FTP" value=""/>
</properties>
<example>
<![CDATA[
class Fo {
}
interface Fo {
}
]]>
</example>
</rule>

<rule name="ShortVariable"
since="0.2"
message="Avoid variables with short names like {0}. Configured minimum length is {1}."
Expand Down
2 changes: 2 additions & 0 deletions src/site/rst/rules/index.rst
Expand Up @@ -64,6 +64,8 @@ many bugs, especially when the loop manipulates an array, as count happens on ea
Naming Rules
============

- `LongClassName <naming.html#longclassname>`_: Detects when classes or interfaces are declared with excessively long names.
- `ShortClassName <naming.html#shortclassname>`_: Detects when classes or interfaces have a very short name.
- `ShortVariable <naming.html#shortvariable>`_: Detects when a field, local, or parameter has a very short name.
- `LongVariable <naming.html#longvariable>`_: Detects when a field, formal or local variable is declared with a long name.
- `ShortMethodName <naming.html#shortmethodname>`_: Detects when very short method names are used.
Expand Down

0 comments on commit 2823caf

Please sign in to comment.