Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 1 addition & 5 deletions ShipMonkCodingStandard/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,7 @@
@return,
"/>
<element value="
@template,
@template-covariant,
@template-contravariant,
@template-extends,
@template-implements,
@template*,
@extends,
@implements,
"/>
Expand Down
2 changes: 2 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
<file>ShipMonkCodingStandard/</file>
<file>tests/</file>

<exclude-pattern>tests/Data/</exclude-pattern>

<rule ref="ShipMonkCodingStandard"/>
</ruleset>
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ parameters:
paths:
- ShipMonkCodingStandard
- tests
excludePaths:
- tests/Data
tmpDir: cache/phpstan/
checkMissingCallableSignature: true
checkUninitializedProperties: true
Expand Down
5 changes: 5 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
failOnWarning="true"
cacheResultFile="cache/phpunit.result.cache"
>
<testsuites>
<testsuite name="tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<php>
<ini name="error_reporting" value="-1"/>
</php>
Expand Down
36 changes: 36 additions & 0 deletions tests/Data/DocCommentSpacing/TemplateAnnotationOrderCorrect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace ShipMonkTests\CodingStandard\Data\DocCommentSpacing;

/**
* @template-covariant TEntity
* @template TEntityId
* @extends SomeInterface<TEntity, TEntityId>
*/
interface CorrectCovariantBeforeTemplate
{

}

/**
* @template TKey
* @template-contravariant TValue
* @template-covariant TResult
* @implements SomeInterface<TKey, TValue, TResult>
*/
interface CorrectMixedTemplateOrder
{

}

/**
* @template T
* @template-extends SomeClass<T>
* @template-implements SomeInterface<T>
* @extends OtherClass<T>
* @implements OtherInterface<T>
*/
class CorrectTemplateExtendsImplements
{

}
45 changes: 45 additions & 0 deletions tests/Data/DocCommentSpacing/TemplateAnnotationOrderWrong.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php declare(strict_types = 1);

namespace ShipMonkTests\CodingStandard\Data\DocCommentSpacing;

// line 6: IncorrectOrderOfAnnotationsInGroup (@extends before @template)
/**
* @extends SomeInterface<T>
* @template T
*/
interface ExtendsBeforeTemplate
{

}

// line 17: IncorrectOrderOfAnnotationsInGroup (@implements before @template)
/**
* @implements SomeInterface<T>
* @template T
*/
interface ImplementsBeforeTemplate
{

}

// line 28: IncorrectOrderOfAnnotationsInGroup (@implements before @extends)
/**
* @template T
* @implements SomeInterface<T>
* @extends OtherInterface<T>
*/
interface ImplementsBeforeExtends
{

}

// lines 37-39: IncorrectAnnotationsGroup (blank line between annotations in same group)
/**
* @template T
*
* @extends SomeInterface<T>
*/
interface BlankLineBetweenAnnotationsInSameGroup
{

}
119 changes: 119 additions & 0 deletions tests/DocCommentSpacingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php declare(strict_types = 1);

namespace ShipMonkTests\CodingStandard;

use PHPUnit\Framework\TestCase;
use function escapeshellarg;
use function exec;
use function implode;
use function preg_match;
use function strpos;
use const PHP_EOL;

class DocCommentSpacingTest extends TestCase
{

private const SNIFF_PREFIX = 'SlevomatCodingStandard.Commenting.DocCommentSpacing';

/**
* Verify that @template* annotations are not reordered relative to each other.
* The order of @template, @template-covariant, and @template-contravariant determines
* template parameter position in PHPStan's type system, so reordering changes semantics.
*/
public function testTemplateAnnotationsAreNotReordered(): void
{
$output = self::runPhpcs(__DIR__ . '/Data/DocCommentSpacing/TemplateAnnotationOrderCorrect.php');
self::assertNoSniffErrors(self::SNIFF_PREFIX, $output);
}

public function testExtendsBeforeTemplateIsReported(): void
{
$output = self::runPhpcs(__DIR__ . '/Data/DocCommentSpacing/TemplateAnnotationOrderWrong.php');
self::assertSniffErrorOnLine(self::SNIFF_PREFIX . '.IncorrectOrderOfAnnotationsInGroup', $output, 7);
}

public function testImplementsBeforeTemplateIsReported(): void
{
$output = self::runPhpcs(__DIR__ . '/Data/DocCommentSpacing/TemplateAnnotationOrderWrong.php');
self::assertSniffErrorOnLine(self::SNIFF_PREFIX . '.IncorrectOrderOfAnnotationsInGroup', $output, 17);
}

public function testImplementsBeforeExtendsIsReported(): void
{
$output = self::runPhpcs(__DIR__ . '/Data/DocCommentSpacing/TemplateAnnotationOrderWrong.php');
self::assertSniffErrorOnLine(self::SNIFF_PREFIX . '.IncorrectOrderOfAnnotationsInGroup', $output, 28);
}

public function testBlankLineBetweenAnnotationsInSameGroupIsReported(): void
{
$output = self::runPhpcs(__DIR__ . '/Data/DocCommentSpacing/TemplateAnnotationOrderWrong.php');
self::assertSniffErrorOnLine(self::SNIFF_PREFIX . '.IncorrectAnnotationsGroup', $output, 38);
}

/**
* @return list<string>
*/
private static function runPhpcs(string $filePath): array
{
$phpcs = __DIR__ . '/../vendor/bin/phpcs';
$output = [];
exec($phpcs . ' --standard=ShipMonkCodingStandard --no-colors -s ' . escapeshellarg($filePath), $output);
return $output;
}

/**
* @param list<string> $phpcsOutput
*/
private static function assertNoSniffErrors(
string $sniffPrefix,
array $phpcsOutput,
): void
{
$relevantErrors = [];

foreach ($phpcsOutput as $line) {
if (strpos($line, $sniffPrefix) !== false) {
$relevantErrors[] = $line;
}
}

self::assertSame(
[],
$relevantErrors,
'Expected no ' . $sniffPrefix . ' errors, but found:' . PHP_EOL . implode(PHP_EOL, $relevantErrors),
);
}

/**
* @param list<string> $phpcsOutput
*/
/**
* @param list<string> $phpcsOutput
*/
private static function assertSniffErrorOnLine(
string $sniffCode,
array $phpcsOutput,
int $expectedLine,
): void
{
$currentLine = 0;
$found = false;

foreach ($phpcsOutput as $outputLine) {
if (preg_match('~^\s*(\d+)\s*\|~', $outputLine, $matches) === 1) {
$currentLine = (int) $matches[1];
}

if (strpos($outputLine, $sniffCode) !== false && $currentLine === $expectedLine) {
$found = true;
}
}

self::assertTrue(
$found,
'Expected ' . $sniffCode . ' error on line ' . $expectedLine
. ', but not found in output:' . PHP_EOL . implode(PHP_EOL, $phpcsOutput),
);
}

}
Loading