First rulesets for DigiComp and inwebs

This commit is contained in:
Ferdinand Kuhl 2021-08-01 13:33:41 +02:00
parent 44567f306f
commit 96dde4a0ab
23 changed files with 2765 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
vendor/
temp/

40
composer.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "digicomp/php-codesniffer",
"description": "DigiComp and in.webs PHP_CodeSniffer RuleSets",
"type": "phpcodesniffer-standard",
"license": "proprietary",
"keywords": [
"phpcs"
],
"autoload": {
"psr-4": {
"DigiComp\\PhpCodesniffer\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"DigiComp\\PhpCodesniffer\\": "tests/"
}
},
"require": {
"php": ">=7.4",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
"slevomat/coding-standard": "^7.0.13",
"squizlabs/php_codesniffer": "^3.6.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"scripts": {
"phpunit": "php vendor/bin/phpunit",
"phpcs": "php vendor/bin/phpcs --standard=Inwebs Inwebs/",
"tests": [
"@phpunit",
"@phpcs"
],
"phpcbf": "php vendor/bin/phpcbf --standard=Inwebs Inwebs/"
},
"config": {
"sort-packages": true
}
}

2032
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

42
phpunit.xml.dist Normal file
View file

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/8.1/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutChangesToGlobalState="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutTodoAnnotatedTests="true"
cacheResult="true"
cacheResultFile="temp/.phpunit.result.cache"
stopOnDefect="true"
executionOrder="defects"
>
<testsuites>
<testsuite name="Coding Standard">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<logging>
<log
type="coverage-html"
target="temp/coverage"
/>
<log type="coverage-clover" target="temp/coverage.xml"/>
<log
type="coverage-text"
target="php://stdout"
showUncoveredFiles="true"
showOnlySummary="true"
/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">SlevomatCodingStandard</directory>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\Annotations;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
class VarIsLastTagOnPropertySniff extends AbstractVariableSniff
{
/**
* @inheritDoc
*/
protected function processVariable(File $phpcsFile, $stackPtr)
{
}
/**
* @inheritDoc
*/
protected function processVariableInString(File $phpcsFile, $stackPtr)
{
}
/**
* @inheritDoc
*/
protected function processMemberVar(File $phpcsFile, $stackPtr)
{
try {
$propertyInfo = $phpcsFile->getMemberProperties($stackPtr);
if (empty($propertyInfo) === true) {
return;
}
} catch (\Exception $e) {
// Turns out not to be a property after all.
return;
}
$tokens = $phpcsFile->getTokens();
$ignore = [
T_PUBLIC,
T_PRIVATE,
T_PROTECTED,
T_VAR,
T_STATIC,
T_WHITESPACE,
T_STRING,
T_NS_SEPARATOR,
T_NULLABLE,
];
$commentEnd = $phpcsFile->findPrevious($ignore, ($stackPtr - 1), null, true);
if ($commentEnd === false
|| ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG
&& $tokens[$commentEnd]['code'] !== T_COMMENT)
) {
// Obviously, there is NO comment at all, we are done
return;
}
$commentStart = $tokens[$commentEnd]['comment_opener'];
$varToken = $otherToken = null;
foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
if ($tokens[$tag]['content'] === '@var') {
$varToken = $tag;
} else {
if ($otherToken === null || $tag > $otherToken) {
$otherToken = $tag;
}
}
}
if ($otherToken > $varToken) {
$fix = $phpcsFile->addFixableError('@var should be last property annotation', $varToken, 'VarNotLast');
if ($fix) {
$this->moveVarTagToEndOfDoctype(
$varToken,
$tokens[$commentStart]['comment_tags'],
$phpcsFile,
$commentEnd
);
}
}
}
/**
* @param int $varToken
* @param array $commentTags
* @param File $phpcsFile
* @param int $commentEnd
*/
protected function moveVarTagToEndOfDoctype(
int $varToken,
array $commentTags,
File $phpcsFile,
int $commentEnd
): void {
$nextTokenIndex = array_search($varToken, $commentTags) + 1;
$nextToken = $commentTags[$nextTokenIndex];
// subtracting four tokens (whitespace + "*" + whitespace + newline) as we already know, we are not last
$varContent = $phpcsFile->getTokensAsString($varToken - 4, $nextToken - $varToken);
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->addContentBefore($commentEnd - 2, $varContent);
for ($tokenToDelete = $varToken; $tokenToDelete < $nextToken; $tokenToDelete++) {
$phpcsFile->fixer->replaceToken($tokenToDelete, '');
}
$phpcsFile->fixer->endChangeset();
}
}

