Skip to content
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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
* text=auto

/.github export-ignore
/build export-ignore
/cache export-ignore

/.editorconfig export-ignore
/collision-detector.json export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/composer-dependency-analyser.php export-ignore
Expand Down
86 changes: 86 additions & 0 deletions ShipMonkCodingStandard/Sniffs/Arrays/DoubleArrowSpacingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php declare(strict_types = 1);

namespace ShipMonkCodingStandard\Sniffs\Arrays;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use function in_array;
use function ltrim;
use function strpos;
use const T_DOUBLE_ARROW;
use const T_FN_ARROW;
use const T_MATCH_ARROW;
use const T_WHITESPACE;

final class DoubleArrowSpacingSniff implements Sniff
{

private const WRONG_SPACING_AROUND_DOUBLE_ARROW = 'WrongSpacingAroundDoubleArrow';

/**
* @param int $pointer
*/
public function process(
File $phpcsFile,
$pointer
): void
{
$tokens = $phpcsFile->getTokens();

$before = $tokens[$pointer - 1];
$after = $tokens[$pointer + 1];

$beforeValid = in_array($before['content'], [' ', "\n"], true);
$afterValid = in_array($after['content'], [' ', "\n"], true);

if ($beforeValid && $afterValid) {
return;
}

$fix = $phpcsFile->addFixableError(
'Expected 1 space (or newline) before and after double arrow',
$pointer,
self::WRONG_SPACING_AROUND_DOUBLE_ARROW,
);

if (!$fix) {
return;
}

$phpcsFile->fixer->beginChangeset();

if (!$beforeValid) {
if ($before['code'] !== T_WHITESPACE) {
$phpcsFile->fixer->addContentBefore($pointer, ' ');
} elseif (strpos($before['content'], "\n") === false) {
$phpcsFile->fixer->replaceToken($pointer - 1, ' ');
}
}

if (!$afterValid) {
if ($after['code'] !== T_WHITESPACE) {
$phpcsFile->fixer->addContent($pointer, ' ');
} elseif (strpos($after['content'], "\n") === false) {
$phpcsFile->fixer->replaceToken($pointer + 1, ' ');
} else {
// whitespace runs into a newline: drop the trailing spaces, keep the line break
$phpcsFile->fixer->replaceToken($pointer + 1, ltrim($after['content'], " \t"));
}
}

$phpcsFile->fixer->endChangeset();
}

/**
* @return list<int|string>
*/
public function register(): array
{
return [
T_DOUBLE_ARROW,
T_MATCH_ARROW,
T_FN_ARROW,
];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace ShipMonkCodingStandard\Sniffs\ControlStructures;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use function strtoupper;
use const T_ELSE;
use const T_ELSEIF;
use const T_IF;
use const T_WHITESPACE;

final class EmptyConditionBodySniff implements Sniff
{

private const MISSING_COMMENT = 'MissingComment';

/**
* @return list<int>
*/
public function register(): array
{
return [
T_IF,
T_ELSEIF,
T_ELSE,
];
}

/**
* @param int $pointer
*/
public function process(
File $phpcsFile,
$pointer
): void
{
$tokens = $phpcsFile->getTokens();

if (!isset($tokens[$pointer]['scope_opener'], $tokens[$pointer]['scope_closer'])) {
return;
}

$scopeStart = $tokens[$pointer]['scope_opener'];
$scopeEnd = $tokens[$pointer]['scope_closer'];

$firstContent = $phpcsFile->findNext(T_WHITESPACE, $scopeStart + 1, $scopeEnd, true);

if ($firstContent === false) {
$name = strtoupper($tokens[$pointer]['content']);
$phpcsFile->addError("Empty {$name} body must have a comment to explain why it is empty", $scopeStart, self::MISSING_COMMENT);
}
}

}
217 changes: 217 additions & 0 deletions ShipMonkCodingStandard/Sniffs/Whitespaces/CatchSpacingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php declare(strict_types = 1);

namespace ShipMonkCodingStandard\Sniffs\Whitespaces;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function floor;
use function ltrim;
use function str_repeat;
use function strlen;
use const T_BITWISE_OR;
use const T_CATCH;
use const T_CLOSE_PARENTHESIS;
use const T_OPEN_PARENTHESIS;
use const T_STRING;
use const T_VARIABLE;
use const T_WHITESPACE;

final class CatchSpacingSniff implements Sniff
{

public const INVALID_SPACING = 'InvalidSpacing';

private const INDENT_SIZE = 4;

/**
* @return list<(int|string)>
*/
public function register(): array
{
return [
T_CATCH,
];
}

/**
* @param int $catchPointer
*/
public function process(
File $phpcsFile,
$catchPointer
): void
{
$tokens = $phpcsFile->getTokens();
$catchToken = $tokens[$catchPointer];

$catchParenthesisOpenerPointer = $catchToken['parenthesis_opener'];
$catchParenthesisCloserPointer = $catchToken['parenthesis_closer'];

$closingParenthesisBeforeCatchPointer = TokenHelper::findPreviousEffective($phpcsFile, $catchPointer - 1);
if ($closingParenthesisBeforeCatchPointer === null) {
return; // invalid php file
}

$variablePointer = TokenHelper::findNext(
$phpcsFile,
T_VARIABLE,
$catchParenthesisOpenerPointer,
$catchParenthesisCloserPointer,
);

if ($variablePointer === null) {
return; // Non-capturing catch is not supported by this sniff
}

// horizontal indentation of the catch line; ltrim drops the leading newline that the
// preceding whitespace token carries when the catch sits at column 1 (e.g. top-level try/catch)
$catchIndent = ltrim($tokens[$closingParenthesisBeforeCatchPointer - 1]['content'], "\r\n");
$catchIndentSize = strlen($catchIndent);
$expectedIndentBlocks = (int) floor($catchIndentSize / self::INDENT_SIZE) + 1;
$expectedInnerIndent = str_repeat(' ', $expectedIndentBlocks * self::INDENT_SIZE);

$multilineMode = $tokens[$catchParenthesisOpenerPointer + 1]['code'] === T_WHITESPACE && $tokens[$catchParenthesisOpenerPointer + 1]['content'] === "\n";
$tokenPointer = $catchParenthesisOpenerPointer;

do {
if ($tokenPointer === $catchParenthesisCloserPointer) {
break;
}

$token = $tokens[$tokenPointer];

if ($token['code'] === T_OPEN_PARENTHESIS) {
if ($multilineMode) {
if ($tokens[$tokenPointer + 1]['content'] !== "\n") {
$fix = $phpcsFile->addFixableError("Invalid catch format: expected newline after {$token['content']}", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, "\n");
}
}

if ($tokens[$tokenPointer + 2]['content'] !== $expectedInnerIndent) {
$fix = $phpcsFile->addFixableError("Invalid catch indent, expected '{$expectedInnerIndent}'", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 2, $expectedInnerIndent);
}
}

} else {
if ($tokens[$tokenPointer + 1]['code'] === T_WHITESPACE) {
$fix = $phpcsFile->addFixableError("'Invalid catch format: expected no whitespace after {$token['content']}'", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, '');
}
}
}
}

if ($token['code'] === T_STRING) {
if ($tokens[$tokenPointer + 1]['content'] !== ' ') {
$fix = $phpcsFile->addFixableError("Invalid catch format: expected single space after {$token['content']}", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, ' ');
}
}
}

if ($token['code'] === T_BITWISE_OR) {
if ($tokens[$tokenPointer - 1]['content'] !== ' ') {
$fix = $phpcsFile->addFixableError('Invalid catch format: expected single whitespace before |', $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer - 1, ' ');
}
}

if ($multilineMode) {
if ($tokens[$tokenPointer + 1]['content'] !== "\n") {
$fix = $phpcsFile->addFixableError('Invalid catch format: expected newline after |', $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, "\n");
}
}

if ($tokens[$tokenPointer + 2]['content'] !== $expectedInnerIndent) {
$fix = $phpcsFile->addFixableError("Invalid catch indent, expected '{$expectedInnerIndent}'", $tokenPointer + 2, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 2, $expectedInnerIndent);
}
}

} else {
if ($tokens[$tokenPointer + 1]['content'] !== ' ') {
$fix = $phpcsFile->addFixableError('Invalid catch format: expected single whitespace after |', $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, ' ');
}
}
}
}

