├── .docker
└── validate-against-schema
│ └── Dockerfile
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── composer.json
└── src
└── Unleashed
├── Helpers
├── NamespaceHelper.php
├── SniffLocalCache.php
└── UseStatements.php
├── Sniffs
├── Commenting
│ ├── ForbiddenSingleLineCommentsSniff.php
│ └── InheritDocFormatSniff.php
├── DoctrineMigrations
│ └── DescriptionRequiredSniff.php
├── Namespaces
│ └── FullyQualifiedGlobalFunctionsSniff.php
└── PHP
│ └── ForbiddenClassesSniff.php
└── ruleset.xml
/.docker/validate-against-schema/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.17.3
2 |
3 | RUN apk add --no-cache libxml2-utils=2.10.4-r0
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Although this coding standard is primarily for Unleashed Technologies' internal use, bug fixes are welcome and feature requests may be considered.
4 |
5 | 1. Fork it
6 | 2. Create your feature branch (`git checkout -b my-new-feature`)
7 | 3. Commit your changes (`git commit -am 'Add some feature'`)
8 | 4. [Add tests for your changes](https://github.com/unleashedtech/php-coding-standard/blob/master/TESTING.md)
9 | 5. Push your changes to your feature branch (`git push origin my-new-feature`)
10 | 6. Create a new Pull Request (PR)
11 |
12 | ## Testing
13 | Contributions will only be accepted if they are fully tested as specified in [TESTING.md](https://github.com/unleashedtech/php-coding-standard/blob/master/TESTING.md).
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Unleashed Technologies
4 |
5 | Forked from doctrine/coding-standard, Copyright (c) 2013 Steve Müller
6 |
7 | FullyQualifiedGlobalFunctionsSniff based on soderlind/coding-standard, Copyright (c) 2020 Per Søderlind
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in all
17 | copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test phpcs test-report test-fix
2 |
3 | test: test-report test-fix
4 |
5 | phpcs:
6 | ./bin/phpcs
7 |
8 | test-report: vendor
9 | ./bin/test-report
10 |
11 | test-fix: vendor
12 | ./bin/test-fix
13 |
14 | vendor: composer.json
15 | composer update
16 | touch -c vendor
17 |
18 | update-tests-to-latest-standards: vendor
19 | ./bin/update-tests-to-latest-standards
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unleashed Technologies PHP_CodeSniffer Coding Standard
2 |
3 | [](https://packagist.org/packages/unleashedtech/php-coding-standard)
4 | [](https://packagist.org/packages/unleashedtech/php-coding-standard)
5 | [](https://travis-ci.org/unleashedtech/php-coding-standard)
6 | [](LICENSE)
7 |
8 | A PHP coding standard for Unleashed Technologies, originally based on [doctrine/coding-standard](https://github.com/doctrine/coding-standard).
9 |
10 | ## Overview
11 |
12 | This coding standard is based on [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md),
13 | with some noticeable exceptions/differences/extensions based on best-practices adopted by Symfony, Doctrine, and the wider community:
14 |
15 | - Keep the nesting of control structures per method as small as possible
16 | - Add spaces around a concatenation operator ``$foo = 'Hello ' . 'World!';``
17 | - Add spaces between assignment, control and return statements
18 | - Add spaces after the colon in return type declaration ``function (): void {}``
19 | - Add spaces after a type cast ``$foo = (int) '12345';``
20 | - Use single-quotes for enclosing strings
21 | - Always use strict comparisons
22 | - Always add ``declare(strict_types=1)`` at the beginning of a file
23 | - Always add native types where possible
24 | - Omit phpDoc for parameters/returns with native types, unless adding description
25 | - Don't use ``@author``, ``@since`` and similar annotations that duplicate Git information
26 | - Use parentheses when creating new instances that do not require arguments ``$foo = new Foo()``
27 | - Use Null Coalesce Operator ``$foo = $bar ?? $baz``
28 | - Use Null Safe Object Operator ``$foo = $object?->property``
29 | - Prefer early exit over nesting conditions or using else
30 | - Always use fully-qualified global functions (without needing `use function` statements)
31 | - Forbids the use of `\DateTime`
32 |
33 | For full reference of enforcements, go through ``src/Unleashed/ruleset.xml`` where each sniff is briefly described.
34 |
35 | ## Installation
36 |
37 | You can install the Unleashed Coding Standard as a [Composer](https://getcomposer.org/) dependency in your project:
38 |
39 | ```sh
40 | composer require --dev unleashedtech/php-coding-standard
41 | ```
42 |
43 | Then you can use it like this:
44 |
45 | ```sh
46 | vendor/bin/phpcs --standard=Unleashed /path/to/some/files.php
47 | ```
48 |
49 | You can also use `phpcbp` to automatically find and fix any violations:
50 |
51 | ```sh
52 | vendor/bin/phpcbf --standard=Unleashed /path/to/some/files.php
53 | ```
54 |
55 | ### Project-Level Ruleset
56 |
57 | To enable the Unleashed Coding Standard for your project, create a `phpcs.xml.dist` file with the following content:
58 |
59 | ```xml
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | src
73 | tests
74 |
75 |
76 |
77 |
78 | ```
79 |
80 |
81 | This will enable the full Unleashed Coding Standard with all rules included with their defaults.
82 | From now on you can just run `vendor/bin/phpcs` and `vendor/bin/phpcbf` without any arguments.
83 |
84 | Don't forget to add `.phpcs-cache` and `phpcs.xml` (without `.dist` suffix) to your `.gitignore`.
85 | The first ignored file is a cache used by PHP CodeSniffer to speed things up,
86 | the second one allows any developer to adjust configuration locally without touching the versioned file.
87 |
88 | For further reading about the CodeSniffer configuration, please refer to
89 | [the configuration format overview](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-Ruleset)
90 | and [the list of configuration options](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options).
91 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unleashedtech/php-coding-standard",
3 | "type": "phpcodesniffer-standard",
4 | "description": "CodeSniffer ruleset used by Unleashed Technologies",
5 | "keywords": ["coding standard", "phpcs"],
6 | "homepage": "https://github.com/unleashedtech/php-coding-standard",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Colin O'Dell",
11 | "email": "codell@unleashed-technologies.com"
12 | }
13 | ],
14 | "extra": {
15 | "branch-alias": {
16 | "dev-master": "3.0.x-dev"
17 | }
18 | },
19 | "support" : {
20 | "source": "https://github.com/unleashedtech/php-coding-standard",
21 | "issues": "https://github.com/unleashedtech/php-coding-standard/issues"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "Unleashed\\": "src/Unleashed"
26 | }
27 | },
28 | "autoload-dev": {
29 | "psr-4": {
30 | "Unleashed\\Tests\\": "tests"
31 | }
32 | },
33 | "require": {
34 | "php": "^8.1",
35 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0.0",
36 | "slevomat/coding-standard": "^8.11",
37 | "squizlabs/php_codesniffer": "^3.7"
38 | },
39 | "require-dev": {
40 | "symfony/phpunit-bridge": "^6.4"
41 | },
42 | "scripts": {
43 | "test": [
44 | "@phpcs",
45 | "./vendor/bin/simple-phpunit",
46 | "make test"
47 | ],
48 | "phpcs": "./vendor/bin/phpcs"
49 | },
50 | "config": {
51 | "allow-plugins": {
52 | "dealerdirect/phpcodesniffer-composer-installer": true
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Unleashed/Helpers/NamespaceHelper.php:
--------------------------------------------------------------------------------
1 | findNext(T_NAMESPACE, 0);
20 |
21 | return $token === false ? null : $token;
22 | };
23 |
24 | return SniffLocalCache::getAndSetIfNotCached($phpcsFile, 'firstNamespacePointer', $lazyValue);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Unleashed/Helpers/SniffLocalCache.php:
--------------------------------------------------------------------------------
1 | > */
17 | private static array $cache = [];
18 |
19 | public static function getAndSetIfNotCached(File $phpcsFile, string $key, \Closure $lazyValue): mixed
20 | {
21 | $fixerLoops = $phpcsFile->fixer?->loops ?? 0;
22 | $internalKey = \sprintf('%s-%s', $phpcsFile->getFilename(), $key);
23 |
24 | self::setIfNotCached($fixerLoops, $internalKey, $lazyValue);
25 |
26 | return self::$cache[$fixerLoops][$internalKey] ?? null;
27 | }
28 |
29 | private static function setIfNotCached(int $fixerLoops, string $internalKey, \Closure $lazyValue): void
30 | {
31 | if (
32 | \array_key_exists($fixerLoops, self::$cache) &&
33 | \array_key_exists($internalKey, self::$cache[$fixerLoops])
34 | ) {
35 | return;
36 | }
37 |
38 | self::$cache[$fixerLoops][$internalKey] = $lazyValue();
39 |
40 | if ($fixerLoops > 0) {
41 | unset(self::$cache[$fixerLoops - 1]);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Unleashed/Helpers/UseStatements.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | public static function getAliasesAndNonGlobalFunctionsDefinedInUseStatements(File $file): array
20 | {
21 | $lazyValue = static function () use ($file): array {
22 | $result = [];
23 |
24 | foreach (UseStatementHelper::getFileUseStatements($file) as $useStatements) {
25 | foreach ($useStatements as $useStatement) {
26 | \assert($useStatement instanceof UseStatement);
27 | if ($useStatement->getType() !== 'function') {
28 | continue;
29 | }
30 |
31 | if (
32 | $useStatement->getAlias() !== null
33 | || \strpos($useStatement->getFullyQualifiedTypeName(), '\\') !== false
34 | ) {
35 | $result[$useStatement->getCanonicalNameAsReferencedInFile()] = true;
36 | }
37 | }
38 | }
39 |
40 | return $result;
41 | };
42 |
43 | return SniffLocalCache::getAndSetIfNotCached($file, __METHOD__, $lazyValue);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Unleashed/Sniffs/Commenting/ForbiddenSingleLineCommentsSniff.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | public function register(): array
22 | {
23 | return [
24 | T_COMMENT,
25 | ];
26 | }
27 |
28 | /**
29 | * @param int $stackPtr
30 | *
31 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
32 | */
33 | public function process(File $phpcsFile, $stackPtr): void
34 | {
35 | $tokens = $phpcsFile->getTokens();
36 |
37 | $content = $tokens[$stackPtr]['content'];
38 |
39 | foreach (SniffSettingsHelper::normalizeArray($this->forbiddenCommentPatterns) as $forbiddenCommentPattern) {
40 | if (! SniffSettingsHelper::isValidRegularExpression($forbiddenCommentPattern)) {
41 | throw new \Exception(\sprintf('%s is not valid PCRE pattern.', $forbiddenCommentPattern));
42 | }
43 |
44 | if (\preg_match($forbiddenCommentPattern, $content) === 0) {
45 | continue;
46 | }
47 |
48 | $fix = $phpcsFile->addFixableError(
49 | \sprintf('Code contains forbidden comment "%s".', \trim($content)),
50 | $stackPtr,
51 | self::CODE_COMMENT_FORBIDDEN,
52 | );
53 |
54 | if (! $fix) {
55 | continue;
56 | }
57 |
58 | $phpcsFile->fixer->beginChangeset();
59 | $fixedContent = \preg_replace($forbiddenCommentPattern, '', $content);
60 | $phpcsFile->fixer->replaceToken($stackPtr, $fixedContent);
61 | $phpcsFile->fixer->endChangeset();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Unleashed/Sniffs/Commenting/InheritDocFormatSniff.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public function register(): array
23 | {
24 | return [
25 | T_DOC_COMMENT_OPEN_TAG,
26 | ];
27 | }
28 |
29 | /**
30 | * @param int $stackPtr
31 | *
32 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
33 | */
34 | public function process(File $phpcsFile, $stackPtr): void
35 | {
36 | $tokens = $phpcsFile->getTokens();
37 |
38 | for ($i = $stackPtr + 1; $i < $tokens[$stackPtr]['comment_closer']; $i++) {
39 | if (\in_array($tokens[$i]['code'], [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR], true)) {
40 | continue;
41 | }
42 |
43 | $content = $tokens[$i]['content'];
44 |
45 | if (\preg_match('~^(?:{@inheritDoc}|@inheritDoc)$~i', $content) === 0) {
46 | continue;
47 | }
48 |
49 | $fixed = \preg_replace('~({@inheritDoc}|@inheritDoc)~i', $this->style, $content);
50 | if ($content === $fixed) {
51 | continue;
52 | }
53 |
54 | $fix = $phpcsFile->addFixableError(
55 | \sprintf('Incorrect formatting of "%s"', $this->style),
56 | $i,
57 | self::CODE_INVALID_INHERITDOC_STYLE,
58 | );
59 |
60 | if (! $fix) {
61 | return;
62 | }
63 |
64 | $phpcsFile->fixer->beginChangeset();
65 | $phpcsFile->fixer->replaceToken($i, $fixed);
66 | $phpcsFile->fixer->endChangeset();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Unleashed/Sniffs/DoctrineMigrations/DescriptionRequiredSniff.php:
--------------------------------------------------------------------------------
1 | getTokens();
35 |
36 | $parentClassPtr = TokenHelper::findNext($phpcsFile, T_STRING, $stackPtr);
37 | if ($parentClassPtr === null) {
38 | return;
39 | }
40 |
41 | // Only continue if we've found a Doctrine Migration class
42 | $parentClass = NamespaceHelper::resolveClassName($phpcsFile, $tokens[$parentClassPtr]['content'], $stackPtr);
43 | if ($parentClass !== '\\Doctrine\\Migrations\\AbstractMigration') {
44 | return;
45 | }
46 |
47 | // Does a `getDescription()` method exist?
48 | $classEndPtr = self::findApproximateClassEndPointer($phpcsFile, $parentClassPtr);
49 | $methodPtr = self::findMethodInClass($phpcsFile, 'getDescription', $parentClassPtr, $classEndPtr);
50 | if ($methodPtr === null) {
51 | // Nope - method is missing
52 | $fix = $phpcsFile->addFixableError(
53 | 'Doctrine Migrations must have a getDescription() method.',
54 | TokenHelper::findPrevious($phpcsFile, T_CLASS, $stackPtr),
55 | self::CODE_MISSING_DESCRIPTION,
56 | );
57 |
58 | if ($fix) {
59 | $phpcsFile->fixer->beginChangeset();
60 | $phpcsFile->fixer->addContent(
61 | TokenHelper::findNext($phpcsFile, T_OPEN_CURLY_BRACKET, $stackPtr),
62 | "\n public function getDescription(): string\n {\n return '';\n }",
63 | );
64 | $phpcsFile->fixer->endChangeset();
65 | }
66 |
67 | return;
68 | }
69 |
70 | // Does the method have a description?
71 | $returnValuePtr = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $methodPtr + 1, null, false, null, true);
72 | if ($tokens[$returnValuePtr]['content'] !== '""' && $tokens[$returnValuePtr]['content'] !== "''") {
73 | return;
74 | }
75 |
76 | $phpcsFile->addError(
77 | 'Doctrine Migrationss must return useful information from getDescription(); empty strings not allowed',
78 | $returnValuePtr,
79 | self::CODE_EMPTY_DESCRIPTION,
80 | );
81 | }
82 |
83 | private static function findMethodInClass(File $phpcsFile, string $methodName, int $startPtr, int $endPtr): int|null
84 | {
85 | do {
86 | $nextFunctionPointer = TokenHelper::findNext($phpcsFile, T_FUNCTION, $startPtr + 1);
87 | if ($nextFunctionPointer === null) {
88 | break;
89 | }
90 |
91 | if ($nextFunctionPointer >= $endPtr) {
92 | return null;
93 | }
94 |
95 | $startPtr = $nextFunctionPointer;
96 |
97 | if (! FunctionHelper::isMethod($phpcsFile, $startPtr)) {
98 | continue;
99 | }
100 |
101 | $name = FunctionHelper::getName($phpcsFile, $nextFunctionPointer);
102 |
103 | if ($name !== $methodName) {
104 | continue;
105 | }
106 |
107 | return $nextFunctionPointer;
108 | } while (true);
109 |
110 | return null;
111 | }
112 |
113 | private static function findApproximateClassEndPointer(File $phpcsFile, int $ptrWithinClass): int|null
114 | {
115 | $classPtrs = \array_keys(ClassHelper::getAllNames($phpcsFile));
116 |
117 | while (\current($classPtrs) !== false) {
118 | $start = \current($classPtrs);
119 | $end = \next($classPtrs);
120 |
121 | if ($start <= $ptrWithinClass && $end >= $ptrWithinClass) {
122 | return $end;
123 | }
124 | }
125 |
126 | return $phpcsFile->numTokens;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Unleashed/Sniffs/Namespaces/FullyQualifiedGlobalFunctionsSniff.php:
--------------------------------------------------------------------------------
1 | */
18 | private array $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 | * @param int $stackPtr
82 | *
83 | * {@inheritDoc}
84 | *
85 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
86 | */
87 | public function process(File $phpcsFile, $stackPtr): void
88 | {
89 | // Abort if we're not in namespaced code
90 | $firstNamespacePointer = NamespaceHelper::getFirstNamespacePointer($phpcsFile);
91 | if ($firstNamespacePointer === null || $stackPtr < $firstNamespacePointer) {
92 | return;
93 | }
94 |
95 | if (
96 | $this->onlyOptimizedFunctions !== null
97 | && \filter_var($this->onlyOptimizedFunctions, FILTER_VALIDATE_BOOLEAN) !== false
98 | ) {
99 | $globalFunctions = $this->optimizedFunctions;
100 | } else {
101 | $globalFunctions = \array_flip(\get_defined_functions()['internal']);
102 | }
103 |
104 | $whitelist = UseStatements::getAliasesAndNonGlobalFunctionsDefinedInUseStatements($phpcsFile);
105 |
106 | $tokens = $phpcsFile->getTokens();
107 | $ignore = [
108 | T_DOUBLE_COLON => true,
109 | T_OBJECT_OPERATOR => true,
110 | T_FUNCTION => true,
111 | T_CONST => true,
112 | T_PUBLIC => true,
113 | T_PRIVATE => true,
114 | T_PROTECTED => true,
115 | T_AS => true,
116 | T_NEW => true,
117 | T_INSTEADOF => true,
118 | T_NS_SEPARATOR => true,
119 | T_IMPLEMENTS => true,
120 | ];
121 | $prevToken = $phpcsFile->findPrevious([T_WHITESPACE, T_COMMENT], $stackPtr - 1, null, true);
122 |
123 | // If function call is directly preceded by a NS_SEPARATOR don't try to fix it.
124 | if ($tokens[$prevToken]['code'] === T_NS_SEPARATOR && $tokens[$stackPtr]['code'] === T_STRING) {
125 | return;
126 | }
127 |
128 | if (isset($ignore[$tokens[$prevToken]['code']]) === true) {
129 | // Not a call to a PHP function.
130 | return;
131 | }
132 |
133 | $nextToken = $phpcsFile->findNext([T_WHITESPACE, T_COMMENT], $stackPtr + 1, null, true);
134 | if (isset($ignore[$tokens[$nextToken]['code']]) === true) {
135 | // Not a call to a PHP function.
136 | return;
137 | }
138 |
139 | if ($tokens[$nextToken]['code'] !== T_OPEN_PARENTHESIS) {
140 | // Not a call to a PHP function.
141 | return;
142 | }
143 |
144 | $function = \strtolower($tokens[$stackPtr]['content']);
145 | $functionNormalized = \strtolower($function);
146 |
147 | // Is it a whitelisted alias?
148 | if (isset($whitelist[$functionNormalized])) {
149 | return;
150 | }
151 |
152 | // Is it an global PHP function?
153 | if (isset($globalFunctions[$functionNormalized]) === false) {
154 | return;
155 | }
156 |
157 | $error = \sprintf('Function %1$s() should be referenced via a fully qualified name, e.g.: \%1$s()', $function);
158 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotFullyQualified');
159 |
160 | if ($fix === true) {
161 | $this->applyFix($phpcsFile, $stackPtr, $function);
162 | }
163 | }
164 |
165 | private function applyFix(File $phpcsFile, int $stackPtr, string $function): void
166 | {
167 | // This sniff conflicts with ModernClassNameReferenceSniff, so don't bother fixing things it will attempt to fix
168 | if (
169 | \array_key_exists(ModernClassNameReferenceSniff::class, $phpcsFile->ruleset->sniffs)
170 | && \in_array($function, ['get_class', 'get_parent_class', 'get_called_class'], true)
171 | ) {
172 | return;
173 | }
174 |
175 | $phpcsFile->fixer->beginChangeset();
176 | $phpcsFile->fixer->addContentBefore($stackPtr, '\\');
177 | $phpcsFile->fixer->endChangeset();
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Unleashed/Sniffs/PHP/ForbiddenClassesSniff.php:
--------------------------------------------------------------------------------
1 | getStartPointer();
56 | $name = $referencedName->getNameAsReferencedInFile();
57 |
58 | $fullyQualifiedName = NamespaceHelper::resolveClassName($phpcsFile, $name, $pointer);
59 |
60 | if (! \in_array($fullyQualifiedName, $this->forbiddenClasses, true)) {
61 | continue;
62 | }
63 |
64 | $error = \sprintf('The use of "%s" is forbidden', $fullyQualifiedName);
65 | if ($this->error) {
66 | $phpcsFile->addError($error, $pointer, self::FORBIDDEN);
67 | } else {
68 | $phpcsFile->addWarning($error, $pointer, self::FORBIDDEN);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Unleashed/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 | The Unleashed Technologies PHP coding standard.
8 |
9 |
10 | *.css
11 | *.js
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | error
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
204 |
205 |
210 |
211 |
212 |
213 |
214 |
215 |
220 |
221 |
229 |
235 |
236 |
242 |
243 |
244 |
245 |
250 |
255 |
256 |
264 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
481 |
485 |
489 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 | Variable "%s" not allowed in double quoted string; use sprintf() or concatenation instead
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 | 5
719 |
720 |
721 |
--------------------------------------------------------------------------------