View file

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
/**
* Class AbstractMissingPrefixSniff is a blueprint for checking a missing prefix in a class name.
*/
abstract class AbstractMissingPrefixSniff implements Sniff
{
/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$classNamePointer = TokenHelper::findNext(
$phpcsFile,
\T_STRING,
$stackPtr + 1,
$tokens[$stackPtr]['scope_opener']
);
$classNameToken = $tokens[$classNamePointer];
$className = $classNameToken['content'];
$this->checkPrefix($phpcsFile, $classNamePointer, $className);
}
/**
* Check for the prefix and add an error if it is absent.
*
* @param File $phpcsFile
* @param int $stackPtr
* @param string $className
*/
protected function checkPrefix(File $phpcsFile, int $stackPtr, string $className)
{
$prefix = $this->getPrefix();
$foundPrefix = \substr($className, 0, \strlen($prefix));
if ($foundPrefix && \strtolower($foundPrefix) === \strtolower($prefix)) {
return;
}
$phpcsFile->addError(
'Missing prefix "%s" at "%s"',
$stackPtr,
$this->getErrorCode(),
[
$prefix,
$className,
]
);
}
/**
* Return the prefix which should be checked.
*
* @return string
*/
abstract protected function getPrefix(): string;
/**
* Return the error code which should be used by the sniff.
*
* @return string
*/
abstract protected function getErrorCode(): string;
}

View file

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
/**
* Class AbstractMissingSuffixSniff is a blueprint for checking a missing suffix in a class name.
*/
abstract class AbstractMissingSuffixSniff implements Sniff
{
/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$classNamePointer = TokenHelper::findNext(
$phpcsFile,
\T_STRING,
$stackPtr + 1,
$tokens[$stackPtr]['scope_opener']
);
$classNameToken = $tokens[$classNamePointer];
$className = $classNameToken['content'];
$this->checkSuffix($phpcsFile, $classNamePointer, $className);
}
/**
* Check for the suffix and add an error if it is absent.
*
* @param File $phpcsFile
* @param int $stackPtr
* @param string $className
*/
protected function checkSuffix(File $phpcsFile, int $stackPtr, string $className)
{
$suffix = $this->getSuffix();
$foundSuffix = \substr($className, -\strlen($suffix));
if (\strtolower($foundSuffix) === \strtolower($suffix)) {
return;
}
$phpcsFile->addError(
'Missing suffix "%s" at "%s"',
$stackPtr,
$this->getErrorCode(),
[
$suffix,
$className,
]
);
}
/**
* Return the suffix which should be checked.
*
* @return string
*/
abstract protected function getSuffix(): string;
/**
* Return the error code which should be used by the sniff.
*
* @return string
*/
abstract protected function getErrorCode(): string;
}

