Skip to content

Implement rule from #105 #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions Magento2/Sniffs/Commenting/ClassAndInterfacePHPDocFormattingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/**
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento2\Sniffs\Commenting;

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

/**
* Detects PHPDoc formatting for classes and interfaces.
*/
class ClassAndInterfacePHPDocFormattingSniff implements Sniff
{
/**
* @var PHPDocFormattingValidator
*/
private $PHPDocFormattingValidator;

/**
* @var string[] List of tags that can not be used in comments
*/
public $forbiddenTags = [
'@category',
'@package',
'@subpackage'
];

/**
* Helper initialisation
*/
public function __construct()
{
$this->PHPDocFormattingValidator = new PHPDocFormattingValidator();
}

/**
* @inheritDoc
*/
public function register()
{
return [
T_CLASS,
T_INTERFACE
];
}

/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

$namePtr = $phpcsFile->findNext(T_STRING, $stackPtr + 1, null, false, null, true);

$commentStartPtr = $phpcsFile->findPrevious(
[
T_WHITESPACE,
T_DOC_COMMENT_STAR,
T_DOC_COMMENT_WHITESPACE,
T_DOC_COMMENT_TAG,
T_DOC_COMMENT_STRING,
T_DOC_COMMENT_CLOSE_TAG
],
$stackPtr - 1,
null,
true,
null,
true
);

if ($tokens[$commentStartPtr]['code'] !== T_DOC_COMMENT_OPEN_TAG) {
return;
}

if ($this->PHPDocFormattingValidator->providesMeaning($namePtr, $commentStartPtr, $tokens) !== true) {
$phpcsFile->addWarning(
sprintf(
'%s description should contain additional information beyond the name already supplies.',
ucfirst($tokens[$stackPtr]['content'])
),
$stackPtr,
'InvalidDescription'
);
}

$this->validateTags($phpcsFile, $commentStartPtr, $tokens);
}

/**
* Validates that forbidden tags are not used in comment
*
* @param File $phpcsFile
* @param int $commentStartPtr
* @param array $tokens
* @return bool
*/
private function validateTags(File $phpcsFile, $commentStartPtr, $tokens)
{
$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];

for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
continue;
}

if (in_array($tokens[$i]['content'], $this->forbiddenTags) === true) {
$phpcsFile->addWarning(
sprintf('Tag %s MUST NOT be used.', $tokens[$i]['content']),
$i,
'ForbiddenTags'
);
}
}

return false;
}
}
49 changes: 19 additions & 30 deletions Magento2/Sniffs/Commenting/ConstantsPHPDocFormattingSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
*/
class ConstantsPHPDocFormattingSniff implements Sniff
{
/**
* @var PHPDocFormattingValidator
*/
private $PHPDocFormattingValidator;

/**
* Helper initialisation
*/
public function __construct()
{
$this->PHPDocFormattingValidator = new PHPDocFormattingValidator();
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -45,42 +58,18 @@ public function process(File $phpcsFile, $stackPtr)
null,
true
);
$constName = strtolower(trim($tokens[$constNamePtr]['content'], " '\""));

$commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, null, false, null, true);
if ($commentStartPtr === false) {
return;
}

$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];
for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
$token = $tokens[$i];

// Not an interesting string
if ($token['code'] !== T_DOC_COMMENT_STRING) {
continue;
}

// Comment is the same as constant name
$docComment = trim(strtolower($token['content']), ',.');
if ($docComment === $constName) {
continue;
}

// Comment is exactly the same as constant name
$docComment = str_replace(' ', '_', $docComment);
if ($docComment === $constName) {
continue;
}

// We have found at lease one meaningful line in comment description
return;
if ($this->PHPDocFormattingValidator->providesMeaning($constNamePtr, $commentStartPtr, $tokens) !== true) {
$phpcsFile->addWarning(
'Constants must have short description if they add information beyond what the constant name supplies.',
$stackPtr,
'MissingConstantPHPDoc'
);
}

$phpcsFile->addWarning(
'Constants must have short description if they add information beyond what the constant name supplies.',
$stackPtr,
'MissingConstantPHPDoc'
);
}
}
74 changes: 74 additions & 0 deletions Magento2/Sniffs/Commenting/PHPDocFormattingValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

/**
* Copyright © Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento2\Sniffs\Commenting;

/**
* Helper class for common DocBlock validations
*/
class PHPDocFormattingValidator
{
/**
* Determines if the comment identified by $commentStartPtr provides additional meaning to origin at $namePtr
*
* @param int $namePtr
* @param int $commentStartPtr
* @param array $tokens
* @return bool
*/
public function providesMeaning($namePtr, $commentStartPtr, $tokens)
{
$commentCloserPtr = $tokens[$commentStartPtr]['comment_closer'];
$name = strtolower(str_replace([' ', '"', '_'], '', $tokens[$namePtr]['content']));

$hasTags = false;
$hasDescription = false;

for ($i = $commentStartPtr; $i <= $commentCloserPtr; $i++) {
$token = $tokens[$i];

// Important, but not the string we are looking for
if ($token['code'] === T_DOC_COMMENT_TAG) {
$hasTags = true;
continue;
}

// Not an interesting string
if ($token['code'] !== T_DOC_COMMENT_STRING) {
continue;
}

// Wrong kind of string
if ($tokens[$i - 2]['code'] === T_DOC_COMMENT_TAG) {
continue;
}

$hasDescription = true;

// Comment is the same as the origin name
$docComment = str_replace(['_', ' ', '.', ','], '', strtolower($token['content']));
if ($docComment === $name) {
continue;
}

// Only difference is word Class or Interface
$docComment = str_replace(['class', 'interface'], '', $docComment);
if ($docComment === $name) {
continue;
}

// We have found at lease one meaningful line in comment description
return true;
}

// Contains nothing but the tags
if ($hasTags === true && $hasDescription === false) {
return true;
}

return false;
}
}
3 changes: 0 additions & 3 deletions Magento2/Tests/CodeAnalysis/EmptyBlockUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Class EmptyBlockUnitTest
*/
class EmptyBlockUnitTest extends AbstractSniffUnitTest
{
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/**
* Handler for PHP errors/warnings/notices that converts them to exceptions.
*/
class ErrorHandler
{

}

class NotAnErrorHandler
{

}

/**
* Faulty Handler
*/
class FaultyHandler
{

}

/**
* Class SomeHandler
*/
class SomeHandler
{

}

/**
* YetAnotherHandler
*/
class YetAnotherHandler
{

}

/**
* GreenHandler
* @api Do not confuse tag for faulty short description
*/
class GreenHandler
{

}

/**
*
*/
class EmptyHandler
{

}

/**
* Handler for PHP errors/warnings/notices that converts them to exceptions.
*
* @api is ok here
* @deprecated can be used in this context
* @author is actually ok
* @category is irrelevant
* @package is not ment to be used
* @subpackage does not belong here
*/
class ExampleHandler
{

}

/**
* @api
* @since 100.0.2
*/
class ApiHandler
{

}

/**
* @api
*/
class AsyncApiHandler
{

}

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class GroupRepositoryHandler
{}
Loading