Skip to content

Commit

Permalink
Merge branch 'feature/54' into develop
Browse files Browse the repository at this point in the history
Close #54
  • Loading branch information
michalbundyra committed Nov 11, 2019
2 parents ea6ddbb + b6158f6 commit 29ccc68
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ All notable changes to this project will be documented in this file, in reverse
- [#51](https://github.com/webimpress/coding-standard/pull/51) adds check for blank lines and comments before arrow in arrays in `Array\Format` sniff.
Arrow must be after the index value, can be in new line, but any additional lines or comments are disallowed.

- [#54](https://github.com/webimpress/coding-standard/pull/54) adds `Namespaces\UniqueImport` sniff to detect if class/function/constant is imported only once.
Sniff checks also if the name is used only once. The same name can be used for class/function/constant, and constant names are case sensitive.

### Changed

- [#42](https://github.com/webimpress/coding-standard/pull/42) changes `NamingConventions\ValidVariableName` to require variable names be in strict camelCase. It means two capital letters next to each other are not allowed.
Expand Down
168 changes: 168 additions & 0 deletions src/WebimpressCodingStandard/Sniffs/Namespaces/UniqueImportSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

declare(strict_types=1);

namespace WebimpressCodingStandard\Sniffs\Namespaces;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use WebimpressCodingStandard\CodingStandard;

use function explode;
use function in_array;
use function max;
use function strtolower;

use const T_AS;
use const T_COMMA;
use const T_NAMESPACE;
use const T_NS_SEPARATOR;
use const T_OPEN_TAG;
use const T_OPEN_USE_GROUP;
use const T_SEMICOLON;
use const T_STRING;
use const T_USE;

class UniqueImportSniff implements Sniff
{
/**
* @return int[]
*/
public function register() : array
{
return [T_OPEN_TAG, T_NAMESPACE];
}

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

if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) {
$namespace = $phpcsFile->findNext(T_NAMESPACE, $stackPtr + 1);
if ($namespace) {
return $namespace;
}
}

$uses = $this->getUseStatements($phpcsFile, $stackPtr);

foreach ($uses as $type => $data) {
foreach ($data as $name => $ptrs) {
if (isset($ptrs[1])) {
$ptr = max($ptrs);
if ($type === 'full') {
$error = 'The same %s %s is already imported';
$data = [explode('::', $name)[0], $tokens[$ptr]['content']];
$phpcsFile->addError($error, $ptr, 'DuplicateImport', $data);
} else {
$error = 'The name %s is already used for another %s';
$data = [$tokens[$ptr]['content'], $type];
$phpcsFile->addError($error, $ptr, 'DuplicateAlias', $data);
}
}
}
}

return $tokens[$stackPtr]['code'] === T_OPEN_TAG
? $phpcsFile->numTokens + 1
: $stackPtr + 1;
}

/**
* @return string[][]
*/
private function getUseStatements(File $phpcsFile, int $scopePtr) : array
{
$tokens = $phpcsFile->getTokens();

$uses = [];

if (isset($tokens[$scopePtr]['scope_opener'])) {
$start = $tokens[$scopePtr]['scope_opener'];
$end = $tokens[$scopePtr]['scope_closer'];
} else {
$start = $scopePtr;
$end = null;
}

while ($use = $phpcsFile->findNext(T_USE, $start + 1, $end)) {
if (! CodingStandard::isGlobalUse($phpcsFile, $use)) {
$start = $use;
continue;
}

$semicolon = $phpcsFile->findNext(T_SEMICOLON, $use + 1);

$type = 'class';
$next = $phpcsFile->findNext(Tokens::$emptyTokens, $use + 1, null, true);
if ($tokens[$next]['code'] === T_STRING
&& in_array(strtolower($tokens[$next]['content']), ['const', 'function'], true)
) {
$type = strtolower($tokens[$next]['content']);

$use = $next + 1;
}

$current = $semicolon;
while ($namePtr = $phpcsFile->findPrevious(T_STRING, $current, $use)) {
$key = $tokens[$namePtr]['content'];
if ($type !== 'const') {
$key = strtolower($key);
}
$uses[$type][$key][] = $namePtr;

$lastPtr = $namePtr;
$as = $phpcsFile->findPrevious(Tokens::$emptyTokens, $namePtr - 1, null, true);
if ($tokens[$as]['code'] === T_AS) {
$lastPtr = $phpcsFile->findPrevious(T_STRING, $as - 1);
$key = $tokens[$lastPtr]['content'];
if ($type !== 'const') {
$key = strtolower($key);
}
}

$from = $phpcsFile->findPrevious(
Tokens::$emptyTokens + [
T_STRING => T_STRING,
T_NS_SEPARATOR => T_NS_SEPARATOR,
],
$lastPtr - 1,
$use,
true
) ?: $use;

$full = '';
for ($i = $from + 1; $i < $lastPtr; ++$i) {
if ($tokens[$i]['code'] === T_STRING || $tokens[$i]['code'] === T_NS_SEPARATOR) {
$full .= $tokens[$i]['content'];
}
}
$full .= $key;

$prev = $phpcsFile->findPrevious(T_OPEN_USE_GROUP, $lastPtr - 1, $use);
if ($prev) {
for ($i = $prev - 1; $i > $use; --$i) {
if ($tokens[$i]['code'] === T_STRING || $tokens[$i]['code'] === T_NS_SEPARATOR) {
$full = $tokens[$i]['content'] . $full;
}
}
}

$uses['full'][$type . '::' . $full][] = $lastPtr;

$current = $phpcsFile->findPrevious(T_COMMA, $namePtr - 1, $use);
if ($current === false) {
break;
}
}

$start = $semicolon;
}

return $uses;
}
}
37 changes: 37 additions & 0 deletions test/Sniffs/Namespaces/UniqueImportUnitTest.1.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace OneNamespace;

use MyNamespace\MyClass;
use MyNamespace\General;
use OtherNamespace\MyClass as General;

use function MyFunctions\general;
use function array_merge as f1;
use function array_diff as f1;
use function array_diff as f2;
use function
array_filter,
array_merge;

use const MyConsts\GENERAL;
use const MyConsts\OTHER as A;
use const MyConsts\FOO as A;
use const Other\{
CONST1,
CONST2 as General,
CONST3 as GENERAL
};
use const Myconsts\OTHER;

class One
{
use MyTrait;

private $closure;

public function __construct(MyClass $a)
{
$this->closure = function() use($a) {};
}
}
43 changes: 43 additions & 0 deletions test/Sniffs/Namespaces/UniqueImportUnitTest.2.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace First {
use MyNamespace\MyClass;
use MyNamespace\General;
use OtherNamespace\MyClass as General;

use function MyFunctions\general;
use function array_merge as f1;
use function array_diff as f1;
use function array_diff as f2;
use function
array_filter,
array_merge;

use const MyConsts\GENERAL;
use const MyConsts\OTHER as A;
use const MyConsts\FOO as A;
use const Other\{
CONST1,
CONST2 as General,
CONST3 as GENERAL
};
use const Myconsts\OTHER;
}

namespace Second {
use MyNamespace\MyClass;

use function array_merge;

use const A;

class Two {
use MyTrait;

private $closure;

public function __construct(MyClass $a) {
$this->closure = function() use($a) {};
}
}
}
33 changes: 33 additions & 0 deletions test/Sniffs/Namespaces/UniqueImportUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use MyNamespace\MyClass;
use MyNamespace\General;
use OtherNamespace\MyClass as General;

use function MyFunctions\general;
use function array_merge as f1;
use function array_diff as f1;
use function array_diff as f2;
use function
array_filter,
array_merge;

use const MyConsts\GENERAL;
use const MyConsts\OTHER as A;
use const MyConsts\FOO as A;
use const Other\{
CONST1,
CONST2 as General,
CONST3 as GENERAL
};
use const Myconsts\OTHER;

class None {
use MyTrait;

private $closure;

public function __construct(MyClass $a) {
$this->closure = function() use($a) {};
}
}
48 changes: 48 additions & 0 deletions test/Sniffs/Namespaces/UniqueImportUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace WebimpressCodingStandardTest\Sniffs\Namespaces;

use WebimpressCodingStandardTest\Sniffs\AbstractTestCase;

class UniqueImportUnitTest extends AbstractTestCase
{
protected function getErrorList(string $testFile = '') : array
{
switch ($testFile) {
case 'UniqueImportUnitTest.1.inc':
return [
7 => 1,
11 => 1,
12 => 1,
15 => 1,
19 => 1,
23 => 1,
];
case 'UniqueImportUnitTest.2.inc':
return [
6 => 1,
10 => 1,
11 => 1,
14 => 1,
18 => 1,
22 => 1,
];
}

return [
5 => 1,
9 => 1,
10 => 1,
13 => 1,
17 => 1,
21 => 1,
];
}

protected function getWarningList(string $testFile = '') : array
{
return [];
}
}

0 comments on commit 29ccc68

Please sign in to comment.