├── .qlty
├── .gitignore
├── configs
│ └── .yamllint.yaml
└── qlty.toml
├── MO4
├── Tests
│ ├── Formatting
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.3.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.4.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.5.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.2.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.pass.1.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.1.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.1.inc.fixed
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.2.inc.fixed
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.2.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.6.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.6.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.4.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.4.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.4.inc.fixed
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.3.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.3.inc.fixed
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.2.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.fail.2.inc.fixed
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.3.inc.fixed
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.3.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.pass.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.1.inc.fixed
│ │ ├── UnnecessaryNamespaceUsageUnitTest.pass.1.inc
│ │ ├── UnnecessaryNamespaceUsageUnitTest.fail.1.inc
│ │ ├── AlphabeticalUseStatementsUnitTest.php
│ │ └── UnnecessaryNamespaceUsageUnitTest.php
│ ├── Strings
│ │ ├── VariableInDoubleQuotedStringUnitTest.fail.inc
│ │ ├── VariableInDoubleQuotedStringUnitTest.fail.inc.fixed
│ │ ├── VariableInDoubleQuotedStringUnitTest.pass.inc
│ │ └── VariableInDoubleQuotedStringUnitTest.php
│ ├── Arrays
│ │ ├── MultiLineArrayUnitTest.pass.inc
│ │ ├── MultiLineArrayUnitTest.fail.inc
│ │ ├── MultiLineArrayUnitTest.fail.inc.fixed
│ │ ├── MultiLineArrayUnitTest.php
│ │ ├── ArrayDoubleArrowAlignmentUnitTest.php
│ │ ├── ArrayDoubleArrowAlignmentUnitTest.pass.inc
│ │ ├── ArrayDoubleArrowAlignmentUnitTest.fail.inc
│ │ └── ArrayDoubleArrowAlignmentUnitTest.fail.inc.fixed
│ ├── WhiteSpace
│ │ ├── MultipleEmptyLinesUnitTest.pass.inc
│ │ ├── MultipleEmptyLinesUnitTest.fail.inc.fixed
│ │ ├── MultipleEmptyLinesUnitTest.fail.inc
│ │ ├── ConstantSpacingUnitTest.pass.inc
│ │ ├── ConstantSpacingUnitTest.fail.inc.fixed
│ │ ├── ConstantSpacingUnitTest.fail.inc
│ │ ├── MultipleEmptyLinesUnitTest.php
│ │ └── ConstantSpacingUnitTest.php
│ ├── Commenting
│ │ ├── PropertyCommentUnitTest.fail.inc
│ │ ├── PropertyCommentUnitTest.fail.inc.fixed
│ │ ├── PropertyCommentUnitTest.pass.inc
│ │ └── PropertyCommentUnitTest.php
│ └── AbstractMo4SniffUnitTest.php
├── Library
│ └── PregLibrary.php
├── Sniffs
│ ├── WhiteSpace
│ │ ├── ConstantSpacingSniff.php
│ │ └── MultipleEmptyLinesSniff.php
│ ├── Arrays
│ │ ├── MultiLineArraySniff.php
│ │ └── ArrayDoubleArrowAlignmentSniff.php
│ ├── Strings
│ │ └── VariableInDoubleQuotedStringSniff.php
│ ├── Commenting
│ │ └── PropertyCommentSniff.php
│ └── Formatting
│ │ ├── AlphabeticalUseStatementsSniff.php
│ │ └── UnnecessaryNamespaceUsageSniff.php
└── ruleset.xml
├── phpstan.neon
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE.md
├── CONTRIBUTING.md
└── workflows
│ └── ci.yml
├── .pinact.yaml
├── phpcs.mo4.xml
├── tests
├── static_analysis_bootstrap.php
└── bootstrap.php
├── phpcs.xml.dist
├── psalm.xml
├── .gitignore
├── phpunit.xml.dist
├── LICENSE
├── .phan
└── config.php
├── composer.json
├── integrationtests
└── testfile.php
├── CHANGELOG.md
└── README.md
/.qlty/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !configs
3 | !configs/**
4 | !hooks
5 | !hooks/**
6 | !qlty.toml
7 | !.gitignore
8 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/UnnecessaryNamespaceUsageUnitTest.pass.3.inc:
--------------------------------------------------------------------------------
1 | b();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MO4/Tests/Strings/VariableInDoubleQuotedStringUnitTest.fail.inc:
--------------------------------------------------------------------------------
1 | bar();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MO4/Tests/Strings/VariableInDoubleQuotedStringUnitTest.fail.inc.fixed:
--------------------------------------------------------------------------------
1 |
12 |
13 | ### Description
14 |
15 | Please explain what you want to change and why.
16 | Additional information about the code and the commits
17 | may be helpful as well.
18 |
--------------------------------------------------------------------------------
/MO4/Tests/WhiteSpace/MultipleEmptyLinesUnitTest.pass.inc:
--------------------------------------------------------------------------------
1 |
2 |
3 | The coding standard for PHP_CodeSniffer itself.
4 |
5 |
6 | MO4
7 | tests
8 |
9 | */Tests/*\.(inc|css|js)
10 | vendor/*
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/MO4/Tests/WhiteSpace/MultipleEmptyLinesUnitTest.fail.inc:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * @license http://spdx.org/licenses/MIT MIT License
11 | *
12 | * @link https://github.com/mayflower/mo4-coding-standard
13 | */
14 |
15 | declare(strict_types=1);
16 |
17 | require_once __DIR__.'/../vendor/squizlabs/php_codesniffer/autoload.php';
18 | require_once __DIR__.'/../vendor/squizlabs/php_codesniffer/src/Util/Tokens.php';
19 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/AlphabeticalUseStatementsUnitTest.fail.2.inc:
--------------------------------------------------------------------------------
1 |
2 |
3 | The coding standard for PHP_CodeSniffer itself, applied to MO4.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /vendor/
3 | /composer.phar
4 | composer.lock
5 |
6 | # Build & Tests
7 | /build/
8 | .atoum.php
9 | phpunit.xml
10 | .phpunit.result.cache
11 | behat.yml
12 |
13 | # OSX
14 | .DS_Store
15 | .AppleDouble
16 | .LSOverride
17 | Icon
18 | .Spotlight-V100
19 | .Trashes
20 |
21 | # Windows image file caches
22 | Thumbs.db
23 | ehthumbs.db
24 | Desktop.ini
25 | $RECYCLE.BIN/
26 |
27 | # Netbeans
28 | nbproject/private/
29 | build/
30 | nbbuild/
31 | dist/
32 | nbdist/
33 | nbactions.xml
34 | nb-configuration.xml
35 |
36 | # Phpstorm
37 | .idea/
38 |
39 | # SublimeText
40 | *.sublime-workspace
41 |
42 | # TextMate
43 | *.tmproj
44 | *.tmproject
45 | tmtags
46 |
47 | #vi & emacs
48 | *~
49 |
--------------------------------------------------------------------------------
/MO4/Tests/Strings/VariableInDoubleQuotedStringUnitTest.pass.inc:
--------------------------------------------------------------------------------
1 | getComputedPath($inputFile->getKey())}\n";
17 | $k = "{$a[$b]}";
18 | $l = "{$a[$b->foo()]}";
19 | $m = "{$a[$b->foo($c)]}";
20 | $n = "{$a[$b[0]][$b[0]]}";
21 | $o = "{$a['foo'][$b[0]]}";
22 | $p = "{$a[$b->foo($c['foo'][$d[0]])]}";
23 | $p = "{$good[$fail]}";
24 | $q = "{$prefix}/{$this->resolveName($id)}";
25 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | vendor/squizlabs/php_codesniffer/tests/Standards/AllSniffs.php
12 |
13 |
14 |
15 |
16 | ./MO4/Sniffs
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/MultiLineArrayUnitTest.fail.inc:
--------------------------------------------------------------------------------
1 | [
8 | 'object_id' => 1,
9 | 'object_class' => 'Test',
10 | 'object_class_name' => 'TestClass',
11 | 'object_name' => 'TestObject',
12 | 'id' => 'Test:1',],
13 | 'Test:2' => [
14 | 'object_id' => 2,
15 | 'object_class' => 'Test',
16 | 'object_class_name' => 'TestClass',
17 | 'object_name' => 'TestObject',
18 | 'id' => 'Test:2',],];
19 |
20 |
21 | $fail = array(
22 | 1);
23 |
24 | $fail = array(1,
25 | 2,
26 | );
27 |
28 | $fail = [ 1,
29 | 2,
30 | ];
31 |
32 | $fail = array( // fail
33 | );
34 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/UnnecessaryNamespaceUsageUnitTest.fail.3.inc.fixed:
--------------------------------------------------------------------------------
1 | [
9 | 'object_id' => 1,
10 | 'object_class' => 'Test',
11 | 'object_class_name' => 'TestClass',
12 | 'object_name' => 'TestObject',
13 | 'id' => 'Test:1',
14 | ],
15 | 'Test:2' => [
16 | 'object_id' => 2,
17 | 'object_class' => 'Test',
18 | 'object_class_name' => 'TestClass',
19 | 'object_name' => 'TestObject',
20 | 'id' => 'Test:2',
21 | ],
22 | ];
23 |
24 |
25 | $fail = array(
26 | 1
27 | );
28 |
29 | $fail = array(
30 | 1,
31 | 2,
32 | );
33 |
34 | $fail = [
35 | 1,
36 | 2,
37 | ];
38 |
39 | $fail = array(
40 | // fail
41 | );
42 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/UnnecessaryNamespaceUsageUnitTest.fail.3.inc:
--------------------------------------------------------------------------------
1 | bar arrays
14 | */
15 | private $_privateProperty = [['foo' => 'bar']];
16 |
17 | /**
18 | * @var
19 | *
20 | * foo
21 | *
22 | * @var array
23 | */
24 | private $_tmi;
25 |
26 | /** stuff */
27 | private $_moar;
28 |
29 | /*
30 | * no doc block
31 | */
32 | private $_priv;
33 |
34 | // no doc block
35 | private $_otherPriv;
36 |
37 | private $z = null; /** invalid
38 | multiline
39 | comment */
40 |
41 | private $y = null; /** invalid doc block after declaration */
42 | private $q = null; /* valid single line comment */
43 | private $x = null;
44 | private $u = null; /* invalid
45 | multiline
46 | comment */
47 | }
48 |
--------------------------------------------------------------------------------
/MO4/Tests/Commenting/PropertyCommentUnitTest.fail.inc.fixed:
--------------------------------------------------------------------------------
1 | bar arrays
16 | */
17 | private $_privateProperty = [['foo' => 'bar']];
18 |
19 | /**
20 | * @var
21 | *
22 | * foo
23 | *
24 | * @var array
25 | */
26 | private $_tmi;
27 |
28 | /** stuff */
29 | private $_moar;
30 |
31 | /*
32 | * no doc block
33 | */
34 | private $_priv;
35 |
36 | // no doc block
37 | private $_otherPriv;
38 |
39 | private $z = null; /** invalid
40 | multiline
41 | comment */
42 |
43 | private $y = null; /** invalid doc block after declaration */
44 | private $q = null; /* valid single line comment */
45 | private $x = null;
46 | private $u = null; /* invalid
47 | multiline
48 | comment */
49 | }
50 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/AlphabeticalUseStatementsUnitTest.pass.inc:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * @license http://spdx.org/licenses/MIT MIT License
11 | *
12 | * @link https://github.com/mayflower/mo4-coding-standard
13 | */
14 |
15 | declare(strict_types=1);
16 |
17 | use PHP_CodeSniffer\Config;
18 | use PHP_CodeSniffer\Util\Standards;
19 |
20 | $myStandardName = 'MO4';
21 |
22 | require_once __DIR__.'/../vendor/squizlabs/php_codesniffer/tests/bootstrap.php';
23 |
24 | // Add this Standard.
25 | Config::setConfigData(
26 | 'installed_paths',
27 | __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.$myStandardName,
28 | true
29 | );
30 |
31 | // Ignore all other Standards in tests.
32 | $standards = Standards::getInstalledStandards();
33 | $standards[] = 'Generic';
34 |
35 | $ignoredStandardsStr = implode(
36 | ',',
37 | array_filter(
38 | $standards,
39 | static function ($v) use ($myStandardName): bool {
40 | return $v !== $myStandardName;
41 | }
42 | )
43 | );
44 |
45 | putenv("PHPCS_IGNORE_TESTS={$ignoredStandardsStr}");
46 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/UnnecessaryNamespaceUsageUnitTest.fail.1.inc:
--------------------------------------------------------------------------------
1 | bar arrays
23 | *
24 | * @var array
25 | */
26 | private $_privateProperty = [['foo' => 'bar']];
27 |
28 | /**
29 | * @var int
30 | *
31 | * @ORM\Column(name="SECT_ID", type="integer", nullable=false)
32 | * @ORM\Id
33 | */
34 | protected $id;
35 |
36 | /**
37 | * foo
38 | */
39 | public function foo()
40 | {
41 | $foo = 2;
42 | /** @var int $bla */
43 | $bla = 1;
44 | /** @var int $bla */
45 |
46 | return ($bla === $foo); /* valid single line comment */
47 | }
48 |
49 | private $_endThing;
50 |
51 | /**
52 | * stuff
53 | */
54 | const STUFF = 1;
55 |
56 | public $stuff = [self::STUFF];
57 | }
58 |
--------------------------------------------------------------------------------
/MO4/Tests/WhiteSpace/MultipleEmptyLinesUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\WhiteSpace;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the MultipleEmptyLines sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class MultipleEmptyLinesUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'MultipleEmptyLinesUnitTest.pass.inc' => [],
37 | 'MultipleEmptyLinesUnitTest.fail.inc' => [
38 | 2 => 1,
39 | 14 => 1,
40 | 21 => 1,
41 | 24 => 1,
42 | 29 => 1,
43 | ],
44 | ];
45 | }
46 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/MultiLineArrayUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Arrays;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for @see MultiLineArraySniff
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class MultiLineArrayUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'MultiLineArrayUnitTest.pass.inc' => [],
37 | 'MultiLineArrayUnitTest.fail.inc' => [
38 | 4 => 1,
39 | 12 => 1,
40 | 18 => 2,
41 | 22 => 1,
42 | 24 => 1,
43 | 28 => 1,
44 | 32 => 1,
45 | ],
46 | ];
47 | }
48 |
--------------------------------------------------------------------------------
/MO4/Tests/Commenting/PropertyCommentUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Commenting;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the AlphabeticalUseStatements sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2014-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class PropertyCommentUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'PropertyCommentUnitTest.pass.inc' => [],
37 | 'PropertyCommentUnitTest.fail.inc' => [
38 | 7 => 1,
39 | 10 => 1,
40 | 17 => 1,
41 | 26 => 2,
42 | 29 => 1,
43 | 34 => 1,
44 | 37 => 2,
45 | 41 => 1,
46 | 44 => 1,
47 | ],
48 | ];
49 | }
50 |
--------------------------------------------------------------------------------
/MO4/Tests/Strings/VariableInDoubleQuotedStringUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Strings;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the VariableInDoubleQuotedString sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class VariableInDoubleQuotedStringUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'VariableInDoubleQuotedStringUnitTest.pass.inc' => [],
37 | 'VariableInDoubleQuotedStringUnitTest.fail.inc' => [
38 | 3 => 1,
39 | 4 => 1,
40 | 5 => 2,
41 | 6 => 2,
42 | 7 => 1,
43 | 8 => 1,
44 | 9 => 1,
45 | 10 => 1,
46 | 11 => 1,
47 | ],
48 | ];
49 | }
50 |
--------------------------------------------------------------------------------
/MO4/Tests/WhiteSpace/ConstantSpacingUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\WhiteSpace;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the VariableInDoubleQuotedString sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class ConstantSpacingUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'ConstantSpacingUnitTest.pass.inc' => [],
37 | 'ConstantSpacingUnitTest.fail.inc' => [
38 | 4 => 1,
39 | 5 => 1,
40 | 6 => 1,
41 | 10 => 1,
42 | 12 => 1,
43 | 13 => 1,
44 | 14 => 1,
45 | 15 => 1,
46 | 18 => 1,
47 | 22 => 1,
48 | 23 => 1,
49 | 24 => 1,
50 | ],
51 | ];
52 | }
53 |
--------------------------------------------------------------------------------
/.phan/config.php:
--------------------------------------------------------------------------------
1 | null,
14 |
15 | // A list of directories that should be parsed for class and
16 | // method information. After excluding the directories
17 | // defined in exclude_analysis_directory_list, the remaining
18 | // files will be statically analyzed for errors.
19 | //
20 | // Thus, both first-party and third-party code being used by
21 | // your application should be included in this list.
22 | 'directory_list' => [
23 | 'MO4',
24 | 'tests',
25 | 'vendor/squizlabs/php_codesniffer',
26 | 'vendor/symfony/polyfill-php83',
27 | ],
28 |
29 | // A directory list that defines files that will be excluded
30 | // from static analysis, but whose class and method
31 | // information should be included.
32 | //
33 | // Generally, you'll want to include the directories for
34 | // third-party code (such as "vendor/") in this list.
35 | //
36 | // n.b.: If you'd like to parse but not analyze 3rd
37 | // party code, directories containing that code
38 | // should be added to both the `directory_list`
39 | // and `exclude_analysis_directory_list` arrays.
40 | "exclude_analysis_directory_list" => [
41 | 'vendor/'
42 | ],
43 | ];
44 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mayflower/mo4-coding-standard",
3 | "description": "PHP CodeSniffer ruleset implementing the MO4 coding standards extending the Symfony coding standards.",
4 | "license": "MIT",
5 | "type": "phpcodesniffer-standard",
6 | "keywords": [
7 | "phpcs",
8 | "static analysis",
9 | "Symfony",
10 | "mo4",
11 | "standards",
12 | "PSR"
13 | ],
14 | "authors": [
15 | {
16 | "name": "Xaver Loppenstedt",
17 | "email": "xaver@loppenstedt.de"
18 | }
19 | ],
20 | "homepage": "https://github.com/mayflower/mo4-coding-standard",
21 | "support": {
22 | "issues": "https://github.com/mayflower/mo4-coding-standard/issues",
23 | "source": "https://github.com/mayflower/mo4-coding-standard"
24 | },
25 | "require": {
26 | "php": "^8.1",
27 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
28 | "escapestudios/symfony2-coding-standard": "^3.16.0",
29 | "slevomat/coding-standard": "^8.14",
30 | "squizlabs/php_codesniffer": "^3.8.0"
31 | },
32 | "require-dev": {
33 | "ergebnis/composer-normalize": "^2.45",
34 | "nikic/php-parser": "< 5.0.1",
35 | "phan/phan": "^5.4.5",
36 | "phpstan/phpstan": "^2.0",
37 | "phpstan/phpstan-strict-rules": "^2.0",
38 | "phpunit/phpunit": "^9.6.15",
39 | "psalm/plugin-phpunit": "^0.19",
40 | "sabre/event": ">= 5.1.6",
41 | "symfony/filesystem": ">= 5.4.45",
42 | "symfony/polyfill-php83": "^1.32",
43 | "vimeo/psalm": "^6.0.0"
44 | },
45 | "config": {
46 | "allow-plugins": {
47 | "dealerdirect/phpcodesniffer-composer-installer": true,
48 | "ergebnis/composer-normalize": true
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.qlty/qlty.toml:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by `qlty init`.
2 | # You can modify it to suit your needs.
3 | # We recommend you to commit this file to your repository.
4 | #
5 | # This configuration is used by both Qlty CLI and Qlty Cloud.
6 | #
7 | # Qlty CLI -- Code quality toolkit for developers
8 | # Qlty Cloud -- Fully automated Code Health Platform
9 | #
10 | # Try Qlty Cloud: https://qlty.sh
11 | #
12 | # For a guide to configuration, visit https://qlty.sh/d/config
13 | # Or for a full reference, visit https://qlty.sh/d/qlty-toml
14 | config_version = "0"
15 |
16 | exclude_patterns = [
17 | "*_min.*",
18 | "*-min.*",
19 | "*.min.*",
20 | "**/.yarn/**",
21 | "**/*.d.ts",
22 | "**/assets/**",
23 | "**/bower_components/**",
24 | "**/build/**",
25 | "**/cache/**",
26 | "**/config/**",
27 | "**/db/**",
28 | "**/deps/**",
29 | "**/dist/**",
30 | "**/extern/**",
31 | "**/external/**",
32 | "**/generated/**",
33 | "**/Godeps/**",
34 | "**/gradlew/**",
35 | "**/mvnw/**",
36 | "**/node_modules/**",
37 | "**/protos/**",
38 | "**/seed/**",
39 | "**/target/**",
40 | "**/templates/**",
41 | "**/testdata/**",
42 | "**/vendor/**", "integrationtests/",
43 | ]
44 |
45 | test_patterns = [
46 | "**/test/**",
47 | "**/spec/**",
48 | "**/*.test.*",
49 | "**/*.spec.*",
50 | "**/*_test.*",
51 | "**/*_spec.*",
52 | "**/test_*.*",
53 | "**/spec_*.*",
54 | ]
55 |
56 | [smells]
57 | mode = "comment"
58 |
59 | [smells.boolean_logic]
60 | threshold = 4
61 |
62 | [smells.file_complexity]
63 | threshold = 55
64 |
65 | [smells.return_statements]
66 | threshold = 4
67 | enabled = false
68 |
69 | [smells.nested_control_flow]
70 | threshold = 4
71 |
72 | [smells.function_parameters]
73 | threshold = 4
74 |
75 | [smells.function_complexity]
76 | threshold = 16
77 |
78 | [smells.duplication]
79 | enabled = true
80 | threshold = 20
81 |
82 | [[source]]
83 | name = "default"
84 | default = true
85 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/ArrayDoubleArrowAlignmentUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Arrays;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for @see ArrayDoubleArrowAlignmentSniff
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class ArrayDoubleArrowAlignmentUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'ArrayDoubleArrowAlignmentUnitTest.pass.inc' => [],
37 | 'ArrayDoubleArrowAlignmentUnitTest.fail.inc' => [
38 | 5 => 1,
39 | 10 => 1,
40 | 17 => 2,
41 | 18 => 2,
42 | 22 => 1,
43 | 28 => 1,
44 | 38 => 1,
45 | 43 => 1,
46 | 45 => 1,
47 | 49 => 1,
48 | 51 => 1,
49 | 58 => 1,
50 | 59 => 1,
51 | 61 => 1,
52 | 67 => 1,
53 | 70 => 1,
54 | 71 => 1,
55 | 73 => 1,
56 | 82 => 1,
57 | 83 => 1,
58 | 85 => 1,
59 | 93 => 1,
60 | 94 => 1,
61 | 97 => 1,
62 | 105 => 1,
63 | 130 => 1,
64 | 132 => 1,
65 | 134 => 1,
66 | 136 => 2,
67 | 140 => 1,
68 | 141 => 1,
69 | 145 => 2,
70 | 149 => 1,
71 | ],
72 | ];
73 | }
74 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/AlphabeticalUseStatementsUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Formatting;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the AlphabeticalUseStatements sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | */
33 | final class AlphabeticalUseStatementsUnitTest extends AbstractMo4SniffUnitTest
34 | {
35 | protected $expectedErrorList = [
36 | 'AlphabeticalUseStatementsUnitTest.pass.inc' => [],
37 | 'AlphabeticalUseStatementsUnitTest.pass.1.inc' => [],
38 | 'AlphabeticalUseStatementsUnitTest.fail.1.inc' => [
39 | 4 => 1,
40 | 5 => 1,
41 | 8 => 1,
42 | 9 => 1,
43 | 12 => 1,
44 | ],
45 | // Take care, more than one fix will be applied.
46 | 'AlphabeticalUseStatementsUnitTest.fail.2.inc' => [
47 | 6 => 1,
48 | 8 => 1,
49 | ],
50 | 'AlphabeticalUseStatementsUnitTest.fail.3.inc' => [
51 | 7 => 1,
52 | 8 => 1,
53 | 10 => 1,
54 | 15 => 1,
55 | ],
56 | 'AlphabeticalUseStatementsUnitTest.fail.4.inc' => [
57 | 4 => 1,
58 | 8 => 1,
59 | 13 => 1,
60 | 17 => 1,
61 | 20 => 1,
62 | 21 => 1,
63 | ],
64 | 'AlphabeticalUseStatementsUnitTest.fail.6.inc' => [5 => 1],
65 | ];
66 | }
67 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | If you contribute code, please make sure it conforms to the
4 | MO4 coding standard
5 | and that the unit tests still pass.
6 | Whenever possible, add an auto fixer for coding standard violations.
7 |
8 | ## Setup
9 |
10 | We do recommend the following setup:
11 |
12 | * make sure that [Composer](https://getcomposer.org) is installed
13 | * clone this repository
14 |
15 | git clone https://github.com/mayflower/mo4-coding-standard.git
16 |
17 | * install all required dependencies
18 |
19 | composer install
20 |
21 | * be sure that [Xdebug](https://xdebug.org/) is installed, if you like to check code coverage.
22 |
23 |
24 | ## Coding Standard
25 |
26 | To check the coding standard, execute in the repository root:
27 |
28 | ./vendor/bin/phpcs
29 |
30 | `phpcs` might report that some coding standard issues can be fixed automatically.
31 | So give `phpcbf` a try and let it fix the issues for you:
32 |
33 | ./vendor/bin/phpcbf
34 |
35 | ## Tests
36 |
37 | To run the unit tests, execute in the repository root:
38 |
39 | ./vendor/bin/phpunit
40 |
41 | To run the integration tests, execute
42 |
43 | ./vendor/bin/phpcs --standard=MO4 integrationtests/*.php
44 |
45 | ## Static analysis
46 |
47 | We use [PHPStan](https://github.com/phpstan/phpstan) and [Phan](https://github.com/phan/phan), please refer to the
48 | respective documentation for installation instructions.
49 |
50 | ./vendor/bin/phpstan analyse
51 | ./vendor/bin/phan -i
52 |
53 | ## Code Coverage
54 |
55 | Make sure, that you write tests for your code.
56 |
57 | Testing code coverage with [PHPUnit](https://phpunit.de/) requires [Xdebug](https://xdebug.org/) to be enabled.
58 |
59 | You can generate a simple code coverage report by running in the repository root:
60 |
61 | ./vendor/bin/phpunit --coverage-text
62 |
63 | In the case that Xdebug is disabled by default
64 |
65 | php -d zend_extension=xdebug.so vendor/bin/phpunit --coverage-text
66 |
67 | will do the trick.
68 |
69 | Please refer to the [PHPUnit Manual](https://phpunit.de/documentation.html) for further information about code coverage.
70 |
71 |
--------------------------------------------------------------------------------
/MO4/Library/PregLibrary.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Library;
16 |
17 | use PHP_CodeSniffer\Exceptions\RuntimeException;
18 |
19 | final class PregLibrary
20 | {
21 | /**
22 | * Split string by a regular expression
23 | *
24 | * @param string $pattern The pattern to search for, as a string.
25 | * @param string $subject The input string.
26 | * @param int $limit If specified, then only substrings up to limit are returned with the rest of the string
27 | * being placed in the last substring. A limit of -1, 0 or NULL means "no limit" and, as is
28 | * standard across PHP, you can use NULL to skip to the flags parameter.
29 | * @param int $flags Can be any combination of the following flags (combined with the | bitwise operator):
30 | * PREG_SPLIT_NO_EMPTY: If this flag is set, only non-empty pieces will be returned.
31 | * PREG_SPLIT_DELIM_CAPTURE: If this flag is set, parenthesized expression in the delimiter
32 | * pattern will be captured and returned as well.
33 | * PREG_SPLIT_OFFSET_CAPTURE: If this flag is set, for every occurring match the appendant
34 | * string offset will also be returned.
35 | *
36 | * @return array|array
37 | *
38 | * @throws RuntimeException
39 | *
40 | * @psalm-suppress ArgumentTypeCoercion
41 | */
42 | public static function MO4PregSplit(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
43 | {
44 | $pregSplitResult = \preg_split($pattern, $subject, $limit, $flags);
45 |
46 | // @phan-suppress-next-line PhanTypeComparisonToArray
47 | if (false === $pregSplitResult) {
48 | throw new RuntimeException('Unexpected Error in MO4 Coding Standard.');
49 | }
50 |
51 | return $pregSplitResult;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/MO4/Tests/Formatting/UnnecessaryNamespaceUsageUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests\Formatting;
16 |
17 | use MO4\Tests\AbstractMo4SniffUnitTest;
18 |
19 | /**
20 | * Unit test class for the UnnecessaryNamespaceUsageUnitTest sniff.
21 | *
22 | * A sniff unit test checks a .inc file for expected violations of a single
23 | * coding standard. Expected errors and warnings are stored in this class.
24 | *
25 | * @author Xaver Loppenstedt
26 | * @author Marco Jantke
27 | * @author Steffen Ritter
28 | *
29 | * @copyright 2013-21 Xaver Loppenstedt, some rights reserved.
30 | *
31 | * @license http://spdx.org/licenses/MIT MIT License
32 | *
33 | * @link https://github.com/mayflower/mo4-coding-standard
34 | */
35 | final class UnnecessaryNamespaceUsageUnitTest extends AbstractMo4SniffUnitTest
36 | {
37 | protected $expectedWarningList = [
38 | 'UnnecessaryNamespaceUsageUnitTest.pass.1.inc' => [],
39 | 'UnnecessaryNamespaceUsageUnitTest.pass.2.inc' => [],
40 | 'UnnecessaryNamespaceUsageUnitTest.pass.3.inc' => [],
41 | 'UnnecessaryNamespaceUsageUnitTest.pass.4.inc' => [],
42 | 'UnnecessaryNamespaceUsageUnitTest.pass.5.inc' => [],
43 | 'UnnecessaryNamespaceUsageUnitTest.pass.6.inc' => [],
44 | 'UnnecessaryNamespaceUsageUnitTest.fail.1.inc' => [
45 | 17 => 1,
46 | 19 => 1,
47 | 24 => 1,
48 | 25 => 1,
49 | 26 => 2,
50 | 28 => 1,
51 | 30 => 2,
52 | 32 => 1,
53 | 33 => 1,
54 | 40 => 1,
55 | 44 => 1,
56 | 45 => 1,
57 | 46 => 1,
58 | 52 => 1,
59 | 56 => 1,
60 | ],
61 | 'UnnecessaryNamespaceUsageUnitTest.fail.2.inc' => [
62 | 10 => 1,
63 | 11 => 1,
64 | ],
65 | 'UnnecessaryNamespaceUsageUnitTest.fail.3.inc' => [
66 | 15 => 1,
67 | 16 => 1,
68 | 17 => 1,
69 | 18 => 1,
70 | 22 => 1,
71 | 23 => 1,
72 | 25 => 3,
73 | ],
74 | 'UnnecessaryNamespaceUsageUnitTest.fail.4.inc' => [],
75 | ];
76 | }
77 |
--------------------------------------------------------------------------------
/MO4/Sniffs/WhiteSpace/ConstantSpacingSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\WhiteSpace;
16 |
17 | use PHP_CodeSniffer\Files\File;
18 | use PHP_CodeSniffer\Sniffs\Sniff;
19 |
20 | /**
21 | * Multi Line Array sniff.
22 | *
23 | * @author Xaver Loppenstedt
24 | *
25 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
26 | *
27 | * @license http://spdx.org/licenses/MIT MIT License
28 | *
29 | * @link https://github.com/mayflower/mo4-coding-standard
30 | *
31 | * @psalm-api
32 | */
33 | class ConstantSpacingSniff implements Sniff
34 | {
35 | /**
36 | * Define all types of arrays.
37 | *
38 | * @var array
39 | */
40 | protected $arrayTokens = [
41 | T_CONST,
42 | ];
43 |
44 | /**
45 | * Registers the tokens that this sniff wants to listen for.
46 | *
47 | * @return array
48 | *
49 | * @see Tokens.php
50 | */
51 | #[\Override]
52 | public function register(): array
53 | {
54 | return $this->arrayTokens;
55 | }
56 |
57 | /**
58 | * Processes this test, when one of its tokens is encountered.
59 | *
60 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
61 | *
62 | * @param File $phpcsFile The file being scanned.
63 | * @param int $stackPtr The position of the current token in
64 | * the stack passed in $tokens.
65 | *
66 | * @return void
67 | */
68 | #[\Override]
69 | public function process(File $phpcsFile, $stackPtr): void
70 | {
71 | $tokens = $phpcsFile->getTokens();
72 | $nextPtr = $stackPtr + 1;
73 | $next = $tokens[$nextPtr]['content'];
74 |
75 | if (T_WHITESPACE !== $tokens[$nextPtr]['code']) {
76 | return;
77 | }
78 |
79 | if (' ' === $next) {
80 | return;
81 | }
82 |
83 | $fix = $phpcsFile->addFixableError(
84 | 'Keyword const must be followed by a single space, but found %s',
85 | $stackPtr,
86 | 'Incorrect',
87 | [\strlen($next)]
88 | );
89 |
90 | if (true !== $fix) {
91 | return;
92 | }
93 |
94 | $phpcsFile->fixer->replaceToken($nextPtr, ' ');
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/ArrayDoubleArrowAlignmentUnitTest.pass.inc:
--------------------------------------------------------------------------------
1 | 1,
5 | 'four' => 4,
6 | ];
7 |
8 | $a = [
9 | 'one' => 1,
10 | 2,
11 | 'four' => 4,
12 | ];
13 |
14 | $a = [
15 | 'one' => 1,
16 | 2,
17 | 'four' /* comment */ => 4,
18 | ];
19 |
20 | $a = [
21 | 'one' => 1,
22 | 2,
23 | // comment
24 | 'four' => 4,
25 | ];
26 |
27 | $a = [
28 | 2,
29 | 'four' => 4,
30 | // comment
31 | 'one' => /* comment */
32 | 1,
33 | ];
34 |
35 | $a = array(
36 | 'short' => 1,
37 | 'verylongerkey' => 2,
38 | 'x' => 3,
39 | );
40 |
41 | $a = [
42 | 'short' => 1,
43 | 'verylongerkey' => 2,
44 | 'x' => 3,
45 | ];
46 |
47 | $a = array(
48 | 'short' => 1,
49 | 'verylongerkey' => 2,
50 | 'array' => array(
51 | 'val1' => 1,
52 | 'val112' => 1,
53 | 'val1123' => 1,
54 | 'v' => 1,
55 | ),
56 | 'x' => 3,
57 | );
58 |
59 | $a = [
60 | 'short' => 1,
61 | 'verylongerkey' => 2,
62 | 'array' => [
63 | 'val1' => 1,
64 | 'val112' => 1,
65 | 'val1123' => 1,
66 | 'v' => 1,
67 | ],
68 | 'x' => 3,
69 | ];
70 |
71 | $a = [
72 | 'short' => 1,
73 | 'verylongerkey' => 2,
74 | 'array' => array(
75 | 'val1' => 1,
76 | 'val112' => 1,
77 | 'val1123' => 1,
78 | 'v' => 1,
79 | ),
80 | 'x' => 3,
81 | ];
82 |
83 | $a = array(
84 | 'short' => 1,
85 | 'verylongerkey' => 2,
86 | 'array' => [
87 | 'val1' => 1,
88 | 'val112' => 1,
89 | 'val1123' => 1,
90 | 'v' => 1,
91 | ],
92 | 'x' => 3,
93 | );
94 |
95 | $a = [
96 | 0 => [1 => 'O', 0 => 'O'],
97 | 1 => [1 => 'O', 0 => 'I'],
98 | 2 => [1 => 'I', 0 => 'O'],
99 | 3 => [1 => 'I', 0 => 'I'],
100 | ];
101 |
102 | $a = [
103 | 0 => [1 => 'O', 0 => 'O'],
104 | 1 => [1 => 'O', 0 => 'I'],
105 | 2 => [1 => 'I', 0 => 'O'],
106 | 3 => [1 => 'I', 0 => 'I'],
107 | ];
108 |
109 | $a = [
110 | function () {
111 | $b = [
112 | 'one' => 1,
113 | 'four' => 4,
114 | ];
115 | foreach ($b as $k => $v) {
116 | }
117 | $bar = [
118 | 'one' => 1,
119 | 'four' => 4,
120 | ];
121 | foreach ($bar as $key => $value) {
122 | }
123 | },
124 | 'func' => function () {
125 | },
126 | 'one' => 1,
127 | 2,
128 | 'four' => 2,
129 | ];
130 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/ArrayDoubleArrowAlignmentUnitTest.fail.inc:
--------------------------------------------------------------------------------
1 | 1,
6 | 'four' => 4,
7 | ];
8 |
9 | $a = [
10 | 'one' => 1,
11 | 2,
12 | 'four' => 4,
13 | ];
14 |
15 | // bad
16 | $a = [
17 | 'one' => 1, 'two' => 2,
18 | 'three' => 1, 'four' => 4,
19 | ];
20 |
21 | $a = [
22 | 'one' => 1,
23 | 2,
24 | 'four' /* comment */ => 4,
25 | ];
26 |
27 | $a = [
28 | 'one' => 1,
29 | 2,
30 | // comment
31 | 'four' => 4,
32 | ];
33 |
34 | $a = [
35 | 2,
36 | 'four' => 4,
37 | // comment
38 | 'one' => /* comment */
39 | 1,
40 | ];
41 |
42 | $a = array(
43 | 'short' => 1,
44 | 'verylongerkey' => 2,
45 | 'x' => 3,
46 | );
47 |
48 | $a = [
49 | 'short' => 1,
50 | 'verylongerkey' => 2,
51 | 'x' => 3,
52 | ];
53 |
54 | $a = array(
55 | 'short' => 1,
56 | 'verylongerkey' => 2,
57 | 'array' => array(
58 | 'val1' => 1,
59 | 'val112' => 1,
60 | 'val11234567' => 1,
61 | 'v' => 1,
62 | ),
63 | 'x' => 3,
64 | );
65 |
66 | $a = [
67 | 'short' => 1,
68 | 'verylongerkey' => 2,
69 | 'array' => [
70 | 'val1' => 1,
71 | 'val112' => 1,
72 | 'val1123456' => 1,
73 | 'v' => 1,
74 | ],
75 | 'x' => 3,
76 | ];
77 |
78 | $a = [
79 | 'short' => 1,
80 | 'verylongerkey' => 2,
81 | 'array' => array(
82 | 'val1' => 1,
83 | 'val112' => 1,
84 | 'val11234567' => 1,
85 | 'v' => 1,
86 | ),
87 | 'x' => 3,
88 | ];
89 |
90 | $a = array(
91 | 'short' => 1,
92 | 'verylongerkey' => 2,
93 | 'array' => [
94 | 'val1' => 1,
95 | 'val112' => 1,
96 | 'val1123' => 1,
97 | 'v' => 1,
98 | ],
99 | 'x' => 3,
100 | );
101 |
102 | $a = [
103 | 0 => [ 1 => 'O', 0 => 'O'],
104 | 1 => [ 1 => 'O', 0 => 'I'],
105 | 2 => [ 1 => 'I', 0 => 'O'],
106 | 3 => [ 1 => 'I', 0 => 'I'],
107 | ];
108 |
109 | foreach ($x as $k => $v) {
110 |
111 | }
112 |
113 | $a = [
114 | function () {
115 | $b = [
116 | 'one' => 1,
117 | 'four' => 4,
118 | ];
119 | foreach ($b as $k => $v) {
120 | }
121 | $bar = [
122 | 'one' => 1,
123 | 'four' => 4,
124 | ];
125 | foreach ($bar as $key => $value) {
126 | }
127 | },
128 | function () {
129 | },
130 | 'one' => 1,
131 | 2,
132 | 'four' => 2,
133 | 123456789
134 | /* bla */ => 'fail',
135 | 98765432
136 | => 'fail',
137 | ];
138 |
139 | $a=[
140 | 'v'=>1,
141 | 'vv'=>2,
142 | ];
143 | // deal with syntax errors
144 | $aa = [
145 | 'y' => [1, 2, 3] 'x' => 0 'z' => -1
146 | ];
147 |
148 | $a=[
149 | 'v'=>1 'vv'=>2
150 | ];
151 |
--------------------------------------------------------------------------------
/MO4/Tests/Arrays/ArrayDoubleArrowAlignmentUnitTest.fail.inc.fixed:
--------------------------------------------------------------------------------
1 | 1,
6 | 'four' => 4,
7 | ];
8 |
9 | $a = [
10 | 'one' => 1,
11 | 2,
12 | 'four' => 4,
13 | ];
14 |
15 | // bad
16 | $a = [
17 | 'one' => 1,
18 | 'two' => 2,
19 | 'three' => 1,
20 | 'four' => 4,
21 | ];
22 |
23 | $a = [
24 | 'one' => 1,
25 | 2,
26 | 'four' /* comment */ => 4,
27 | ];
28 |
29 | $a = [
30 | 'one' => 1,
31 | 2,
32 | // comment
33 | 'four' => 4,
34 | ];
35 |
36 | $a = [
37 | 2,
38 | 'four' => 4,
39 | // comment
40 | 'one' => /* comment */
41 | 1,
42 | ];
43 |
44 | $a = array(
45 | 'short' => 1,
46 | 'verylongerkey' => 2,
47 | 'x' => 3,
48 | );
49 |
50 | $a = [
51 | 'short' => 1,
52 | 'verylongerkey' => 2,
53 | 'x' => 3,
54 | ];
55 |
56 | $a = array(
57 | 'short' => 1,
58 | 'verylongerkey' => 2,
59 | 'array' => array(
60 | 'val1' => 1,
61 | 'val112' => 1,
62 | 'val11234567' => 1,
63 | 'v' => 1,
64 | ),
65 | 'x' => 3,
66 | );
67 |
68 | $a = [
69 | 'short' => 1,
70 | 'verylongerkey' => 2,
71 | 'array' => [
72 | 'val1' => 1,
73 | 'val112' => 1,
74 | 'val1123456' => 1,
75 | 'v' => 1,
76 | ],
77 | 'x' => 3,
78 | ];
79 |
80 | $a = [
81 | 'short' => 1,
82 | 'verylongerkey' => 2,
83 | 'array' => array(
84 | 'val1' => 1,
85 | 'val112' => 1,
86 | 'val11234567' => 1,
87 | 'v' => 1,
88 | ),
89 | 'x' => 3,
90 | ];
91 |
92 | $a = array(
93 | 'short' => 1,
94 | 'verylongerkey' => 2,
95 | 'array' => [
96 | 'val1' => 1,
97 | 'val112' => 1,
98 | 'val1123' => 1,
99 | 'v' => 1,
100 | ],
101 | 'x' => 3,
102 | );
103 |
104 | $a = [
105 | 0 => [ 1 => 'O', 0 => 'O'],
106 | 1 => [ 1 => 'O', 0 => 'I'],
107 | 2 => [ 1 => 'I', 0 => 'O'],
108 | 3 => [ 1 => 'I', 0 => 'I'],
109 | ];
110 |
111 | foreach ($x as $k => $v) {
112 |
113 | }
114 |
115 | $a = [
116 | function () {
117 | $b = [
118 | 'one' => 1,
119 | 'four' => 4,
120 | ];
121 | foreach ($b as $k => $v) {
122 | }
123 | $bar = [
124 | 'one' => 1,
125 | 'four' => 4,
126 | ];
127 | foreach ($bar as $key => $value) {
128 | }
129 | },
130 | function () {
131 | },
132 | 'one' => 1,
133 | 2,
134 | 'four' => 2,
135 | 123456789 /* bla */ => 'fail',
136 | 98765432 => 'fail',
137 | ];
138 |
139 | $a=[
140 | 'v' =>1,
141 | 'vv' =>2,
142 | ];
143 | // deal with syntax errors
144 | $aa = [
145 | 'y' => [1, 2, 3] 'x' => 0 'z' => -1
146 | ];
147 |
148 | $a=[
149 | 'v'=>1 'vv'=>2
150 | ];
151 |
--------------------------------------------------------------------------------
/MO4/Sniffs/WhiteSpace/MultipleEmptyLinesSniff.php:
--------------------------------------------------------------------------------
1 |
25 | *
26 | * @see Tokens.php
27 | */
28 | #[\Override]
29 | public function register(): array
30 | {
31 | return [
32 | // Assume most comments end with a newline
33 | T_COMMENT,
34 | // Assume all getTokens();
52 |
53 | // This sniff intentionally doesn't care about whitespace at the end of the file
54 | if (!isset($tokens[$stackPtr + 3])
55 | || $tokens[$stackPtr + 2]['line'] === $tokens[$stackPtr + 3]['line']
56 | ) {
57 | return $stackPtr + 3;
58 | }
59 |
60 | if ($tokens[$stackPtr + 1]['line'] === $tokens[$stackPtr + 2]['line']) {
61 | return $stackPtr + 2;
62 | }
63 |
64 | // Finally, check the assumption the current token is or ends with a newline
65 | if ($tokens[$stackPtr]['line'] === $tokens[$stackPtr + 1]['line']) {
66 | return;
67 | }
68 |
69 | // Search for the next non-newline token
70 | $next = $stackPtr + 1;
71 |
72 | while (isset($tokens[$next + 1]) &&
73 | $tokens[$next]['code'] === T_WHITESPACE &&
74 | $tokens[$next]['line'] !== $tokens[$next + 1]['line']
75 | ) {
76 | $next++;
77 | }
78 |
79 | $count = $next - $stackPtr - 1;
80 |
81 | if ($count > 1
82 | && $phpcsFile->addFixableError(
83 | 'Multiple empty lines should not exist in a row; found %s consecutive empty lines',
84 | $stackPtr + 1,
85 | 'MultipleEmptyLines',
86 | [$count]
87 | )
88 | ) {
89 | $phpcsFile->fixer->beginChangeset();
90 |
91 | // Remove all newlines except the first two, i.e. keep one empty line
92 | for ($i = $stackPtr + 2; $i < $next; $i++) {
93 | $phpcsFile->fixer->replaceToken($i, '');
94 | }
95 |
96 | $phpcsFile->fixer->endChangeset();
97 | }
98 |
99 | // Don't check the current sequence a second time
100 | return $next;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/integrationtests/testfile.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Acme;
15 |
16 | const BAM = 1;
17 |
18 | /**
19 | * Coding standards demonstration.
20 | */
21 | class FooBar
22 | {
23 | public const SOME_CONST = 42;
24 | public const STR_CONST = '43';
25 | protected const PROTECT = 0;
26 | public const LALA = 'lala';
27 |
28 | /**
29 | * @var string
30 | */
31 | private $fooBar;
32 |
33 | /**
34 | * @param string $dummy Some argument description
35 | */
36 | public function __construct(string $dummy)
37 | {
38 | $this->fooBar = $this->transformText($dummy);
39 | }
40 |
41 | /**
42 | * @return string
43 | *
44 | * @deprecated
45 | */
46 | public function someDeprecatedMethod(): string
47 | {
48 | @\trigger_error(\sprintf('The %s() method is deprecated since version 2.8 and will be removed in 3.0. Use Acme\Baz::someMethod() instead.', __METHOD__), E_USER_DEPRECATED);
49 |
50 | return Baz::someMethod();
51 | }
52 |
53 | /**
54 | * Transforms the input given as first argument.
55 | *
56 | * @param bool|string $dummy Some argument description
57 | * @param array $options An options collection to be used within the transformation
58 | *
59 | * @return string|null The transformed input
60 | *
61 | * @throws \RuntimeException When an invalid option is provided
62 | */
63 | private function transformText($dummy, array $options = []): ?string
64 | {
65 | /** @var array $defaultOptions */
66 | $defaultOptions = [
67 | 'some_default' => 'values',
68 | 'another_default' => 'more values',
69 | ];
70 |
71 | foreach ($options as $option) {
72 | if (!\in_array($option, $defaultOptions)) {
73 | throw new \RuntimeException(\sprintf('Unrecognized option "%s"', $option));
74 | }
75 | }
76 |
77 | $destructuredStuff = \array_flip(...$defaultOptions);
78 |
79 | $mergedOptions = \array_merge(
80 | $defaultOptions,
81 | $options
82 | );
83 |
84 | if (true === $dummy) {
85 | return null;
86 | }
87 |
88 | if ('string' === $dummy) {
89 | if ('values' === $mergedOptions['some_default']) {
90 | return \substr($dummy, 0, 5);
91 | }
92 |
93 | return \ucwords($dummy);
94 | }
95 |
96 | return $destructuredStuff;
97 | }
98 |
99 | /**
100 | * Performs some basic check for a given value.
101 | *
102 | * @param mixed $value Some value to check against
103 | * @param bool $theSwitch Some switch to control the method's flow
104 | *
105 | * @return bool|void The resultant check if $theSwitch isn't false, void otherwise
106 | */
107 | private function reverseBoolean($value = null, bool $theSwitch = false)
108 | {
109 | if (!$theSwitch) {
110 | return;
111 | }
112 |
113 | return !$value;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Arrays/MultiLineArraySniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Arrays;
16 |
17 | use PHP_CodeSniffer\Files\File;
18 | use PHP_CodeSniffer\Sniffs\Sniff;
19 |
20 | /**
21 | * Multi Line Array sniff.
22 | *
23 | * @author Xaver Loppenstedt
24 | *
25 | * @copyright 2013-2017 Xaver Loppenstedt, some rights reserved.
26 | *
27 | * @license http://spdx.org/licenses/MIT MIT License
28 | *
29 | * @link https://github.com/mayflower/mo4-coding-standard
30 | *
31 | * @psalm-api
32 | */
33 | class MultiLineArraySniff implements Sniff
34 | {
35 | /**
36 | * Define all types of arrays.
37 | *
38 | * @var array
39 | */
40 | protected $arrayTokens = [
41 | // @phan-suppress-next-line PhanUndeclaredConstant
42 | T_OPEN_SHORT_ARRAY,
43 | T_ARRAY,
44 | ];
45 |
46 | /**
47 | * Registers the tokens that this sniff wants to listen for.
48 | *
49 | * @return array
50 | *
51 | * @see Tokens.php
52 | */
53 | #[\Override]
54 | public function register(): array
55 | {
56 | return $this->arrayTokens;
57 | }
58 |
59 | /**
60 | * Processes this test, when one of its tokens is encountered.
61 | *
62 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
63 | *
64 | * @param File $phpcsFile The file being scanned.
65 | * @param int $stackPtr The position of the current token in
66 | * the stack passed in $tokens.
67 | *
68 | * @return void
69 | */
70 | #[\Override]
71 | public function process(File $phpcsFile, $stackPtr): void
72 | {
73 | $tokens = $phpcsFile->getTokens();
74 | $current = $tokens[$stackPtr];
75 |
76 | if (T_ARRAY === $current['code']) {
77 | $arrayType = 'parenthesis';
78 | $start = $current['parenthesis_opener'];
79 | $end = $current['parenthesis_closer'];
80 | } else {
81 | $arrayType = 'bracket';
82 | $start = $current['bracket_opener'];
83 | $end = $current['bracket_closer'];
84 | }
85 |
86 | if ($tokens[$start]['line'] === $tokens[$end]['line']) {
87 | return;
88 | }
89 |
90 | if ($tokens[($start + 2)]['line'] === $tokens[$start]['line']) {
91 | $fixable = $phpcsFile->addFixableError(
92 | \sprintf(
93 | 'opening %s of multi line array must be followed by newline',
94 | $arrayType
95 | ),
96 | $start,
97 | 'OpeningMustBeFollowedByNewline'
98 | );
99 |
100 | if (true === $fixable) {
101 | $phpcsFile->fixer->beginChangeset();
102 | $phpcsFile->fixer->addNewline($start);
103 | $phpcsFile->fixer->endChangeset();
104 | }
105 | }
106 |
107 | if ($tokens[($end - 2)]['line'] !== $tokens[$end]['line']) {
108 | return;
109 | }
110 |
111 | $fixable = $phpcsFile->addFixableError(
112 | \sprintf(
113 | 'closing %s of multi line array must in own line',
114 | $arrayType
115 | ),
116 | $end,
117 | 'ClosingMustBeInOwnLine'
118 | );
119 |
120 | if (true !== $fixable) {
121 | return;
122 | }
123 |
124 | $phpcsFile->fixer->beginChangeset();
125 | $phpcsFile->fixer->addNewlineBefore($end);
126 | $phpcsFile->fixer->endChangeset();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/MO4/Tests/AbstractMo4SniffUnitTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Tests;
16 |
17 | use PHP_CodeSniffer\Exceptions\RuntimeException;
18 | use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
19 |
20 | /**
21 | * Abstract class to make the writing of tests more convenient.
22 | *
23 | * A sniff unit test checks a .inc file for expected violations of a single
24 | * coding standard.
25 | *
26 | * Expected errors and warnings are stored in the class fields $expectedErrorList
27 | * and $expectedWarningList
28 | *
29 | * @author Xaver Loppenstedt
30 | *
31 | * @copyright 2013-2021 Xaver Loppenstedt, some rights reserved.
32 | *
33 | * @license http://spdx.org/licenses/MIT MIT License
34 | *
35 | * @link https://github.com/mayflower/mo4-coding-standard
36 | */
37 | abstract class AbstractMo4SniffUnitTest extends AbstractSniffUnitTest
38 | {
39 | /**
40 | * Array or Array containing the test file as key and as value the key-value pairs with line number and number of#
41 | * errors as describe in @see AbstractSniffUnitTest::getErrorList
42 | *
43 | * When the array is empty, the test will pass.
44 | *
45 | * @var array
46 | */
47 | protected $expectedErrorList = [];
48 |
49 | /**
50 | * Array or Array containing the test file as key and as value the key-value pairs with line number and number of#
51 | * errors as describe in @see AbstractSniffUnitTest::getWarningList
52 | *
53 | * When the array is empty, the test will pass.
54 | *
55 | * @var array
56 | */
57 | protected $expectedWarningList = [];
58 |
59 | /**
60 | * Returns the lines where errors should occur.
61 | *
62 | * The key of the array should represent the line number and the value
63 | * should represent the number of errors that should occur on that line.
64 | *
65 | * @param string $testFile test file
66 | *
67 | * @return array
68 | *
69 | * @throws RuntimeException
70 | */
71 | #[\Override]
72 | protected function getErrorList(string $testFile = ''): array
73 | {
74 | return $this->getRecordForTestFile($testFile, $this->expectedErrorList);
75 | }
76 |
77 | /**
78 | * Returns the lines where warnings should occur.
79 | *
80 | * The key of the array should represent the line number and the value
81 | * should represent the number of warnings that should occur on that line.
82 | *
83 | * @param string $testFile test file
84 | *
85 | * @return array
86 | *
87 | * @throws RuntimeException
88 | */
89 | #[\Override]
90 | protected function getWarningList(string $testFile = ''): array
91 | {
92 | return $this->getRecordForTestFile($testFile, $this->expectedWarningList);
93 | }
94 |
95 | /**
96 | * Returns the lines where warnings should occur for the error or warning list.
97 | *
98 | * The key of the array should represent the line number and the value
99 | * should represent the number of warnings that should occur on that line.
100 | *
101 | * @param string $testFile test file
102 | * @param array $list record for test file
103 | *
104 | * @return array
105 | *
106 | * @throws RuntimeException
107 | */
108 | private function getRecordForTestFile(string $testFile, array $list): array
109 | {
110 | if ([] === $list) {
111 | return [];
112 | }
113 |
114 | if (!\array_key_exists($testFile, $list)) {
115 | throw new RuntimeException(
116 | \sprintf('%s%s is not handled by %s', \sprintf('Testfile %s in ', $testFile), __DIR__, self::class)
117 | );
118 | }
119 |
120 | return $list[$testFile];
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Strings/VariableInDoubleQuotedStringSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Strings;
16 |
17 | use PHP_CodeSniffer\Files\File;
18 | use PHP_CodeSniffer\Sniffs\Sniff;
19 |
20 | /**
21 | * Variable in Double Quoted String sniff.
22 | *
23 | * Variables in double quoted strings must be surrounded by { }
24 | *
25 | * @author Xaver Loppenstedt
26 | *
27 | * @copyright 2013 Xaver Loppenstedt, some rights reserved.
28 | *
29 | * @license http://spdx.org/licenses/MIT MIT License
30 | *
31 | * @link https://github.com/mayflower/mo4-coding-standard
32 | *
33 | * @psalm-api
34 | */
35 | class VariableInDoubleQuotedStringSniff implements Sniff
36 | {
37 | /**
38 | * Registers the tokens that this sniff wants to listen for.
39 | *
40 | * @return array
41 | *
42 | * @see Tokens.php
43 | */
44 | #[\Override]
45 | public function register(): array
46 | {
47 | return [T_DOUBLE_QUOTED_STRING];
48 | }
49 |
50 | /**
51 | * Called when one of the token types that this sniff is listening for
52 | * is found.
53 | *
54 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
55 | *
56 | * @param File $phpcsFile The PHP_CodeSniffer file where the
57 | * token was found.
58 | * @param int $stackPtr The position in the PHP_CodeSniffer
59 | * file's token stack where the token
60 | * was found.
61 | *
62 | * @return void
63 | */
64 | #[\Override]
65 | public function process(File $phpcsFile, $stackPtr): void
66 | {
67 | $varRegExp = '/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/';
68 |
69 | $tokens = $phpcsFile->getTokens();
70 | $content = $tokens[$stackPtr]['content'];
71 |
72 | $matches = [];
73 |
74 | \preg_match_all($varRegExp, $content, $matches, PREG_OFFSET_CAPTURE);
75 |
76 | foreach ($matches as $match) {
77 | foreach ($match as [$var, $pos]) {
78 | if (1 !== $pos && '{' === $content[($pos - 1)]) {
79 | continue;
80 | }
81 |
82 | if (\strpos(\substr($content, 0, $pos), '{') > 0
83 | && !\str_contains(\substr($content, 0, $pos), '}')
84 | ) {
85 | continue;
86 | }
87 |
88 | $lastOpeningBrace = \strrpos(\substr($content, 0, $pos), '{');
89 |
90 | if (false !== $lastOpeningBrace
91 | && '$' === $content[($lastOpeningBrace + 1)]
92 | ) {
93 | $lastClosingBrace = \strrpos(\substr($content, 0, $pos), '}');
94 |
95 | if (false !== $lastClosingBrace
96 | && $lastClosingBrace < $lastOpeningBrace
97 | ) {
98 | continue;
99 | }
100 | }
101 |
102 | $fix = $phpcsFile->addFixableError(
103 | \sprintf(
104 | 'must surround variable %s with { }',
105 | $var
106 | ),
107 | $stackPtr,
108 | 'NotSurroundedWithBraces'
109 | );
110 |
111 | if (true !== $fix) {
112 | continue;
113 | }
114 |
115 | $correctVariable = $this->surroundVariableWithBraces(
116 | $content,
117 | $pos,
118 | $var
119 | );
120 |
121 | $this->fixPhpCsFile($stackPtr, $correctVariable, $phpcsFile);
122 | }
123 | }
124 | }
125 |
126 | /**
127 | * Surrounds a variable with curly brackets
128 | *
129 | * @param string $content content
130 | * @param int $pos position
131 | * @param string $var variable
132 | *
133 | * @return string
134 | */
135 | private function surroundVariableWithBraces(string $content, int $pos, string $var): string
136 | {
137 | $before = \substr($content, 0, $pos);
138 | $after = \substr($content, ($pos + \strlen($var)));
139 |
140 | return $before.'{'.$var.'}'.$after;
141 | }
142 |
143 | /**
144 | * Fixes the file
145 | *
146 | * @param int $stackPtr stack pointer
147 | * @param string $correctVariable correct variable
148 | * @param File $phpCsFile PHP_CodeSniffer File object
149 | *
150 | * @return void
151 | */
152 | private function fixPhpCsFile(int $stackPtr, string $correctVariable, File $phpCsFile): void
153 | {
154 | $phpCsFile->fixer->beginChangeset();
155 | $phpCsFile->fixer->replaceToken($stackPtr, $correctVariable);
156 | $phpCsFile->fixer->endChangeset();
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: MO4 Coding Standard CI
2 | on:
3 | pull_request:
4 | push:
5 | schedule:
6 | - cron: '30 5 1 * *'
7 |
8 | jobs:
9 | style-checks:
10 | env:
11 | XMLLINT_INDENT: " "
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: install dependencies
15 | run: sudo apt update -qq && sudo apt -y install libxml2-utils
16 | - name: Check out repository code
17 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
18 | - name: Install PHP
19 | uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # master
20 | with:
21 | php-version: '8.1'
22 | coverage: 'none'
23 | - name: Cache dependencies
24 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
25 | with:
26 | path: '~/.cache/composer'
27 | key: "cache-composer-${{ hashFiles('composer.json') }}"
28 | restore-keys: 'cache-composer-'
29 | - name: run composer
30 | run: composer update --prefer-lowest --prefer-dist --no-interaction --no-progress
31 | - run: xmllint --noout --schema vendor/squizlabs/php_codesniffer/phpcs.xsd MO4/ruleset.xml
32 | - run: xmllint --noout --schema vendor/squizlabs/php_codesniffer/phpcs.xsd phpcs.mo4.xml
33 | - run: xmllint --noout --schema vendor/squizlabs/php_codesniffer/phpcs.xsd phpcs.xml.dist
34 | - run: diff -B MO4/ruleset.xml <(xmllint --format MO4/ruleset.xml)
35 | - run: diff -B phpcs.mo4.xml <(xmllint --format phpcs.mo4.xml)
36 | - run: diff -B phpcs.xml.dist <(xmllint --format phpcs.xml.dist)
37 | - name: Stylecheck against MO4 itself
38 | run: vendor/bin/phpcs
39 | run-tests:
40 | strategy:
41 | matrix:
42 | os: [ubuntu-latest]
43 | php_version:
44 | - 8.1
45 | - 8.2
46 | - 8.3
47 | - 8.4
48 | dependencies_level:
49 | - --prefer-lowest
50 | - ""
51 | include:
52 | - os: windows-latest
53 | php_version: 8.1
54 | dependencies_level: --prefer-lowest
55 | - os: windows-latest
56 | php_version: 8.1
57 | dependencies_level: ''
58 | runs-on: ${{ matrix.os }}
59 | steps:
60 | - name: Set git to use LF on windows
61 | if: ${{ matrix.os == 'windows-latest' }}
62 | run: |
63 | git config --global core.autocrlf false
64 | git config --global core.eol lf
65 | - name: Check out repository code
66 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
67 | - name: Install PHP
68 | uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # master
69 | with:
70 | coverage: 'xdebug'
71 | php-version: ${{ matrix.php_version }}
72 | extensions: ast-1.1.1
73 | - name: Cache dependencies
74 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
75 | with:
76 | path: '~/.cache/composer'
77 | key: "cache-composer-${{ hashFiles('composer.json') }}"
78 | restore-keys: 'cache-composer-'
79 | - name: Run composer
80 | run: composer update ${{ matrix.dependencies_level }} --prefer-dist --no-interaction --no-progress
81 | - name: Check composer.json
82 | run: composer normalize --dry-run
83 | - name: Run tests
84 | run: vendor/bin/phpunit
85 | - name: Run integration tests
86 | run: vendor/bin/phpcs -s --standard=MO4 integrationtests/testfile.php
87 | - name: Run PHPStan
88 | run: vendor/bin/phpstan analyse --no-progress
89 | - name: Run psalm
90 | if: ${{ matrix.os != 'windows-latest' }}
91 | run: vendor/bin/psalm
92 | - name: Run phan
93 | if: ${{ matrix.os != 'windows-latest' }}
94 | run: vendor/bin/phan
95 | # AST 1.1 binary for Windows seems to be missing on PECL
96 | - name: Run phan with polyfill
97 | if: ${{ matrix.os == 'windows-latest' }}
98 | run: vendor/bin/phan --allow-polyfill-parser
99 | - name: Run tests with coverage
100 | if: ${{ matrix.os != 'windows-latest' && matrix.php_version == '8.1' && matrix.dependencies_level != '--prefer-lowest' }}
101 | run: php vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-cobertura=cobertura.xml --log-junit=junit.xml
102 | - name: Upload coverage to Codecov
103 | if: ${{ matrix.os != 'windows-latest' && matrix.php_version == '8.1' && matrix.dependencies_level != '--prefer-lowest' }}
104 | uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
105 | with:
106 | token: ${{ secrets.CODECOV_TOKEN }}
107 | - name: Upload test results to Codecov
108 | if: ${{ matrix.os != 'windows-latest' && matrix.php_version == '8.1' && matrix.dependencies_level != '--prefer-lowest' }}
109 | uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
110 | with:
111 | token: ${{ secrets.CODECOV_TOKEN }}
112 | - name: Upload coverage to Qlty
113 | if: ${{ matrix.os != 'windows-latest' && matrix.php_version == '8.1' && matrix.dependencies_level != '--prefer-lowest' }}
114 | uses: qltysh/qlty-action/coverage@a19242102d17e497f437d7466aa01b528537e899 # v2.2.0
115 | with:
116 | token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
117 | files: cobertura.xml
118 | strip-prefix: '/home/runner/work/mo4-coding-standard'
119 | add-prefix: 'MO4/Sniffs/'
120 | env:
121 | QLTY_COVERAGE_TOKEN: ${{ secrets.QLTY_COVERAGE_TOKEN }}
122 |
123 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
4 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
5 |
6 | ## [10.0.2] - 2025-07-28
7 | ### Changed
8 | - Raised minimum Symfony CS release, fixes #218
9 |
10 | ## [10.0.1] - 2024-12-16
11 | ### Changed
12 | - Fix uninitialized string offset in UnnecessaryNamespaceUsage, fixes #212
13 |
14 | ## [10.0.0] - 2024-03-13
15 | ### Changed
16 | Allow dealerdirect/phpcodesniffer-composer-installer 1.x
17 | ### Added
18 | - Add `SlevomatCodingStandard.Arrays.ArrayAccess`
19 | - Add `SlevomatCodingStandard.Attributes.AttributesOrder`
20 | - Add `SlevomatCodingStandard.Attributes.DisallowAttributesJoining`
21 | - Add `SlevomatCodingStandard.Attributes.DisallowMultipleAttributesPerLine`
22 | - Add `SlevomatCodingStandard.Classes.EnumCaseSpacing`
23 | - Add `SlevomatCodingStandard.Functions.NamedArgumentSpacing`
24 |
25 | ## [9.0.1] - 2023-12-12
26 | ### Changed
27 | - Fix invalid property case (fixes a runtime error when using PHPCS 3.8.0+)
28 |
29 | ## [9.0.0] - 2022-08-23
30 | ### Added
31 | - Add PHP 8.1 support
32 | - Add `SlevomatCodingStandard.Classes.BackedEnumTypeSpacing`
33 | - Add `SlevomatCodingStandard.Classes.ForbiddenPublicProperty`
34 |
35 | ## [8.0.0] - 2021-08-31
36 | ### Added
37 | - Add `MO4.WhiteSpace.ConstantSpacing`
38 | - Add `MO4.WhiteSpace.MultipleEmptyLinesSniff`
39 | ### Changed
40 | - refactored tests
41 |
42 | ## [7.0.0] - 2021-04-22
43 | ### Added
44 | - Add PHP 8.0 support
45 | - Add `Generic.WhiteSpace.ArbitraryParenthesesSpacing`
46 | - Add `Squiz.PHP.DisallowMultipleAssignments`
47 | - Add `SlevomatCodingStandard.Arrays.SingleLineArrayWhitespace`
48 | - Add `SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly`
49 | - Add `SlevomatCodingStandard.Functions.UnusedParameter`
50 | - Add `SlevomatCodingStandard.Functions.UselessParameterDefaultValue`
51 | ### Changed
52 | - Raised minimum PHPCS release
53 | - Raised minimum Slevomat CS release
54 |
55 | ## [6.0.0] - 2020-10-29
56 | ### Added
57 | - Add `SlevomatCodingStandard.Classes.DisallowMultiConstantDefinition`
58 | - Add `SlevomatCodingStandard.Commenting.UselessInheritDocComment`
59 | - Add `SlevomatCodingStandard.Functions.StaticClosure`
60 | - Add `SlevomatCodingStandard.Namespaces.UseSpacing`
61 | - Add `PSR12` (excluding conflicting rules)
62 | ### Changed
63 | - Raised PHP requirement to 7.2
64 |
65 | ## [5.0.0] - 2019-12-16
66 | ### Added
67 | - Add `Squiz.WhiteSpace.FunctionSpacing`
68 | - Add `Squiz.WhiteSpace.MemberVarSpacing`
69 | - Add `PSR12.Traits.UseDeclaration`
70 | - Add `SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions`
71 | ### Changed
72 | - Raised minimum Slevomat CS release and adapt MO4 ruleset
73 | - Raised minimum Symfony CS release
74 | - Raised minimum PHPCS release
75 | - Support PHP 7.4
76 |
77 | ## [4.0.0] - 2019-09-25
78 | ### Added
79 | - New sniffs from Slevomat, see comments in [the ruleset](https://github.com/mayflower/mo4-coding-standard/blob/master/MO4/ruleset.xml)
80 | ### Changed
81 | - Raised minimum Symfony CS release
82 |
83 | ## [3.2.2] - 2019-08-29
84 | ### Changed
85 | - Add `@group` to default `ignoredAnnotationNames`
86 | - Add `ignoreMultiline` for if vs. ternary
87 |
88 | ## [3.2.1] - 2019-07-01
89 | ### Changed
90 | - Raised minimum Symfony CS release
91 | - Use strict typing in MO4 sniff code
92 |
93 | ## [3.2.0] - 2019-04-23
94 | ### Changed
95 | - Raised minimum Symfony CS release
96 |
97 | ## [3.1.0] - 2019-04-14
98 | ### Changed
99 | - Raised minimum Symfony CS release
100 | - Raised minimum Slevomat CS release
101 |
102 | ## [3.0.1] - 2018-11-23
103 | ### Changed
104 | - Updated dealerdirect/phpcodesniffer-composer-installer dependency
105 |
106 | ## [3.0.0] - 2018-10-24
107 | ### Added
108 | - PHP 7.3 support
109 | - Windows support
110 | - Integration tests
111 | - New sniffs from upstream rulesets, see comments in [the ruleset](https://github.com/mayflower/mo4-coding-standard/blob/master/MO4/ruleset.xml)
112 | - Autofixer for `MO4.Arrays.ArrayDoubleArrowAlignment`, fixes [#91](https://github.com/mayflower/mo4-coding-standard/issues/91)
113 |
114 | ### Changed
115 | - Raised PHP requirement to 7.1
116 | - Detection of more comments after declarations, fixes [#96](https://github.com/mayflower/mo4-coding-standard/issues/96)
117 | - Documentation about upstream sniffs moved from README to comments per rule in [the ruleset](https://github.com/mayflower/mo4-coding-standard/blob/master/MO4/ruleset.xml)
118 |
119 | ### Removed
120 | - Support for PHP 5.6 and PHP 7.0
121 | - PEAR as installation method
122 |
123 | ## [2.1.2] - 2018-09-06
124 | ### Changed
125 | - prefer builtin-functions for performance
126 |
127 | ## [2.1.1] - 2018-05-17
128 | ### Changed
129 | - Check for empty needle when calling strpos, fixes [#78](https://github.com/mayflower/mo4-coding-standard/issues/78)
130 |
131 | ## [2.1.0] - 2018-04-06
132 | ### Changed
133 | - PHPCS version 3.2.3 or later is required.
134 |
135 | ## [2.0.0] - 2017-12-18
136 | ### Added
137 | - `MO4.Arrays.ArrayDoubleArrowAlignment`
138 | - `MO4.Arrays.MultiLineArray`
139 | - `Generic.Arrays.ArrayIndent`
140 | - `Squiz.WhiteSpace.OperatorSpacing`
141 | - The behaviour of `MO4.Formatting.AlphabeticalUseStatements` can be configured with the property `order`.
142 | Possible values are: `dictionary`, `string`, `string-locale` or `string-case-insensitive`.
143 | - Static code analysis with PHPStan.
144 | - Various cloud based code quality tools like: Scrutinizer CI, codecov.io, ...
145 |
146 | ### Changed
147 | - PHPCS version 3.2.0 or later is required.
148 | - Code complies to PHPCS coding standard version 3.2.0.
149 | - Default ordering of `MO4.Formatting.AlphabeticalUseStatements` is now `dictionary` instead of `string`.
150 | - Updates and fixes for class documentation.
151 | - Improve testing and code coverage.
152 | - Unknown test files will not trigger wrong type exceptions anymore, but report decent error messages with `RuntimeException`.
153 | - Many fixes and stability improvements.
154 |
155 | ### Removed
156 | - `MO4.Formatting.ArrayAlignmentUnit`
157 | - replaced by `MO4.Arrays.ArrayDoubleArrowAlignment` and `MO4.Arrays.MultiLineArray`.
158 | - `MO4.Formatting.UseArrayShortTag`
159 | - replaced by `Generic.Arrays.DisallowLongArray`.
160 | - Dead code from `MO4.Strings.VariableInDoubleQuotedString`.
161 |
162 | ## [1.0.0] - 2017-11-20
163 | ### Changed
164 | - MO4 coding standard can be installed as composer package and is released on packagist.org.
165 | - Replaced underlying Symfony coding standard.
166 | - PHPCS 3.0 or later is required.
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MO4 CodeSniffer ruleset
2 |
3 | Provides a PHP CodeSniffer ruleset for the MO4 coding standard
4 |
5 | [](https://github.com/mayflower/mo4-coding-standard/actions)
6 | [](https://codecov.io/gh/mayflower/mo4-coding-standard/branch/master/)
7 | [](https://qlty.sh/gh/mayflower/projects/mo4-coding-standard)
8 | [](https://sonarcloud.io/dashboard?id=mayflower_mo4-coding-standard)
9 |
10 | [](https://packagist.org/packages/mayflower/mo4-coding-standard)
11 | [](https://packagist.org/packages/mayflower/mo4-coding-standard)
12 | [](https://packagist.org/packages/mayflower/mo4-coding-standard)
13 | [](https://packagist.org/packages/mayflower/mo4-coding-standard)
14 | [](https://packagist.org/packages/mayflower/mo4-coding-standard)
15 |
16 | ## MO4 Coding Standard
17 |
18 | The MO4 Coding Standard is an extension of the [Symfony Coding Standard](http://symfony.com/doc/current/contributing/code/standards.html) and adds following rules:
19 |
20 | ### MO4.Arrays.ArrayDoubleArrowAlignment
21 | * In associative arrays, the `=>` operators must be aligned.
22 | * In arrays, the key and `=>` operator must be on the same line.
23 |
24 | ### MO4.Arrays.MultiLineArray
25 | * In multi line arrays, the opening bracket must be followed by newline.
26 | * In multi line arrays, the closing bracket must be in own line.
27 | * In multi line arrays, the elements must be indented.
28 |
29 | ### MO4.Commenting.PropertyComment
30 | * doc blocks of class properties must be multiline and have exactly one `@var` annotation
31 |
32 | ### MO4.Formatting.AlphabeticalUseStatements
33 | * `use` statements must be sorted lexicographically. The order function can be configured.
34 |
35 | #### Configuration
36 | The `order` property of the `MO4.Formatting.AlphabeticalUseStatements` sniff defines
37 | which function is used for ordering.
38 |
39 | Possible values for order:
40 | * `dictionary` (default): based on [strcmp](http://php.net/strcmp), the namespace separator
41 | precedes any other character
42 | ```php
43 | use Doctrine\ORM\Query;
44 | use Doctrine\ORM\Query\Expr;
45 | use Doctrine\ORM\QueryBuilder;
46 | ```
47 | * `string`: binary safe string comparison using [strcmp](http://php.net/strcmp)
48 | ```php
49 | use Doctrine\ORM\Query;
50 | use Doctrine\ORM\QueryBuilder;
51 | use Doctrine\ORM\Query\Expr;
52 |
53 | use ExampleSub;
54 | use Examples;
55 | ```
56 | * `string-locale`: locale based string comparison using [strcoll](http://php.net/strcoll)
57 | * `string-case-insensitive`: binary safe case-insensitive string comparison [strcasecmp](http://php.net/strcasecmp)
58 | ```php
59 | use Examples;
60 | use ExampleSub;
61 | ```
62 |
63 | To change the sorting order for your project, add this snippet to your custom `ruleset.xml`:
64 |
65 | ```xml
66 |
67 |
68 |
69 |
70 |
71 | ```
72 |
73 | ### MO4.Formatting.UnnecessaryNamespaceUsage
74 | * The imported class name must be used, when it was imported with a `use` statement.
75 |
76 | ### MO4.Strings.VariableInDoubleQuotedString
77 | * Interpolated variables in double quoted strings must be surrounded by `{ }`, e.g. `{$VAR}` instead of `$VAR`.
78 |
79 | ### MO4.WhiteSpace.ConstantSpacing
80 | * const must be followed by a single space.
81 |
82 | ### MO4.WhiteSpace.MultipleEmptyLines
83 | * No more than one empty consecutive line is allowed. Taken from [mediawiki/mediawiki-codesniffer](https://github.com/wikimedia/mediawiki-tools-codesniffer).
84 |
85 | ### Further rules (imported from other standards)
86 | * See `MO4/ruleset.xml`, which has each imported rule commented.
87 |
88 | Note that with this ruleset, the following [Symfony Coding Standard](http://symfony.com/doc/current/contributing/code/standards.html) rules are not enforced:
89 | * "`add doc blocks for all classes`": the doc block for classes can be omitted, if they add no value
90 | * "`the license block has to be present at the top of every PHP file, before the namespace`": the license block can be omitted
91 |
92 | Most of the issues can be auto-fixed with `phpcbf`.
93 |
94 | ## Requires
95 |
96 | * [PHP](http://php.net)
97 | * [Composer](https://getcomposer.org/) is optional, but strongly recommended
98 |
99 | ## Installation
100 |
101 | ### Composer
102 |
103 | Using [Composer](https://getcomposer.org/) is the preferred way.
104 |
105 | 1. Add the MO4 coding standard to `composer.json`
106 |
107 | composer require --dev mayflower/mo4-coding-standard
108 |
109 | 2. Profit
110 |
111 | ./vendor/bin/phpcs --standard=MO4 path/to/my/file.php
112 |
113 | 3. Optionally, you might set MO4 as default coding standard
114 |
115 | ./vendor/bin/phpcs --config-set default_standard MO4
116 |
117 | ### Source
118 |
119 | 1. Checkout this repository
120 |
121 | git clone https://github.com/mayflower/mo4-coding-standard.git
122 |
123 | 2. Install dependencies
124 |
125 | composer install
126 |
127 | 3. Check, that Symfony and MO4 are listed as coding standards
128 |
129 | ./vendor/bin/phpcs -i
130 |
131 | 4. Profit
132 |
133 | ./vendor/bin/phpcs --standard=MO4 path/to/my/file.php
134 |
135 | 5. Optionally, you might set MO4 as default coding standard
136 |
137 | ./vendor/bin/phpcs --config-set default_standard MO4
138 |
139 | ## Troubleshooting
140 |
141 | If `phpcs` complains that MO4 is not installed, please check the installed coding standards with
142 | `phpcs -i` and that `installed_paths` is set correctly with `phpcs --config-show`
143 |
144 | ## Dependencies
145 |
146 | * [PHP CodeSniffer](https://github.com/phpcsstandards/PHP_CodeSniffer)
147 | * [David Joos's Symfony Coding Standard](https://github.com/djoos/Symfony-coding-standard)
148 | * [Composer installer for PHP_CodeSniffer coding standards](https://github.com/DealerDirect/phpcodesniffer-composer-installer)
149 | * [Slevomat Coding Standard](https://github.com/slevomat/coding-standard)
150 |
151 | ## Contributing
152 |
153 | See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for information.
154 |
155 | ## License
156 |
157 | This project is licensed under the MIT license.
158 | See the [LICENSE](LICENSE) file for details.
159 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Arrays/ArrayDoubleArrowAlignmentSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Arrays;
16 |
17 | use PHP_CodeSniffer\Files\File;
18 | use PHP_CodeSniffer\Sniffs\Sniff;
19 | use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
20 |
21 | /**
22 | * Array Double Arrow Alignment sniff.
23 | *
24 | * '=>' must be aligned in arrays, and the key and the '=>' must be in the same line
25 | *
26 | * @author Xaver Loppenstedt
27 | *
28 | * @copyright 2013 Xaver Loppenstedt, some rights reserved.
29 | *
30 | * @license http://spdx.org/licenses/MIT MIT License
31 | *
32 | * @link https://github.com/mayflower/mo4-coding-standard
33 | *
34 | * @psalm-api
35 | */
36 | class ArrayDoubleArrowAlignmentSniff implements Sniff
37 | {
38 | /**
39 | * Define all types of arrays.
40 | *
41 | * @var array
42 | */
43 | protected $arrayTokens = [
44 | // @phan-suppress-next-line PhanUndeclaredConstant
45 | T_OPEN_SHORT_ARRAY,
46 | T_ARRAY,
47 | ];
48 |
49 | /**
50 | * Registers the tokens that this sniff wants to listen for.
51 | *
52 | * @return array
53 | *
54 | * @see Tokens.php
55 | */
56 | #[\Override]
57 | public function register(): array
58 | {
59 | return $this->arrayTokens;
60 | }
61 |
62 | /**
63 | * Processes this test, when one of its tokens is encountered.
64 | *
65 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
66 | *
67 | * @param File $phpcsFile The file being scanned.
68 | * @param int $stackPtr The position of the current token in
69 | * the stack passed in $tokens.
70 | *
71 | * @return void
72 | */
73 | #[\Override]
74 | public function process(File $phpcsFile, $stackPtr): void
75 | {
76 | $tokens = $phpcsFile->getTokens();
77 | $current = $tokens[$stackPtr];
78 |
79 | if (T_ARRAY === $current['code']) {
80 | $start = $current['parenthesis_opener'];
81 | $end = $current['parenthesis_closer'];
82 | } else {
83 | $start = $current['bracket_opener'];
84 | $end = $current['bracket_closer'];
85 | }
86 |
87 | if ($tokens[$start]['line'] === $tokens[$end]['line']) {
88 | return;
89 | }
90 |
91 | // phpcs:disable
92 | /** @var array $assignments */
93 | $assignments = [];
94 | // phpcs:enable
95 | $keyEndColumn = -1;
96 | $lastLine = -1;
97 |
98 | for ($i = ($start + 1); $i < $end; $i++) {
99 | $current = $tokens[$i];
100 | $previous = $tokens[($i - 1)];
101 |
102 | // Skip nested arrays.
103 | if (\in_array($current['code'], $this->arrayTokens, true)) {
104 | $i = T_ARRAY === $current['code'] ? ($current['parenthesis_closer'] + 1) : ($current['bracket_closer'] + 1);
105 |
106 | continue;
107 | }
108 |
109 | // Skip closures in array.
110 | if (T_CLOSURE === $current['code']) {
111 | $i = ($current['scope_closer'] + 1);
112 |
113 | continue;
114 | }
115 |
116 | $i = (int) $i;
117 |
118 | if (T_DOUBLE_ARROW !== $current['code']) {
119 | continue;
120 | }
121 |
122 | $assignments[] = $i;
123 | $column = $previous['column'];
124 | $line = $current['line'];
125 |
126 | if ($lastLine === $line) {
127 | $previousComma = $this->getPreviousComma($phpcsFile, $i, $start);
128 |
129 | $msg = 'only one "=>" assignments per line is allowed in a multi line array';
130 |
131 | if (false !== $previousComma) {
132 | $fixable = $phpcsFile->addFixableError($msg, $i, 'OneAssignmentPerLine');
133 |
134 | if (true === $fixable) {
135 | $phpcsFile->fixer->beginChangeset();
136 | $phpcsFile->fixer->addNewline((int) $previousComma);
137 | $phpcsFile->fixer->endChangeset();
138 | }
139 | } else {
140 | // Remove current and previous '=>' from array for further processing.
141 | \array_pop($assignments);
142 | \array_pop($assignments);
143 | $phpcsFile->addError($msg, $i, 'OneAssignmentPerLine');
144 | }
145 | }
146 |
147 | $hasKeyInLine = false;
148 |
149 | $j = ($i - 1);
150 |
151 | while (($j >= 0) && ($tokens[$j]['line'] === $current['line'])) {
152 | if (!\in_array($tokens[$j]['code'], PHP_CodeSniffer_Tokens::$emptyTokens, true)) {
153 | $hasKeyInLine = true;
154 | }
155 |
156 | $j--;
157 | }
158 |
159 | if (false === $hasKeyInLine) {
160 | $fixable = $phpcsFile->addFixableError(
161 | 'in arrays, keys and "=>" must be on the same line',
162 | $i,
163 | 'KeyAndValueNotOnSameLine'
164 | );
165 |
166 | if (true === $fixable) {
167 | $phpcsFile->fixer->beginChangeset();
168 | $phpcsFile->fixer->replaceToken($j, '');
169 | $phpcsFile->fixer->endChangeset();
170 | }
171 | }
172 |
173 | if ($column > $keyEndColumn) {
174 | $keyEndColumn = $column;
175 | }
176 |
177 | $lastLine = $line;
178 | }
179 |
180 | $doubleArrowStartColumn = ($keyEndColumn + 1);
181 |
182 | foreach ($assignments as $ptr) {
183 | $current = $tokens[$ptr];
184 | $column = $current['column'];
185 |
186 | $beforeArrowPtr = ($ptr - 1);
187 | $currentIndent = \strlen($tokens[$beforeArrowPtr]['content']);
188 | $correctIndent = ($currentIndent - $column + $doubleArrowStartColumn);
189 |
190 | if ($column === $doubleArrowStartColumn) {
191 | continue;
192 | }
193 |
194 | $fixable = $phpcsFile->addFixableError("each \"=>\" assignments must be aligned; current indentation before \"=>\" are {$currentIndent} space(s), must be {$correctIndent} space(s)", $ptr, 'AssignmentsNotAligned');
195 |
196 | if (false === $fixable) {
197 | continue;
198 | }
199 |
200 | $phpcsFile->fixer->beginChangeset();
201 |
202 | if (T_WHITESPACE === $tokens[$beforeArrowPtr]['code']) {
203 | $phpcsFile->fixer->replaceToken($beforeArrowPtr, \str_repeat(' ', $correctIndent));
204 | } else {
205 | $phpcsFile->fixer->addContent($beforeArrowPtr, \str_repeat(' ', $correctIndent));
206 | }
207 |
208 | $phpcsFile->fixer->endChangeset();
209 | }
210 | }
211 |
212 | /**
213 | * Find previous comma in array.
214 | *
215 | * @param File $phpcsFile The file being scanned.
216 | * @param int $stackPtr The position of the current token in
217 | * the stack passed in $tokens.
218 | * @param int $start Start of the array
219 | *
220 | * @return bool|int
221 | */
222 | private function getPreviousComma(File $phpcsFile, int $stackPtr, int $start)
223 | {
224 | $previousComma = false;
225 | $tokens = $phpcsFile->getTokens();
226 |
227 | $ptr = $phpcsFile->findPrevious([T_COMMA, T_CLOSE_SHORT_ARRAY], $stackPtr, $start);
228 |
229 | while (false !== $ptr) {
230 | if (T_COMMA === $tokens[$ptr]['code']) {
231 | $previousComma = $ptr;
232 |
233 | break;
234 | }
235 |
236 | if (T_CLOSE_SHORT_ARRAY === $tokens[$ptr]['code']) {
237 | $ptr = $tokens[$ptr]['bracket_opener'];
238 | }
239 |
240 | $ptr = $phpcsFile->findPrevious([T_COMMA, T_CLOSE_SHORT_ARRAY], ($ptr - 1), $start);
241 | }
242 |
243 | return $previousComma;
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Commenting/PropertyCommentSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Commenting;
16 |
17 | use MO4\Library\PregLibrary;
18 | use PHP_CodeSniffer\Exceptions\RuntimeException;
19 | use PHP_CodeSniffer\Files\File;
20 | use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
21 |
22 | /**
23 | * Property Comment Sniff sniff.
24 | *
25 | * Doc blocks of class properties must be multiline and have exactly one var
26 | * annotation.
27 | *
28 | * @author Xaver Loppenstedt
29 | *
30 | * @copyright 2013-2014 Xaver Loppenstedt, some rights reserved.
31 | *
32 | * @license http://spdx.org/licenses/MIT MIT License
33 | *
34 | * @link https://github.com/mayflower/mo4-coding-standard
35 | *
36 | * @psalm-api
37 | */
38 | class PropertyCommentSniff extends AbstractScopeSniff
39 | {
40 | /**
41 | * List of token types this sniff analyzes
42 | *
43 | * @var array
44 | */
45 | private $myTokenTypes = [
46 | T_VARIABLE,
47 | T_CONST,
48 | ];
49 |
50 | /**
51 | * Construct PropertyCommentSniff
52 | *
53 | * @throws RuntimeException
54 | */
55 | public function __construct()
56 | {
57 | $scopes = [T_CLASS];
58 |
59 | parent::__construct($scopes, $this->myTokenTypes, true);
60 | }
61 |
62 | /**
63 | * Processes a token that is found within the scope that this test is
64 | * listening to.
65 | *
66 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
67 | * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
68 | *
69 | * @param File $phpcsFile The file where this token was found.
70 | * @param int $stackPtr The position in the stack where this
71 | * token was found.
72 | * @param int $currScope The position in the tokens array that
73 | * opened the scope that this test is
74 | * listening for.
75 | *
76 | * @return void
77 | *
78 | * @throws RuntimeException
79 | */
80 | #[\Override]
81 | protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope): void
82 | {
83 | $find = [
84 | T_COMMENT,
85 | T_DOC_COMMENT_CLOSE_TAG,
86 | T_CLASS,
87 | T_CONST,
88 | T_FUNCTION,
89 | T_VARIABLE,
90 | T_OPEN_TAG,
91 | ];
92 | $tokens = $phpcsFile->getTokens();
93 |
94 | // Before even checking the doc blocks above the current var/const,
95 | // check if we have a single line comment after it on the same line,
96 | // and if that one is OK.
97 | $postComment = $phpcsFile->findNext(
98 | [
99 | T_DOC_COMMENT_OPEN_TAG,
100 | T_COMMENT,
101 | ],
102 | $stackPtr
103 | );
104 |
105 | if (false !== $postComment
106 | && $tokens[$postComment]['line'] === $tokens[$stackPtr]['line']
107 | ) {
108 | if ('/**' === $tokens[$postComment]['content']) {
109 | // That's an error already.
110 | $phpcsFile->addError(
111 | 'no doc blocks are allowed directly after declaration',
112 | $stackPtr,
113 | 'NoDocBlockAllowed'
114 | );
115 | } elseif (!\str_starts_with($tokens[$postComment]['content'], '//')
116 | && !\str_ends_with($tokens[$postComment]['content'], '*/')
117 | ) {
118 | $phpcsFile->addError(
119 | 'no multiline comments after declarations allowed',
120 | $stackPtr,
121 | 'MustBeOneLine'
122 | );
123 | }
124 | }
125 |
126 | // Don't do constants for now.
127 | if (T_CONST === $tokens[$stackPtr]['code']) {
128 | return;
129 | }
130 |
131 | $commentEnd = (int) $phpcsFile->findPrevious($find, ($stackPtr - 1));
132 |
133 | $conditions = $tokens[$commentEnd]['conditions'];
134 | $lastCondition = \array_pop($conditions);
135 |
136 | if (T_CLASS !== $lastCondition) {
137 | return;
138 | }
139 |
140 | $code = $tokens[$commentEnd]['code'];
141 |
142 | if (T_DOC_COMMENT_CLOSE_TAG === $code) {
143 | $commentStart = $tokens[$commentEnd]['comment_opener'];
144 |
145 | // Check if this comment is completely in one line,
146 | // above the current line,
147 | // and has a variable preceding it in the same line.
148 | // If yes, it doesn't count.
149 | $firstTokenOnLine = $phpcsFile->findFirstOnLine(
150 | $this->myTokenTypes,
151 | $commentEnd
152 | );
153 |
154 | if (false !== $firstTokenOnLine
155 | && $tokens[$commentStart]['line'] === $tokens[$commentEnd]['line']
156 | && $tokens[$stackPtr]['line'] > $tokens[$commentEnd]['line']
157 | ) {
158 | return;
159 | }
160 |
161 | $isCommentOneLiner
162 | = $tokens[$commentStart]['line'] === $tokens[$commentEnd]['line'];
163 |
164 | $length = ($commentEnd - $commentStart + 1);
165 | $tokensAsString = $phpcsFile->getTokensAsString(
166 | $commentStart,
167 | $length
168 | );
169 |
170 | $vars = PregLibrary::MO4PregSplit('/\s+@var\s+/', $tokensAsString);
171 |
172 | $varCount = (\count($vars) - 1);
173 |
174 | if ((0 === $varCount) || ($varCount > 1)) {
175 | $phpcsFile->addError(
176 | 'property doc comment must have exactly one @var annotation',
177 | $commentStart,
178 | 'MustHaveOneVarAnnotationDefined'
179 | );
180 | }
181 |
182 | if (1 === $varCount) {
183 | if (true === $isCommentOneLiner) {
184 | $fix = $phpcsFile->addFixableError(
185 | 'property doc comment must be multi line',
186 | $commentEnd,
187 | 'NotMultiLineDocBlock'
188 | );
189 |
190 | if (true === $fix) {
191 | $phpcsFile->fixer->beginChangeset();
192 | $phpcsFile->fixer->addContent($commentStart, "\n *");
193 | $phpcsFile->fixer->replaceToken(
194 | ($commentEnd - 1),
195 | \rtrim($tokens[($commentEnd - 1)]['content'])
196 | );
197 | $phpcsFile->fixer->addContentBefore($commentEnd, "\n ");
198 | $phpcsFile->fixer->endChangeset();
199 | }
200 | }
201 | } elseif (true === $isCommentOneLiner) {
202 | $phpcsFile->addError(
203 | 'property doc comment must be multi line',
204 | $commentEnd,
205 | 'NotMultiLineDocBlock'
206 | );
207 | }
208 | // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit
209 | } elseif (T_COMMENT === $code) {
210 | // It seems that when we are in here,
211 | // then we have a line comment at $commentEnd.
212 | // Now, check if the same comment has
213 | // a variable definition on the same line.
214 | // If yes, it doesn't count.
215 | $firstOnLine = $phpcsFile->findFirstOnLine(
216 | $this->myTokenTypes,
217 | $commentEnd
218 | );
219 |
220 | // phpcs:enable SlevomatCodingStandard.ControlStructures.EarlyExit
221 | if (false === $firstOnLine) {
222 | $commentStart = $phpcsFile->findPrevious(
223 | T_COMMENT,
224 | $commentEnd,
225 | 0,
226 | true
227 | );
228 | $phpcsFile->addError(
229 | 'property doc comment must begin with /**',
230 | ((int) $commentStart + 1),
231 | 'NotADocBlock'
232 | );
233 | }
234 | }
235 | }
236 |
237 | /**
238 | * Process tokens outside scope.
239 | *
240 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
241 | * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
242 | *
243 | * @param File $phpcsFile The file where this token was found.
244 | * @param int $stackPtr The position in the stack where this
245 | * token was found.
246 | *
247 | * @return void
248 | */
249 | #[\Override]
250 | protected function processTokenOutsideScope(File $phpcsFile, $stackPtr): void
251 | {
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Formatting/AlphabeticalUseStatementsSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Formatting;
16 |
17 | use PHP_CodeSniffer\Files\File;
18 | use PHP_CodeSniffer\Standards\PSR2\Sniffs\Namespaces\UseDeclarationSniff;
19 | use PHP_CodeSniffer\Util\Common;
20 | use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
21 |
22 | /**
23 | * Alphabetical Use Statements sniff.
24 | *
25 | * Use statements must be in alphabetical order, grouped by empty lines.
26 | *
27 | * @author Xaver Loppenstedt
28 | * @author Steffen Ritter
29 | * @author Christian Albrecht
30 | *
31 | * @copyright 2013-2017 Xaver Loppenstedt, some rights reserved.
32 | *
33 | * @license http://spdx.org/licenses/MIT MIT License
34 | *
35 | * @link https://github.com/mayflower/mo4-coding-standard
36 | *
37 | * @psalm-api
38 | */
39 | class AlphabeticalUseStatementsSniff extends UseDeclarationSniff
40 | {
41 | private const NAMESPACE_SEPARATOR_STRING = '\\';
42 |
43 | private const SUPPORTED_ORDERING_METHODS = [
44 | 'dictionary',
45 | 'string',
46 | 'string',
47 | 'string-locale',
48 | 'string-case-insensitive',
49 | ];
50 |
51 | /**
52 | * Sorting order, see SUPPORTED_ORDERING_METHODS for possible settings
53 | *
54 | * Unknown types will be mapped to 'string'.
55 | *
56 | * @var string
57 | */
58 | public $order = 'dictionary';
59 |
60 | /**
61 | * Last import seen in group
62 | *
63 | * @var string
64 | */
65 | private $lastImport = '';
66 |
67 | /**
68 | * Line number of the last seen use statement
69 | *
70 | * @var int
71 | */
72 | private $lastLine = -1;
73 |
74 | /**
75 | * Current file
76 | *
77 | * @var string
78 | */
79 | private $currentFile = '';
80 |
81 | /**
82 | * Processes this test, when one of its tokens is encountered.
83 | *
84 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
85 | *
86 | * @param File $phpcsFile The file being scanned.
87 | * @param int $stackPtr The position of the current token in
88 | * the stack passed in $tokens.
89 | *
90 | * @return void
91 | */
92 | #[\Override]
93 | public function process(File $phpcsFile, $stackPtr): void
94 | {
95 | if (!\in_array($this->order, self::SUPPORTED_ORDERING_METHODS, true)) {
96 | $error = \sprintf(
97 | "'%s' is not a valid order function for %s! Pick one of: %s",
98 | $this->order,
99 | Common::getSniffCode(self::class),
100 | \implode(', ', self::SUPPORTED_ORDERING_METHODS)
101 | );
102 |
103 | $phpcsFile->addError($error, $stackPtr, 'InvalidOrder');
104 |
105 | return;
106 | }
107 |
108 | parent::process($phpcsFile, $stackPtr);
109 |
110 | if ($this->currentFile !== $phpcsFile->getFilename()) {
111 | $this->lastLine = -1;
112 | $this->lastImport = '';
113 | $this->currentFile = $phpcsFile->getFilename();
114 | }
115 |
116 | $tokens = $phpcsFile->getTokens();
117 | $line = $tokens[$stackPtr]['line'];
118 |
119 | // Ignore function () use () {...}.
120 | $isNonImportUse = $this->checkIsNonImportUse($phpcsFile, $stackPtr);
121 |
122 | if (true === $isNonImportUse) {
123 | return;
124 | }
125 |
126 | $currentImportArr = $this->getUseImport($phpcsFile, $stackPtr);
127 |
128 | if (false === $currentImportArr) {
129 | return;
130 | }
131 |
132 | $currentPtr = $currentImportArr['startPtr'];
133 | $currentImport = $currentImportArr['content'];
134 |
135 | if (($this->lastLine + 1) < $line) {
136 | $this->lastLine = $line;
137 | $this->lastImport = $currentImport;
138 |
139 | return;
140 | }
141 |
142 | $fixable = false;
143 |
144 | if ('' !== $this->lastImport
145 | && $this->compareString($this->lastImport, $currentImport) > 0
146 | ) {
147 | $msg = 'USE statements must be sorted alphabetically, order %s';
148 | $code = 'MustBeSortedAlphabetically';
149 | $fixable = $phpcsFile->addFixableError($msg, $currentPtr, $code, [$this->order]);
150 | }
151 |
152 | if (true === $fixable) {
153 | // Find the correct position in current use block.
154 | $newDestinationPtr
155 | = $this->findNewDestination($phpcsFile, $stackPtr, $currentImport);
156 |
157 | $currentUseStr = $this->getUseStatementAsString($phpcsFile, $stackPtr);
158 |
159 | $phpcsFile->fixer->beginChangeset();
160 | $phpcsFile->fixer->addContentBefore($newDestinationPtr, $currentUseStr);
161 | $this->fixerClearLine($phpcsFile, $stackPtr);
162 | $phpcsFile->fixer->endChangeset();
163 | }
164 |
165 | $this->lastImport = $currentImport;
166 | $this->lastLine = $line;
167 | }
168 |
169 | /**
170 | * Get the import class name for use statement pointed by $stackPtr.
171 | *
172 | * @param File $phpcsFile PHP CS File
173 | * @param int $stackPtr pointer
174 | *
175 | * @return array|false
176 | */
177 | private function getUseImport(File $phpcsFile, int $stackPtr)
178 | {
179 | $importTokens = [
180 | T_NS_SEPARATOR,
181 | T_STRING,
182 | ];
183 |
184 | $start = $phpcsFile->findNext(
185 | PHP_CodeSniffer_Tokens::$emptyTokens,
186 | ($stackPtr + 1),
187 | null,
188 | true
189 | );
190 |
191 | // $start is false when "use" is the last token in file...
192 | if (false === $start) {
193 | return false;
194 | }
195 |
196 | $end = (int) $phpcsFile->findNext($importTokens, $start, null, true);
197 | $import = $phpcsFile->getTokensAsString($start, ($end - $start));
198 |
199 | return [
200 | 'startPtr' => $start,
201 | 'content' => $import,
202 | ];
203 | }
204 |
205 | /**
206 | * Get the full use statement as string, including trailing white space.
207 | *
208 | * @param File $phpcsFile PHP CS File
209 | * @param int $stackPtr pointer
210 | *
211 | * @return string
212 | */
213 | private function getUseStatementAsString(File $phpcsFile, int $stackPtr): string
214 | {
215 | $tokens = $phpcsFile->getTokens();
216 |
217 | $useEndPtr = (int) $phpcsFile->findNext([T_SEMICOLON], ($stackPtr + 2));
218 | $useLength = ($useEndPtr - $stackPtr + 1);
219 |
220 | if (T_WHITESPACE === $tokens[($useEndPtr + 1)]['code']) {
221 | $useLength++;
222 | }
223 |
224 | return $phpcsFile->getTokensAsString($stackPtr, $useLength);
225 | }
226 |
227 | /**
228 | * Check if "use" token is not used for import.
229 | * E.g. function () use () {...}.
230 | *
231 | * @param File $phpcsFile PHP CS File
232 | * @param int $stackPtr pointer
233 | *
234 | * @return bool
235 | */
236 | private function checkIsNonImportUse(File $phpcsFile, int $stackPtr): bool
237 | {
238 | $tokens = $phpcsFile->getTokens();
239 |
240 | $prev = $phpcsFile->findPrevious(
241 | PHP_CodeSniffer_Tokens::$emptyTokens,
242 | ($stackPtr - 1),
243 | 0,
244 | true,
245 | null,
246 | true
247 | );
248 |
249 | if (false !== $prev) {
250 | $prevToken = $tokens[$prev];
251 |
252 | if (T_CLOSE_PARENTHESIS === $prevToken['code']) {
253 | return true;
254 | }
255 | }
256 |
257 | return false;
258 | }
259 |
260 | /**
261 | * Replace all the token in same line as the element pointed to by $stackPtr
262 | * the by the empty string.
263 | * This will delete the line.
264 | *
265 | * @param File $phpcsFile PHP CS file
266 | * @param int $stackPtr pointer
267 | *
268 | * @return void
269 | */
270 | private function fixerClearLine(File $phpcsFile, int $stackPtr): void
271 | {
272 | $tokens = $phpcsFile->getTokens();
273 | $line = $tokens[$stackPtr]['line'];
274 |
275 | for ($i = ($stackPtr - 1); $tokens[$i]['line'] === $line; $i--) {
276 | $phpcsFile->fixer->replaceToken($i, '');
277 | }
278 |
279 | for ($i = $stackPtr; $tokens[$i]['line'] === $line; $i++) {
280 | $phpcsFile->fixer->replaceToken($i, '');
281 | }
282 | }
283 |
284 | /**
285 | * Find a new destination pointer for the given import string in current
286 | * use block.
287 | *
288 | * @param File $phpcsFile PHP CS File
289 | * @param int $stackPtr pointer
290 | * @param string $import import string requiring new position
291 | *
292 | * @return int
293 | */
294 | private function findNewDestination(File $phpcsFile, int $stackPtr, string $import): int
295 | {
296 | $tokens = $phpcsFile->getTokens();
297 |
298 | $line = $tokens[$stackPtr]['line'];
299 | /** @var int|bool $prevLine */
300 | $prevLine = false;
301 | $prevPtr = $stackPtr;
302 |
303 | do {
304 | $ptr = $prevPtr;
305 |
306 | // Use $line for the first iteration.
307 | if (false !== $prevLine) {
308 | $line = $prevLine;
309 | }
310 |
311 | $prevPtr = $phpcsFile->findPrevious(T_USE, ($ptr - 1));
312 |
313 | if (false === $prevPtr) {
314 | break;
315 | }
316 |
317 | $prevLine = $tokens[$prevPtr]['line'];
318 | // phpcs:disable
319 | /** @var array $prevImportArr */
320 | $prevImportArr = $this->getUseImport($phpcsFile, $prevPtr);
321 | // phpcs:enable
322 | } while ($prevLine === ($line - 1)
323 | && ($this->compareString($prevImportArr['content'], $import) > 0)
324 | );
325 |
326 | return $ptr;
327 | }
328 |
329 | /**
330 | * Compare namespace strings according defined order function.
331 | *
332 | * @param string $a first namespace string
333 | * @param string $b second namespace string
334 | *
335 | * @return int
336 | */
337 | private function compareString(string $a, string $b): int
338 | {
339 | return match ($this->order) {
340 | 'string' => \strcmp($a, $b),
341 | 'string-locale' => \strcoll($a, $b),
342 | 'string-case-insensitive' => \strcasecmp($a, $b),
343 | // Default is 'dictionary'.
344 | default => $this->dictionaryCompare($a, $b),
345 | };
346 | }
347 |
348 | /**
349 | * Lexicographical namespace string compare.
350 | *
351 | * Example:
352 | *
353 | * use Doctrine\ORM\Query;
354 | * use Doctrine\ORM\Query\Expr;
355 | * use Doctrine\ORM\QueryBuilder;
356 | *
357 | * @param string $a first namespace string
358 | * @param string $b second namespace string
359 | *
360 | * @return int
361 | */
362 | private function dictionaryCompare(string $a, string $b): int
363 | {
364 | $min = \min(\strlen($a), \strlen($b));
365 |
366 | for ($i = 0; $i < $min; $i++) {
367 | if ($a[$i] === $b[$i]) {
368 | continue;
369 | }
370 |
371 | if (self::NAMESPACE_SEPARATOR_STRING === $a[$i]) {
372 | return -1;
373 | }
374 |
375 | if (self::NAMESPACE_SEPARATOR_STRING === $b[$i]) {
376 | return 1;
377 | }
378 |
379 | if ($a[$i] < $b[$i]) {
380 | return -1;
381 | }
382 |
383 | if ($a[$i] > $b[$i]) {
384 | return 1;
385 | }
386 | }
387 |
388 | return \strcmp(\substr($a, $min), \substr($b, $min));
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/MO4/Sniffs/Formatting/UnnecessaryNamespaceUsageSniff.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://spdx.org/licenses/MIT MIT License
9 | *
10 | * @link https://github.com/mayflower/mo4-coding-standard
11 | */
12 |
13 | declare(strict_types=1);
14 |
15 | namespace MO4\Sniffs\Formatting;
16 |
17 | use MO4\Library\PregLibrary;
18 | use PHP_CodeSniffer\Exceptions\RuntimeException;
19 | use PHP_CodeSniffer\Files\File;
20 | use PHP_CodeSniffer\Sniffs\Sniff;
21 | use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
22 |
23 | /**
24 | * Unnecessary Namespace Usage sniff.
25 | *
26 | * Full namespace declaration should be skipped in favour of the short declaration.
27 | *
28 | * @author Xaver Loppenstedt
29 | * @author Marco Jantke
30 | * @author Steffen Ritter
31 | *
32 | * @copyright 2013 Xaver Loppenstedt, some rights reserved.
33 | *
34 | * @license http://spdx.org/licenses/MIT MIT License
35 | *
36 | * @link https://github.com/mayflower/mo4-coding-standard
37 | *
38 | * @psalm-api
39 | */
40 | class UnnecessaryNamespaceUsageSniff implements Sniff
41 | {
42 | /**
43 | * Tokens used in full class name.
44 | *
45 | * @var array
46 | */
47 | private $classNameTokens = [
48 | T_NS_SEPARATOR,
49 | T_STRING,
50 | ];
51 |
52 | /**
53 | * Registers the tokens that this sniff wants to listen for.
54 | *
55 | * @return array
56 | *
57 | * @see Tokens.php
58 | */
59 | #[\Override]
60 | public function register(): array
61 | {
62 | return [T_CLASS];
63 | }
64 |
65 | /**
66 | * Called when one of the token types that this sniff is listening for
67 | * is found.
68 | *
69 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
70 | *
71 | * @param File $phpcsFile The PHP_CodeSniffer file where the
72 | * token was found.
73 | * @param int $stackPtr The position in the PHP_CodeSniffer
74 | * file's token stack where the token
75 | * was found.
76 | *
77 | * @return void
78 | *
79 | * @throws RuntimeException
80 | */
81 | #[\Override]
82 | public function process(File $phpcsFile, $stackPtr): void
83 | {
84 | $docCommentTags = [
85 | '@param' => 1,
86 | '@return' => 1,
87 | '@throws' => 1,
88 | '@var' => 2,
89 | ];
90 | $scanTokens = [
91 | T_NS_SEPARATOR,
92 | T_DOC_COMMENT_OPEN_TAG,
93 | ];
94 |
95 | $tokens = $phpcsFile->getTokens();
96 | $useStatements = $this->getUseStatements($phpcsFile, 0, ($stackPtr - 1));
97 | $namespace = $this->getNamespace($phpcsFile, 0, ($stackPtr - 1));
98 |
99 | $nsSep = $phpcsFile->findNext($scanTokens, ($stackPtr + 1));
100 |
101 | while (false !== $nsSep) {
102 | $classNameEnd = (int) $phpcsFile->findNext(
103 | $this->classNameTokens,
104 | $nsSep,
105 | null,
106 | true
107 | );
108 |
109 | if (T_NS_SEPARATOR === $tokens[$nsSep]['code']) {
110 | if (T_STRING === $tokens[($nsSep - 1)]['code']) {
111 | --$nsSep;
112 | }
113 |
114 | $className = $phpcsFile->getTokensAsString(
115 | $nsSep,
116 | ($classNameEnd - $nsSep)
117 | );
118 |
119 | $this->checkShorthandPossible(
120 | $phpcsFile,
121 | $useStatements,
122 | $className,
123 | $namespace,
124 | $nsSep,
125 | ($classNameEnd - 1)
126 | );
127 | } else {
128 | // Doc comment block.
129 | foreach ($tokens[$nsSep]['comment_tags'] as $tag) {
130 | $content = $tokens[$tag]['content'];
131 |
132 | if (!\array_key_exists($content, $docCommentTags)) {
133 | continue;
134 | }
135 |
136 | $next = ($tag + 1);
137 | // PHP Code Sniffer will magically add T_DOC_COMMENT_CLOSE_TAG with empty string content.
138 | $lineEnd = $phpcsFile->findNext(
139 | [
140 | T_DOC_COMMENT_CLOSE_TAG,
141 | T_DOC_COMMENT_STAR,
142 | ],
143 | $next
144 | );
145 |
146 | $docCommentStringPtr = $phpcsFile->findNext(
147 | [T_DOC_COMMENT_STRING],
148 | $next,
149 | (int) $lineEnd
150 | );
151 |
152 | if (false === $docCommentStringPtr) {
153 | continue;
154 | }
155 |
156 | $docLine = $tokens[$docCommentStringPtr]['content'];
157 |
158 | $docLineTokens = PregLibrary::MO4PregSplit(
159 | '/\s+/',
160 | $docLine,
161 | -1,
162 | PREG_SPLIT_NO_EMPTY
163 | );
164 |
165 | // phpcs:disable
166 | /** @var array $docLineTokens */
167 | $docLineTokens = \array_slice(
168 | $docLineTokens,
169 | 0,
170 | $docCommentTags[$content]
171 | );
172 | // phpcs:enable
173 |
174 | foreach ($docLineTokens as $docLineToken) {
175 | // phpcs:disable
176 | /** @var array $typeTokens */
177 | $typeTokens = PregLibrary::MO4PregSplit(
178 | '/\|/',
179 | $docLineToken,
180 | -1,
181 | PREG_SPLIT_NO_EMPTY
182 | );
183 | // phpcs:enable
184 |
185 | foreach ($typeTokens as $typeToken) {
186 | if (\in_array($typeToken, $useStatements, true)) {
187 | continue;
188 | }
189 |
190 | $this->checkShorthandPossible(
191 | $phpcsFile,
192 | $useStatements,
193 | $typeToken,
194 | $namespace,
195 | $docCommentStringPtr,
196 | $docCommentStringPtr,
197 | true
198 | );
199 | }
200 | }
201 | }
202 | }
203 |
204 | $nsSep = $phpcsFile->findNext($scanTokens, ($classNameEnd + 1));
205 | }
206 | }
207 |
208 | /**
209 | * Get all use statements in range
210 | *
211 | * @param File $phpcsFile PHP CS File
212 | * @param int $start start pointer
213 | * @param int $end end pointer
214 | *
215 | * @return array
216 | */
217 | protected function getUseStatements(File $phpcsFile, int $start, int $end): array
218 | {
219 | $useStatements = [];
220 | $i = $start;
221 | $tokens = $phpcsFile->getTokens();
222 | $useTokenPtr = $phpcsFile->findNext(T_USE, $i, $end);
223 |
224 | while (false !== $useTokenPtr) {
225 | $classNameStart = (int) $phpcsFile->findNext(
226 | PHP_CodeSniffer_Tokens::$emptyTokens,
227 | ($useTokenPtr + 1),
228 | $end,
229 | true
230 | );
231 | $classNameEnd = $phpcsFile->findNext(
232 | $this->classNameTokens,
233 | ($classNameStart + 1),
234 | $end,
235 | true
236 | );
237 |
238 | if (false === $classNameEnd) {
239 | break;
240 | }
241 |
242 | $useEnd = $phpcsFile->findNext(
243 | [
244 | T_SEMICOLON,
245 | T_COMMA,
246 | ],
247 | $classNameEnd,
248 | $end
249 | );
250 |
251 | // Prevent endless loop when 'use ;' is the last use statement.
252 | if (false === $useEnd) {
253 | break;
254 | }
255 |
256 | /** @var int $aliasNamePtr */
257 | $aliasNamePtr = $phpcsFile->findPrevious(
258 | PHP_CodeSniffer_Tokens::$emptyTokens,
259 | ($useEnd - 1),
260 | 0,
261 | true
262 | );
263 |
264 | $length = ($classNameEnd - $classNameStart);
265 | $className = $phpcsFile->getTokensAsString($classNameStart, $length);
266 |
267 | $className = $this->getFullyQualifiedClassName($className);
268 | $useStatements[$className] = $tokens[$aliasNamePtr]['content'];
269 | $i = ($useEnd + 1);
270 |
271 | $useTokenPtr = T_COMMA === $tokens[$useEnd]['code'] ? $i : $phpcsFile->findNext(T_USE, $i, $end);
272 | }
273 |
274 | return $useStatements;
275 | }
276 |
277 | /**
278 | * Get the namespace of the current class file
279 | *
280 | * @param File $phpcsFile PHP CS File
281 | * @param int $start start pointer
282 | * @param int $end end pointer
283 | *
284 | * @return string
285 | */
286 | protected function getNamespace(File $phpcsFile, int $start, int $end): string
287 | {
288 | $namespace = (int) $phpcsFile->findNext(T_NAMESPACE, $start, $end);
289 | $namespaceStart = $phpcsFile->findNext(
290 | PHP_CodeSniffer_Tokens::$emptyTokens,
291 | ($namespace + 1),
292 | $end,
293 | true
294 | );
295 |
296 | if (false === $namespaceStart) {
297 | return '';
298 | }
299 |
300 | $namespaceEnd = (int) $phpcsFile->findNext(
301 | $this->classNameTokens,
302 | ($namespaceStart + 1),
303 | $end,
304 | true
305 | );
306 |
307 | $nslen = ($namespaceEnd - $namespaceStart);
308 | $name = $phpcsFile->getTokensAsString($namespaceStart, $nslen);
309 |
310 | return "\\{$name}\\";
311 | }
312 |
313 | /**
314 | * Return the fully qualified class name, e.g. '\Foo\Bar\Faz'
315 | *
316 | * @param string $className class name
317 | *
318 | * @return string
319 | */
320 | private function getFullyQualifiedClassName(string $className): string
321 | {
322 | return '\\' !== $className[0] ? "\\{$className}" : $className;
323 | }
324 |
325 | /**
326 | * Check if short hand is possible.
327 | *
328 | * @param File $phpcsFile PHP CS File
329 | * @param array $useStatements array with class use statements
330 | * @param string $className class name
331 | * @param string $namespace name space
332 | * @param int $startPtr start token pointer
333 | * @param int $endPtr end token pointer
334 | * @param bool $isDocBlock true if fixing doc block
335 | *
336 | * @return void
337 | */
338 | private function checkShorthandPossible(File $phpcsFile, array $useStatements, string $className, string $namespace, int $startPtr, int $endPtr, bool $isDocBlock = false): void
339 | {
340 | $msg = 'Shorthand possible. Replace "%s" with "%s"';
341 | $code = 'UnnecessaryNamespaceUsage';
342 | $fixable = false;
343 | $replaceClassName = false;
344 | $replacement = '';
345 |
346 | $fullClassName = $this->getFullyQualifiedClassName($className);
347 |
348 | if (\array_key_exists($fullClassName, $useStatements)) {
349 | $replacement = $useStatements[$fullClassName];
350 |
351 | $data = [
352 | $className,
353 | $replacement,
354 | ];
355 |
356 | $fixable = $phpcsFile->addFixableWarning(
357 | $msg,
358 | $startPtr,
359 | $code,
360 | $data
361 | );
362 |
363 | $replaceClassName = true;
364 | } elseif ('' !== $namespace && \str_starts_with($fullClassName, $namespace)) {
365 | $replacement = \substr($fullClassName, \strlen($namespace));
366 |
367 | $data = [
368 | $className,
369 | $replacement,
370 | ];
371 | $fixable = $phpcsFile->addFixableWarning(
372 | $msg,
373 | $startPtr,
374 | $code,
375 | $data
376 | );
377 | }
378 |
379 | if (true !== $fixable) {
380 | return;
381 | }
382 |
383 | $phpcsFile->fixer->beginChangeset();
384 |
385 | if (true === $isDocBlock) {
386 | $tokens = $phpcsFile->getTokens();
387 | $oldContent = $tokens[$startPtr]['content'];
388 | /** @var string $newContent */
389 | $newContent = \str_replace($className, $replacement, $oldContent);
390 | $phpcsFile->fixer->replaceToken($startPtr, $newContent);
391 | } else {
392 | for ($i = $startPtr; $i < $endPtr; $i++) {
393 | $phpcsFile->fixer->replaceToken($i, '');
394 | }
395 |
396 | if (true === $replaceClassName) {
397 | $phpcsFile->fixer->replaceToken($endPtr, $replacement);
398 | }
399 | }
400 |
401 | $phpcsFile->fixer->endChangeset();
402 | }
403 | }
404 |
--------------------------------------------------------------------------------
/MO4/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The MO4 PHP coding standard.
4 |
5 | */Resources/*
6 |
7 |
8 |
9 |
10 |
11 |
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 |
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 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 | 0
241 |
242 |
243 | 0
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 0
255 |
256 |
257 | 0
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
--------------------------------------------------------------------------------