Skip to content

Commit d0168ba

Browse files
committed
Replace external dependency with better version
1 parent f9ba1bd commit d0168ba

15 files changed

+397
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/.idea/
2+
/.phpunit.result.cache
23
/composer.lock
34
/composer.phar
45
/tests/input2

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ install: travis_retry composer update --prefer-dist
2525

2626
script:
2727
- vendor/bin/phpcs
28+
- vendor/bin/phpunit
2829
- make test-report
2930

3031
stages:

LICENSE

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Copyright (c) 2019 Unleashed Technologies
44

55
Forked from doctrine/coding-standard, Copyright (c) 2013 Steve Müller
66

7+
FullyQualifiedGlobalFunctionsSniff based on soderlind/coding-standard, Copyright (c) 2020 Per Søderlind
8+
79
Permission is hereby granted, free of charge, to any person obtaining a copy
810
of this software and associated documentation files (the "Software"), to deal
911
in the Software without restriction, including without limitation the rights

composer.json

+10-3
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@
2222
},
2323
"autoload": {
2424
"psr-4": {
25-
"Unleashed\\Sniffs\\": "src/Unleashed/Sniffs"
25+
"Unleashed\\": "src/Unleashed"
26+
}
27+
},
28+
"autoload-dev": {
29+
"psr-4": {
30+
"Unleashed\\Tests\\": "tests"
2631
}
2732
},
2833
"require": {
2934
"php": "^7.1",
3035
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
3136
"slevomat/coding-standard": "^6.2.0",
32-
"squizlabs/php_codesniffer": "^3.4.0",
33-
"soderlind/coding-standard": "^0.0.7"
37+
"squizlabs/php_codesniffer": "^3.4.0"
38+
},
39+
"require-dev": {
40+
"phpunit/phpunit": "^7.5"
3441
},
3542
"scripts": {
3643
"test": [

phpunit.xml.dist

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0"?>
2+
<phpunit
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/8.1/phpunit.xsd"
5+
bootstrap="tests/bootstrap.php"
6+
colors="true"
7+
backupGlobals="false"
8+
backupStaticAttributes="false"
9+
beStrictAboutChangesToGlobalState="true"
10+
beStrictAboutOutputDuringTests="true"
11+
beStrictAboutTestsThatDoNotTestAnything="true"
12+
beStrictAboutTodoAnnotatedTests="true"
13+
cacheResult="true"
14+
cacheResultFile=".phpunit.result.cache"
15+
stopOnDefect="true"
16+
executionOrder="defects"
17+
>
18+
<testsuites>
19+
<testsuite name="Unleashed Coding Standard">
20+
<directory suffix="Test.php">./tests/</directory>
21+
</testsuite>
22+
</testsuites>
23+
</phpunit>

src/Unleashed/SniffHelper.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unleashed;
6+
7+
use PHP_CodeSniffer\Files\File;
8+
use SlevomatCodingStandard\Helpers\SniffLocalCache;
9+
use SlevomatCodingStandard\Helpers\UseStatement;
10+
use SlevomatCodingStandard\Helpers\UseStatementHelper;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class SniffHelper
16+
{
17+
/**
18+
* @return array<string,bool>
19+
*/
20+
public static function getAliasesAndNonGlobalFunctionsDefinedInUseStatements(File $file): array
21+
{
22+
static $cache;
23+
$cache = $cache ?? new SniffLocalCache();
24+
25+
$lazyValue = static function () use ($file): array {
26+
$result = [];
27+
28+
foreach (UseStatementHelper::getFileUseStatements($file) as $useStatements) {
29+
foreach ($useStatements as $useStatement) {
30+
\assert($useStatement instanceof UseStatement);
31+
if ($useStatement->getType() !== 'function') {
32+
continue;
33+
}
34+
35+
if ($useStatement->getAlias() !== null || \strpos($useStatement->getFullyQualifiedTypeName(), '\\') !== false) {
36+
$result[$useStatement->getCanonicalNameAsReferencedInFile()] = true;
37+
}
38+
}
39+
}
40+
41+
return $result;
42+
};
43+
44+
return $cache->getAndSetIfNotCached($file, $lazyValue);
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unleashed\Sniffs\Namespaces;
6+
7+
use PHP_CodeSniffer\Files\File;
8+
use PHP_CodeSniffer\Sniffs\Sniff;
9+
use SlevomatCodingStandard\Sniffs\Classes\ModernClassNameReferenceSniff;
10+
use Unleashed\SniffHelper;
11+
12+
final class FullyQualifiedGlobalFunctionsSniff implements Sniff
13+
{
14+
/** @var bool */
15+
public $onlyOptimizedFunctions = false;
16+
17+
/** @var array<string, bool> */
18+
private $optimizedFunctions = [
19+
// @see https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_compile.c "zend_try_compile_special_func"
20+
'array_key_exists' => true,
21+
'array_slice' => true,
22+
'assert' => true,
23+
'boolval' => true,
24+
'call_user_func' => true,
25+
'call_user_func_array' => true,
26+
'chr' => true,
27+
'count' => true,
28+
'defined' => true,
29+
'doubleval' => true,
30+
'floatval' => true,
31+
'func_get_args' => true,
32+
'func_num_args' => true,
33+
'get_called_class' => true,
34+
'get_class' => true,
35+
'gettype' => true,
36+
'in_array' => true,
37+
'intval' => true,
38+
'is_array' => true,
39+
'is_bool' => true,
40+
'is_double' => true,
41+
'is_float' => true,
42+
'is_int' => true,
43+
'is_integer' => true,
44+
'is_long' => true,
45+
'is_null' => true,
46+
'is_object' => true,
47+
'is_real' => true,
48+
'is_resource' => true,
49+
'is_string' => true,
50+
'ord' => true,
51+
'sizeof' => true,
52+
'strlen' => true,
53+
'strval' => true,
54+
// @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c
55+
'constant' => true,
56+
'define' => true,
57+
'dirname' => true,
58+
'extension_loaded' => true,
59+
'function_exists' => true,
60+
'is_callable' => true,
61+
];
62+
63+
/**
64+
* Returns an array of tokens this test wants to listen for.
65+
* We're looking for all functions, so use T_STRING.
66+
*
67+
* @inheritDoc
68+
*/
69+
public function register()
70+
{
71+
return [\T_STRING];
72+
}
73+
74+
/**
75+
* Processes this test, when one of its tokens is encountered.
76+
*
77+
* Code from ForbiddenFunctionsSniff:
78+
*
79+
* @link https://github.com/squizlabs/PHP_CodeSniffer/blob/master/src/Standards/Generic/Sniffs/PHP/ForbiddenFunctionsSniff.php#L118
80+
*
81+
* @inheritDoc
82+
*/
83+
public function process(File $phpcsFile, $stackPtr)
84+
{
85+
if ($this->onlyOptimizedFunctions !== null && \filter_var($this->onlyOptimizedFunctions, FILTER_VALIDATE_BOOLEAN) !== false) {
86+
$globalFunctions = $this->optimizedFunctions;
87+
} else {
88+
$globalFunctions = \array_flip(\get_defined_functions()['internal']);
89+
}
90+
91+
$whitelist = SniffHelper::getAliasesAndNonGlobalFunctionsDefinedInUseStatements($phpcsFile);
92+
93+
$tokens = $phpcsFile->getTokens();
94+
$ignore = [
95+
T_DOUBLE_COLON => true,
96+
T_OBJECT_OPERATOR => true,
97+
T_FUNCTION => true,
98+
T_CONST => true,
99+
T_PUBLIC => true,
100+
T_PRIVATE => true,
101+
T_PROTECTED => true,
102+
T_AS => true,
103+
T_NEW => true,
104+
T_INSTEADOF => true,
105+
T_NS_SEPARATOR => true,
106+
T_IMPLEMENTS => true,
107+
];
108+
$prevToken = $phpcsFile->findPrevious([T_WHITESPACE, T_COMMENT], $stackPtr - 1, null, true);
109+
110+
// If function call is directly preceded by a NS_SEPARATOR don't try to fix it.
111+
if ($tokens[$prevToken]['code'] === T_NS_SEPARATOR && $tokens[$stackPtr]['code'] === T_STRING) {
112+
return;
113+
}
114+
115+
if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
116+
// Not a call to a PHP function.
117+
return;
118+
}
119+
120+
$nextToken = $phpcsFile->findNext([T_WHITESPACE, T_COMMENT], $stackPtr + 1, null, true);
121+
if (isset($ignore[$tokens[$nextToken]['code']]) === true) {
122+
// Not a call to a PHP function.
123+
return;
124+
}
125+
126+
if ($tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS) {
127+
// Not a call to a PHP function.
128+
return;
129+
}
130+
131+
$function = \strtolower($tokens[$stackPtr]['content']);
132+
$functionNormalized = \strtolower($function);
133+
134+
// Is it a whitelisted alias?
135+
if (isset($whitelist[$functionNormalized])) {
136+
return;
137+
}
138+
139+
// Is it an global PHP function?
140+
if (isset($globalFunctions[$functionNormalized]) === false) {
141+
return;
142+
}
143+
144+
$error = \sprintf('Function %1$s() should be referenced via a fully qualified name, e.g.: \%1$s()', $function);
145+
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotFullyQualified');
146+
147+
if ($fix === true) {
148+
$this->applyFix($phpcsFile, $stackPtr, $function);
149+
}
150+
}
151+
152+
private function applyFix(File $phpcsFile, int $stackPtr, string $function): void
153+
{
154+
// This sniff conflicts with ModernClassNameReferenceSniff, so don't bother fixing things it will attempt to fix
155+
if (\array_key_exists(ModernClassNameReferenceSniff::class, $phpcsFile->ruleset->sniffs) && \in_array($function, ['get_class', 'get_parent_class', 'get_called_class'], true)) {
156+
return;
157+
}
158+
159+
$phpcsFile->fixer->beginChangeset();
160+
$phpcsFile->fixer->addContentBefore($stackPtr, '\\');
161+
$phpcsFile->fixer->endChangeset();
162+
}
163+
}

src/Unleashed/ruleset.xml

-2
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,6 @@
289289
<property name="searchAnnotations" value="true"/>
290290
</properties>
291291
</rule>
292-
<!-- Force usage of fully-qualified global functions -->
293-
<rule ref="FullyQualifiedGlobalFunctions"/>
294292
<!-- Forbid unused use statements -->
295293
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
296294
<properties>

tests/Helpers/SniffHelperTest.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Helpers;
4+
5+
use Unleashed\SniffHelper;
6+
use Unleashed\Tests\PHPCSTestCase;
7+
8+
final class SniffHelperTest extends PHPCSTestCase
9+
{
10+
public function testGetAliasesAndNonGlobalFunctionsDefinedInUseStatements()
11+
{
12+
$file = $this->getCodeSnifferFile(__DIR__ . '/data/useStatements.php');
13+
14+
$result = SniffHelper::getAliasesAndNonGlobalFunctionsDefinedInUseStatements($file);
15+
$expected = [
16+
'stringlength',
17+
'isbar',
18+
'foo',
19+
'strpos',
20+
];
21+
22+
$this->assertSame($expected, array_keys($result));
23+
}
24+
}

tests/Helpers/data/useStatements.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Bar\Baz;
4+
use Foo;
5+
use Lorem\Ipsum as LoremIpsum;
6+
use const Lerdorf\IS_BAR;
7+
use const Rasmus\FOO;
8+
use function time;
9+
use function strlen as stringLength;
10+
use function Lerdorf\isBar;
11+
use function Rasmus\foo;
12+
use function Rasums\bar as strpos;
13+
14+
class FooBar
15+
{
16+
17+
use BarTrait;
18+
19+
function foo() {
20+
$test = 'foo';
21+
function() use ($test) {
22+
23+
};
24+
}
25+
26+
}
27+
28+
$test = 'foo';
29+
30+
function () use ($test) {
31+
32+
};
33+
34+
use Zero;

tests/PHPCSTestCase.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Unleashed\Tests;
4+
5+
use PHP_CodeSniffer\Config;
6+
use PHP_CodeSniffer\Files\File;
7+
use PHP_CodeSniffer\Files\LocalFile;
8+
use PHP_CodeSniffer\Runner;
9+
use PHPUnit\Framework\TestCase;
10+
11+
abstract class PHPCSTestCase extends TestCase
12+
{
13+
protected function getCodeSnifferFile(string $filename): File
14+
{
15+
$codeSniffer = new Runner();
16+
$codeSniffer->config = new Config([
17+
'-s',
18+
]);
19+
$codeSniffer->init();
20+
21+
$phpcsFile = new LocalFile(
22+
$filename,
23+
$codeSniffer->ruleset,
24+
$codeSniffer->config
25+
);
26+
27+
$phpcsFile->process();
28+
29+
return $phpcsFile;
30+
}
31+
}

tests/bootstrap.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
3+
error_reporting(E_ALL);
4+
5+
require __DIR__ . '/../vendor/squizlabs/php_codesniffer/autoload.php';
6+
require __DIR__ . '/../vendor/autoload.php';

0 commit comments

Comments
 (0)