if ($token['code'] === T_VARIABLE) {
if ($tokens[$tokenPointer - 1]['content'] !== ' ') {
$fix = $phpcsFile->addFixableError("Invalid catch format: expected single space before {$token['content']}", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer - 1, ' ');
}
}

if ($multilineMode) {
if ($tokens[$tokenPointer + 1]['content'] !== "\n") {
$fix = $phpcsFile->addFixableError("Invalid catch format: expected newline after {$token['content']}", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, "\n");
}
}

if ($catchIndent === '') {
if ($tokens[$tokenPointer + 2]['code'] === T_WHITESPACE) {
$fix = $phpcsFile->addFixableError('Invalid catch closing indent, expected no indentation', $tokenPointer + 2, self::INVALID_SPACING);
if ($fix) {
$phpcsFile->fixer->replaceToken($tokenPointer + 2, '');
}
}
} elseif ($tokens[$tokenPointer + 2]['content'] !== $catchIndent) {
$fix = $phpcsFile->addFixableError("Invalid catch closing indent, expected '{$catchIndent}'", $tokenPointer + 2, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 2, $catchIndent);
}
}

} else {
if ($tokens[$tokenPointer + 1]['code'] !== T_CLOSE_PARENTHESIS) {
$fix = $phpcsFile->addFixableError("Invalid catch format: no space expected after {$token['content']}", $tokenPointer, self::INVALID_SPACING);
if ($fix) {
$this->addOrReplaceWhitespaceToken($phpcsFile, $tokenPointer + 1, '');
}
}
}
}

$tokenPointer++;
} while (true);
}

private function addOrReplaceWhitespaceToken(
File $phpcsFile,
int $pointer,
string $whitespaceContent
): void
{
$tokens = $phpcsFile->getTokens();

$phpcsFile->fixer->beginChangeset();
if ($tokens[$pointer]['code'] === T_WHITESPACE) {
$phpcsFile->fixer->replaceToken($pointer, $whitespaceContent);
} else {
if ($whitespaceContent === "\n") {
$phpcsFile->fixer->addNewlineBefore($pointer);
} else {
$phpcsFile->fixer->addContentBefore($pointer, $whitespaceContent);
}
}
$phpcsFile->fixer->endChangeset();
}

}
Loading