Skip to content

Commit

Permalink
✨ Add new sniffer forcing immutability through Psalm
Browse files Browse the repository at this point in the history
  • Loading branch information
Th3Mouk committed Jun 7, 2020
1 parent 6b4bd14 commit 8f97071
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 2 deletions.
109 changes: 109 additions & 0 deletions Youdot/Sniffs/Classes/PsalmImmutableSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Youdot\Sniffs\Classes;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\DocCommentHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;

/**
* @psalm-immutable
*/
class PsalmImmutableSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register(): array
{
return [T_CLASS];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcs_file The file being scanned.
* @param int $stack_ptr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcs_file, $stack_ptr)
{
$tokens = $phpcs_file->getTokens();

if (!DocCommentHelper::hasDocComment($phpcs_file, $stack_ptr)) {
$phpcs_file->addFixableError(
'The class has no PhpDoc block, and must have one with @psalm-immutable tag',
$stack_ptr,
'MissingTag'
);

$phpcs_file->fixer->addContent(
$stack_ptr - 1,
"/**\n * @psalm-immutable\n */\n"
);

return;
}

$doc_block_start_pointer = TokenHelper::findPrevious(
$phpcs_file,
T_DOC_COMMENT_OPEN_TAG,
$stack_ptr - 1
);

$tag_found_pointers = TokenHelper::findNextAll(
$phpcs_file,
T_DOC_COMMENT_TAG,
$doc_block_start_pointer,
$stack_ptr - 1
);

foreach ($tag_found_pointers as $tag_pointer) {
$token = $tokens[$tag_pointer];

if ($token['content'] === '@psalm-immutable') {
return;
}
}

$phpcs_file->addFixableError(
'The PhpDoc block of the class does not contains @psalm-immutable tag',
$doc_block_start_pointer,
'MissingTag'
);

// Single line comment must be exploded in multiple
if ($tokens[$stack_ptr]['line'] - 1 === $tokens[$doc_block_start_pointer]['line']) {
$phpcs_file->fixer->addContent($doc_block_start_pointer, "\n * @psalm-immutable\n *\n *");

$phpdoc_content_pointer = TokenHelper::findNext(
$phpcs_file,
[T_DOC_COMMENT_STRING, T_DOC_COMMENT_TAG],
$doc_block_start_pointer,
$stack_ptr
);

if ($tokens[$phpdoc_content_pointer]['code'] === T_DOC_COMMENT_STRING) {
$content = trim($tokens[$phpdoc_content_pointer]['content']);
$phpcs_file->fixer->replaceToken($phpdoc_content_pointer, "$content\n ");

return;
}

$phpcs_file->fixer->addNewline($phpdoc_content_pointer);

return;
}

$phpcs_file->fixer->addContent($doc_block_start_pointer, "\n * @psalm-immutable\n *");

return;
}
}
2 changes: 2 additions & 0 deletions Youdot/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<rule ref="ObjectCalisthenics.NamingConventions.NoSetter"/>

<rule ref="Youdot.Classes.PsalmImmutable"/>

<rule ref="Youdot.NamingConventions.ValidVariableName"/>

</ruleset>
4 changes: 2 additions & 2 deletions YoudotVanilla/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<property name="spacing" value="0" />
</properties>
</rule>

<rule ref="ObjectCalisthenics.Metrics.MaxNestingLevel">
<properties>
<property name="maxNestingLevel" value="2"/>
Expand All @@ -36,7 +36,7 @@
<property name="allowedShortNames" type="array" value="i,id,to,up"/>
</properties>
</rule>

<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
<properties>
<property name="allowFullyQualifiedGlobalClasses" value="true"/>
Expand Down
4 changes: 4 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@
<exclude-pattern>Youdot/Sniffs/NamingConventions/ValidVariableNameSniff.php</exclude-pattern>
</rule>

<rule ref="Youdot.Classes.PsalmImmutable.MissingTag">
<exclude-pattern>Sniff.php</exclude-pattern>
</rule>

</ruleset>
31 changes: 31 additions & 0 deletions tests/Youdot/Sniffs/Classes/PsalmImmutableSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Youdot\Sniffs\Classes;

use SlevomatCodingStandard\Sniffs\TestCase;

final class PsalmImmutableSniffTest extends TestCase
{
public function testNotErrors(): void
{
$report = self::checkFile(__DIR__ . '/data/validClasses.php');
self::assertNoSniffErrorInFile($report);

}

public function testErrorsAndFix(): void
{
$report = self::checkFile(__DIR__ . '/data/wrongClasses.php');

self::assertSame(4, $report->getErrorCount());

self::assertSniffError($report, 3, 'MissingTag');
self::assertSniffError($report, 5, 'MissingTag');
self::assertSniffError($report, 10, 'MissingTag');
self::assertSniffError($report, 13, 'MissingTag');

self::assertAllFixedInFile($report);
}
}
18 changes: 18 additions & 0 deletions tests/Youdot/Sniffs/Classes/data/validClasses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/**
* @psalm-immutable
*/
class Toto {}

/**
* @psalm-immutable
*
* @foo
*/
class Titi {}

/**
* @psalm-immutable
*/
class Tutu {}
27 changes: 27 additions & 0 deletions tests/Youdot/Sniffs/Classes/data/wrongClasses.fixed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/**
* @psalm-immutable
*/
class Toto {}

/**
* @psalm-immutable
*
* Class Titi
*/
class Titi {}

/**
* @psalm-immutable
*
* Heya
*/
class Tutu {}

/**
* @psalm-immutable
*
* @foo
*/
class Tata {}
14 changes: 14 additions & 0 deletions tests/Youdot/Sniffs/Classes/data/wrongClasses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

class Toto {}

/**
* Class Titi
*/
class Titi {}

/** Heya */
class Tutu {}

/** @foo */
class Tata {}

0 comments on commit 8f97071

Please sign in to comment.