View file

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
use PHP_CodeSniffer\Files\File;
/**
* Class MissingAbstractPrefixSniff checks if the class name of an abstract class begins with abstract.
*/
class MissingAbstractPrefixSniff extends AbstractMissingPrefixSniff
{
public const CODE_MISSING_ABSTRACT_PREFIX = 'MissingAbstractPrefix';
/**
* @return int[]
*/
public function register(): array
{
return [\T_CLASS];
}
/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$properties = $phpcsFile->getClassProperties($stackPtr);
if (! $properties['is_abstract']) {
return;
}
parent::process($phpcsFile, $stackPtr);
}
/**
* @inheritDoc
*/
protected function getPrefix(): string
{
return 'abstract';
}
/**
* @inheritDoc
*/
protected function getErrorCode(): string
{
return self::CODE_MISSING_ABSTRACT_PREFIX;
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
/**
* Class MissingInterfaceSuffixSniff checks if the class name of an interface ends with interface.
*/
class MissingInterfaceSuffixSniff extends AbstractMissingSuffixSniff
{
public const CODE_MISSING_INTERFACE_SUFFIX = 'MissingInterfaceSuffix';
/**
* @return int[]
*/
public function register(): array
{
return [\T_INTERFACE];
}
/**
* {@inheritDoc}
*/
protected function getSuffix(): string
{
return 'interface';
}
/**
* {@inheritDoc}
*/
protected function getErrorCode(): string
{
return self::CODE_MISSING_INTERFACE_SUFFIX;
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\Comments;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
class DisallowHashCommentsSniff implements Sniff
{
/**
* @inheritDoc
*/
public function register()
{
return [\T_COMMENT];
}
/**
* @inheritDoc
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'][0] === '#') {
$error = 'Hash comments are prohibited; found %s';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found', [$tokens[$stackPtr]['content']]);
if ($fix) {
$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($stackPtr, '//' . \substr($tokens[$stackPtr]['content'], 1));
$phpcsFile->fixer->endChangeset();
}
}
}
}

82
src/DigiComp/ruleset.xml Normal file
View file

@ -0,0 +1,82 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="DigiComp"
namespace="Inwebs\PhpCodesniffer\DigiComp"
xsi:noNamespaceSchemaLocation="../../vendor/squizlabs/php_codesniffer/phpcs.xsd">
<description>IN.WEBS PHP_CodeSniffer standard</description>
<!-- Base standard -->
<rule ref="PSR12"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.Commenting.VariableComment">
<exclude name="Squiz.Commenting.VariableComment.VarOrder" />
<exclude name="Squiz.Commenting.VariableComment.TagNotAllowed" />
<exclude name="Squiz.Commenting.VariableComment.IncorrectVarType" />
</rule>
<!-- TODO: what about new PHP 7.4 code base, where property annotations may get useles -->
<rule ref="Squiz.Commenting.VariableComment.Missing">
<type>warning</type>
<severity>4</severity>
</rule>
<rule ref="Squiz.Strings.DoubleQuoteUsage" />
<!-- We allow the usages of vars in strings - in this case, double quotes are allowed -->
<rule ref="Squiz.Strings.DoubleQuoteUsage.ContainsVar">
<severity>0</severity>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment" />
<!-- TODO: Could get obsolete, if we deny two newlines generally -->
<rule ref="SlevomatCodingStandard.Classes.MethodSpacing" />
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowEmpty" />
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses" />
<rule ref="SlevomatCodingStandard.Namespaces.DisallowGroupUse" />
<rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalConstants" />
<rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions" />
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
<properties>
<property name="searchAnnotations" value="true" />
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.UseFromSameNamespace" />
<rule ref="SlevomatCodingStandard.Namespaces.UselessAlias" />
<rule ref="SlevomatCodingStandard.Namespaces.UseSpacing" />
<!-- Duplicate spaces are needed for indentation in Doctrine annotations -->
<rule ref="SlevomatCodingStandard.Whitespaces.DuplicateSpaces">
<properties>
<property name="ignoreSpacesInAnnotation" value="true" />
</properties>
</rule>
<!-- TODO: MissingAspect-Suffix -->
<!-- TODO: lowerCamelCase-Properties -->
<!-- TODO: Always use Class-Literals (if possible), might be a VERY expensive test -->
<!-- TODO: New line before bool operators -->
<!-- TODO: Flow rules, like no "@Flow\Inject" in traits! -->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array" value="sizeof=>count,delete=>unset,xdebug_break=>null,join=>implode"/>
</properties>
</rule>
<rule ref="Generic.Formatting.SpaceAfterCast">
<properties>
<property name="spacing" value="0"/>
</properties>
</rule>
<!-- Multiline-Arrays have trailing commas and single line arrays not more, than one entry -->
<!-- Note: trailing commas will not be added for arrays with indices -->
<rule ref="Squiz.Arrays.ArrayDeclaration">
<exclude name="Squiz.Arrays.ArrayDeclaration.CloseBraceNotAligned"/>
<exclude name="Squiz.Arrays.ArrayDeclaration.ValueNotAligned"/>
<exclude name="Squiz.Arrays.ArrayDeclaration.KeyNotAligned"/>
<exclude name="Squiz.Arrays.ArrayDeclaration.DoubleArrowNotAligned"/>
<exclude name="Squiz.Arrays.ArrayDeclaration.SingleLineNotAllowed"/>
</rule>
<rule ref="Generic.Arrays.ArrayIndent" />
</ruleset>

25
src/Inwebs/ruleset.xml Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Inwebs"
namespace="Inwebs\PhpCodesniffer\Inwebs"
xsi:noNamespaceSchemaLocation="../../vendor/squizlabs/php_codesniffer/phpcs.xsd">
<description>IN.WEBS PHP_CodeSniffer standard</description>
<!-- Base standard -->
<rule ref="DigiComp"/>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties>
<property name="spacing" value="0"/>
</properties>
</rule>
<rule ref="Squiz.Commenting.VariableComment.Missing">
<type>error</type>
<severity>6</severity>
</rule>
<!-- TODO: Enforcement of doc types for methods -->
<!-- TODO: Never use /** inside of methods -->
<!-- TODO: Exception code should be unix timestamp (if possible), might be difficult to test -->
</ruleset>

View file

@ -0,0 +1,14 @@
<?php
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\Annotations;
use SlevomatCodingStandard\Sniffs\TestCase;
class VarIsLastTagOnPropertySniffTest extends TestCase
{
public function testErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/PropertyOrder.php');
self::assertSame(2, $report->getErrorCount());
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Inwebs\PhpCodesniffer\Sniffs\Annotations\data;
class PropertyOrder
{
/**
* A small description.
*
* And
* some
* Text
*
* @var string With a nice and multi line
* description
* @Flow\Inject(name="StrangeContent")
* @ORM\Column(stuff={
* "hallo"
* })
*/
protected $test;
/**
* A small description.
*
* And
* some
* Text
*
* @Flow\Inject
* @var string
* @ORM\Column(stuff={
* "hallo"
* })
*/
protected $testTwo;
/**
* @Flow\Inject
* @var \stdClass mega!
*/
protected $valid;
}

View file

@ -0,0 +1,20 @@
<?php
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
use SlevomatCodingStandard\Sniffs\TestCase;
class MissingAbstractPrefixSniffTest extends TestCase
{
public function testErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/ClassMissingAbstractPrefix.php');
self::assertSame(1, $report->getErrorCount());
}
public function testNoErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/AbstractClass.php');
self::assertSame(0, $report->getErrorCount());
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\ClassNames;
use SlevomatCodingStandard\Sniffs\TestCase;
class MissingInterfaceSuffixSniffTest extends TestCase
{
public function testErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/NoInterfaceSuffix.php');
self::assertSame(1, $report->getErrorCount());
}
public function testNoErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/AbstractClass.php');
self::assertSame(0, $report->getErrorCount());
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Inwebs\PhpCodesniffer\Sniffs\ClassNames\data;
abstract class AbstractClass
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Inwebs\PHPCodesniffer\Tests\Sniffs\ClassNames\data;
abstract class ClassMissingAbstractPrefix
{
}

View file

@ -0,0 +1,10 @@
<?php
namespace Inwebs\PhpCodesniffer\Sniffs\ClassNames\data;
/**
* @Neos\Flow\Annotations\Aspect
*/
class ClassMissingAspectSuffix
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Inwebs\PhpCodesniffer\Sniffs\ClassNames\data;
interface NoInterfaceSuffix
{
}

View file

@ -0,0 +1,14 @@
<?php
namespace DigiComp\PhpCodesniffer\DigiComp\Sniffs\Comments;
use SlevomatCodingStandard\Sniffs\TestCase;
class DisallowHashCommentsSniffTest extends TestCase
{
public function testErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/comments.php');
self::assertSame(1, $report->getErrorCount());
}
}

View file

@ -0,0 +1,5 @@
<?php
# This is not allowed
// But this is allowed

6
tests/bootstrap.php Normal file
View file

@ -0,0 +1,6 @@
<?php declare(strict_types = 1);
error_reporting(E_ALL);
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/squizlabs/php_codesniffer/autoload.php';