From 1fb88fe8f4abc3ceaa5667a18703f80f6c470816 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Thu, 10 Aug 2023 18:16:41 +0200 Subject: [PATCH 01/10] Revert "Lower the "and" and "or" uses down from error to warning" This reverts commit c85e30fbbce432a04b5f3ddb39b08b1729499f57. Fixes #5. --- moodle/ruleset.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/moodle/ruleset.xml b/moodle/ruleset.xml index 059922c..58a9e80 100644 --- a/moodle/ruleset.xml +++ b/moodle/ruleset.xml @@ -56,9 +56,7 @@ - - warning - + From 4b6b19c37c7d2b2d4763e28b5474f7535d7b2691 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Mon, 28 Aug 2023 18:30:02 +0200 Subject: [PATCH 02/10] Update composer dependencies to actual ones - All them must work for php74 and up. - PHPCompatibility bump to current dev version: 0a17f9ed Also, update the CHANGELOG file. --- CHANGELOG.md | 3 +++ composer.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e66c72..e3ba851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). The format of this change log follows the advice given at [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Changed +- Update composer dependencies to current versions, notably PHPCompatibility (0a17f9ed). +- Enforce the use of `&&` and `||` logical operators, **now erroring** (after a grace period of 1 year) with `and` and `or` uses: `Squiz.Operators.ValidLogicalOperators` ## [v3.3.4] - 2023-05-28 ### Changed diff --git a/composer.json b/composer.json index a488ec3..cfbd24a 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", "squizlabs/php_codesniffer": "^3.7.2", - "phpcompatibility/php-compatibility": "dev-develop#70e4ca24" + "phpcompatibility/php-compatibility": "dev-develop#0a17f9ed" }, "config": { "allow-plugins": { From abc742c6eb3938ac04fb3120051a1a8c25822866 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Mon, 28 Aug 2023 18:58:23 +0200 Subject: [PATCH 03/10] Prepare the v3.3.5 release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3ba851..7669e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). The format of this change log follows the advice given at [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + +## [v3.3.5] - 2023-08-28 ### Changed - Update composer dependencies to current versions, notably PHPCompatibility (0a17f9ed). - Enforce the use of `&&` and `||` logical operators, **now erroring** (after a grace period of 1 year) with `and` and `or` uses: `Squiz.Operators.ValidLogicalOperators` @@ -54,7 +56,8 @@ All features are maintained and no new features have been introduced to either t All the details about [previous releases] can be found in [local_codechecker](https://github.com/moodlehq/moodle-local_codechecker) own change log. -[Unreleased]: https://github.com/moodlehq/moodle-cs/compare/v3.3.4...main +[Unreleased]: https://github.com/moodlehq/moodle-cs/compare/v3.3.5...main +[v3.3.5]: https://github.com/moodlehq/moodle-cs/compare/v3.3.4...v3.3.5 [v3.3.4]: https://github.com/moodlehq/moodle-cs/compare/v3.3.3...v3.3.4 [v3.3.3]: https://github.com/moodlehq/moodle-cs/compare/v3.3.2...v3.3.3 [v3.3.2]: https://github.com/moodlehq/moodle-cs/compare/v3.3.1...v3.3.2 From e0c0f86ef6367c0941b1ffbf918ed2017fc5d19b Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Thu, 14 Sep 2023 16:21:42 +0200 Subject: [PATCH 04/10] Rename MoodleCSBaseTest to MoodleCSBaseTestCase As a safety check, abstract classes are not allowed to end with [Tt]est anymore in PHPUnit 10. We were doing that with the MoodleCSBaseTest class, hence moving to TestCase suffix. Also, the MoodleCSStandardTest was incorrectly named (not matching the file name that was correct), so renamed it to MoodleStandardTest. These changes come onboard before start adding APIs support to the standard (first in PHPUnit classes and, once working) to the whole codebase (that's the plan). --- moodle/Tests/FilesBoilerPlateCommentTest.php | 2 +- moodle/Tests/FilesMoodleInternalTest.php | 2 +- moodle/Tests/{MoodleCSBaseTest.php => MoodleCSBaseTestCase.php} | 2 +- moodle/Tests/MoodleStandardTest.php | 2 +- moodle/Tests/MoodleUtilTest.php | 2 +- moodle/Tests/NamingConventionsValidFunctionNameTest.php | 2 +- moodle/Tests/PHPIncludingFileTest.php | 2 +- moodle/Tests/PHPMemberVarScopeTest.php | 2 +- moodle/Tests/PHPUnitTestCaseCoversTest.php | 2 +- moodle/Tests/PHPUnitTestCaseNamesTest.php | 2 +- moodle/Tests/SquizArraysArrayBrackerSpacingTest.php | 2 +- moodle/Tests/SquizOperatorsValidLogicalOperatorsTest.php | 2 +- moodle/Tests/WhiteSpaceSpaceAfterCommaTest.php | 2 +- moodle/Tests/WhiteSpaceWhiteSpaceInStringsTest.php | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) rename moodle/Tests/{MoodleCSBaseTest.php => MoodleCSBaseTestCase.php} (99%) diff --git a/moodle/Tests/FilesBoilerPlateCommentTest.php b/moodle/Tests/FilesBoilerPlateCommentTest.php index 7c33068..1c4c981 100644 --- a/moodle/Tests/FilesBoilerPlateCommentTest.php +++ b/moodle/Tests/FilesBoilerPlateCommentTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Files\BoilerplateCommentSniff */ -class FilesBoilerPlateCommentTest extends MoodleCSBaseTest { +class FilesBoilerPlateCommentTest extends MoodleCSBaseTestCase { public function test_moodle_files_boilerplatecomment_ok() { $this->set_standard('moodle'); diff --git a/moodle/Tests/FilesMoodleInternalTest.php b/moodle/Tests/FilesMoodleInternalTest.php index d062213..561540a 100644 --- a/moodle/Tests/FilesMoodleInternalTest.php +++ b/moodle/Tests/FilesMoodleInternalTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Files\MoodleInternalSniff */ -class FilesMoodleInternalTest extends MoodleCSBaseTest { +class FilesMoodleInternalTest extends MoodleCSBaseTestCase { public function test_moodle_files_moodleinternal_problem() { $this->set_standard('moodle'); diff --git a/moodle/Tests/MoodleCSBaseTest.php b/moodle/Tests/MoodleCSBaseTestCase.php similarity index 99% rename from moodle/Tests/MoodleCSBaseTest.php rename to moodle/Tests/MoodleCSBaseTestCase.php index d39e158..a2dfaaf 100644 --- a/moodle/Tests/MoodleCSBaseTest.php +++ b/moodle/Tests/MoodleCSBaseTestCase.php @@ -39,7 +39,7 @@ * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -abstract class MoodleCSBaseTest extends \PHPUnit\Framework\TestCase { +abstract class MoodleCSBaseTestCase extends \PHPUnit\Framework\TestCase { /** * @var string name of the standard to be tested. diff --git a/moodle/Tests/MoodleStandardTest.php b/moodle/Tests/MoodleStandardTest.php index 2a7f4d4..4e3b873 100644 --- a/moodle/Tests/MoodleStandardTest.php +++ b/moodle/Tests/MoodleStandardTest.php @@ -30,7 +30,7 @@ * * @todo Complete coverage of all Sniffs. */ -class MoodleCsStandardTest extends MoodleCSBaseTest { +class MoodleStandardTest extends MoodleCSBaseTestCase { /** * Test the PSR2.Methods.MethodDeclaration sniff. diff --git a/moodle/Tests/MoodleUtilTest.php b/moodle/Tests/MoodleUtilTest.php index 5f19303..c49c5cf 100644 --- a/moodle/Tests/MoodleUtilTest.php +++ b/moodle/Tests/MoodleUtilTest.php @@ -35,7 +35,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Util\MoodleUtil */ -class MoodleUtilTest extends MoodleCSBaseTest { +class MoodleUtilTest extends MoodleCSBaseTestCase { /** * Unit test for calculateAllComponents. diff --git a/moodle/Tests/NamingConventionsValidFunctionNameTest.php b/moodle/Tests/NamingConventionsValidFunctionNameTest.php index 4425c93..501830c 100644 --- a/moodle/Tests/NamingConventionsValidFunctionNameTest.php +++ b/moodle/Tests/NamingConventionsValidFunctionNameTest.php @@ -30,7 +30,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\NamingConventions\ValidFunctionNameSniff */ -class NamingConventionsValidFunctionNameTest extends MoodleCSBaseTest { +class NamingConventionsValidFunctionNameTest extends MoodleCSBaseTestCase { /** * Data provider for self::test_namingconventions_validfunctionname diff --git a/moodle/Tests/PHPIncludingFileTest.php b/moodle/Tests/PHPIncludingFileTest.php index b23cd5e..641f1a9 100644 --- a/moodle/Tests/PHPIncludingFileTest.php +++ b/moodle/Tests/PHPIncludingFileTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHP\IncludingFileSniff */ -class PHPIncludingFileTest extends MoodleCSBaseTest { +class PHPIncludingFileTest extends MoodleCSBaseTestCase { public function test_php_includingfile() { // Define the standard, sniff and fixture to use. diff --git a/moodle/Tests/PHPMemberVarScopeTest.php b/moodle/Tests/PHPMemberVarScopeTest.php index 37727fd..9becd59 100644 --- a/moodle/Tests/PHPMemberVarScopeTest.php +++ b/moodle/Tests/PHPMemberVarScopeTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHP\MemberVarScopeSniff */ -class PHPMemberVarScopeTest extends MoodleCSBaseTest { +class PHPMemberVarScopeTest extends MoodleCSBaseTestCase { public function test_php_membervarscope() { // Define the standard, sniff and fixture to use. diff --git a/moodle/Tests/PHPUnitTestCaseCoversTest.php b/moodle/Tests/PHPUnitTestCaseCoversTest.php index 55649c5..67434b1 100644 --- a/moodle/Tests/PHPUnitTestCaseCoversTest.php +++ b/moodle/Tests/PHPUnitTestCaseCoversTest.php @@ -30,7 +30,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit\TestCaseCoversSniff */ -class PHPUnitTestCaseCoversTest extends MoodleCSBaseTest { +class PHPUnitTestCaseCoversTest extends MoodleCSBaseTestCase { /** * Data provider for self::test_phpunit_testcasecovers diff --git a/moodle/Tests/PHPUnitTestCaseNamesTest.php b/moodle/Tests/PHPUnitTestCaseNamesTest.php index fae5e4a..ca64fe8 100644 --- a/moodle/Tests/PHPUnitTestCaseNamesTest.php +++ b/moodle/Tests/PHPUnitTestCaseNamesTest.php @@ -30,7 +30,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit\TestCaseNamesSniff */ -class PHPUnitTestCaseNamesTest extends MoodleCSBaseTest { +class PHPUnitTestCaseNamesTest extends MoodleCSBaseTestCase { /** * Data provider for self::test_phpunit_testcasenames diff --git a/moodle/Tests/SquizArraysArrayBrackerSpacingTest.php b/moodle/Tests/SquizArraysArrayBrackerSpacingTest.php index 91e9551..5731d18 100644 --- a/moodle/Tests/SquizArraysArrayBrackerSpacingTest.php +++ b/moodle/Tests/SquizArraysArrayBrackerSpacingTest.php @@ -28,7 +28,7 @@ * * @covers \PHP_CodeSniffer\Standards\Squiz\Sniffs\Arrays\ArrayBracketSpacingSniff */ -class SquizArraysArrayBrackerSpacingTest extends MoodleCSBaseTest { +class SquizArraysArrayBrackerSpacingTest extends MoodleCSBaseTestCase { /** * Test the Squid.Arrays.ArrayBracketSpacing sniff diff --git a/moodle/Tests/SquizOperatorsValidLogicalOperatorsTest.php b/moodle/Tests/SquizOperatorsValidLogicalOperatorsTest.php index bbfd0e8..82d2c46 100644 --- a/moodle/Tests/SquizOperatorsValidLogicalOperatorsTest.php +++ b/moodle/Tests/SquizOperatorsValidLogicalOperatorsTest.php @@ -28,7 +28,7 @@ * * @covers \PHP_CodeSniffer\Standards\Squiz\Sniffs\Operators\ValidLogicalOperatorsSniff */ -class SquizOperatorsValidLogicalOperatorsTest extends MoodleCSBaseTest { +class SquizOperatorsValidLogicalOperatorsTest extends MoodleCSBaseTestCase { /** * Test the Squid.Arrays.ValidLogicalOperators sniff diff --git a/moodle/Tests/WhiteSpaceSpaceAfterCommaTest.php b/moodle/Tests/WhiteSpaceSpaceAfterCommaTest.php index fad34ea..83fee62 100644 --- a/moodle/Tests/WhiteSpaceSpaceAfterCommaTest.php +++ b/moodle/Tests/WhiteSpaceSpaceAfterCommaTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\WhiteSpace\SpaceAfterCommaSniff */ -class WhiteSpaceSpaceAfterCommaTest extends MoodleCSBaseTest { +class WhiteSpaceSpaceAfterCommaTest extends MoodleCSBaseTestCase { public function test_whitespace_spaceaftercomma() { // Define the standard, sniff and fixture to use. diff --git a/moodle/Tests/WhiteSpaceWhiteSpaceInStringsTest.php b/moodle/Tests/WhiteSpaceWhiteSpaceInStringsTest.php index fcf1224..7c0fb69 100644 --- a/moodle/Tests/WhiteSpaceWhiteSpaceInStringsTest.php +++ b/moodle/Tests/WhiteSpaceWhiteSpaceInStringsTest.php @@ -28,7 +28,7 @@ * * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\WhiteSpace\WhiteSpaceInStringsSniff */ -class WhiteSpaceWhiteSpaceInStringsTest extends MoodleCSBaseTest { +class WhiteSpaceWhiteSpaceInStringsTest extends MoodleCSBaseTestCase { public function test_whitespace_whitespaceinstrings() { // Define the standard, sniff and fixture to use. From ef04fab44aec56895b5307c94a365caf00af4427 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 14 Sep 2023 23:19:17 +0800 Subject: [PATCH 05/10] Add a new moodle-extra coding style --- CHANGELOG.md | 4 ++ README.md | 95 ++++++++++++++++++---------------------- composer.json | 5 +++ moodle-extra/ruleset.xml | 82 ++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 moodle-extra/ruleset.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7669e46..5e9e644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt ## [Unreleased] +### Added + +- A new moodle-extra coding standard which moves towards a more PSR-12 compliant coding style + ## [v3.3.5] - 2023-08-28 ### Changed - Update composer dependencies to current versions, notably PHPCompatibility (0a17f9ed). diff --git a/README.md b/README.md index 0b17747..84ec567 100644 --- a/README.md +++ b/README.md @@ -16,81 +16,72 @@ ## Information -This repository contains the Moodle Coding Style configuration. +This repository contains the Moodle Coding Style configurations, written as [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) rulesets. -Currently this only includes the configuration for PHP Coding style, but this -may be extended to include custom rules for JavaScript, and any other supported -languages or syntaxes. +Two coding styles are included: +- `moodle` - the main ruleset for the [Moodle Coding Style](https://moodledev.io/general/development/policies/codingstyle) +- `moodle-extra` - extended ruleset which includes recommended best practices + - extends the main `moodle` ruleset + +Currently this only includes the configuration for PHP Coding style, but this may be extended to include custom rules for JavaScript, and any other supported languages or syntaxes. ## Installation -### Using Composer +### Using Composer (recommended) -You can include these coding style rules using Composer to make them available -globally across your system. +You can install these coding style rules using Composer to make them available globally across your system. -This will install the correct version of phpcs, with the Moodle rules, and their -dependencies. +This will install the correct version of phpcs, with the Moodle rules, and their dependencies. -``` +```shell composer global require moodlehq/moodle-cs ``` -### As a part of moodle-local_codechecker - -This plugin is included as part of the [moodle-local_codechecker -plugin](https://github.com/moodlehq/moodle-local_codechecker). - - ## Configuration -You can set the Moodle standard as the system default: -``` -phpcs --config-set default_standard moodle -``` - -This will inform most IDEs automatically. -Alternatively you can configuration your IDE to use phpcs with the Moodle -ruleset as required. - +Typically configuration is not required. Recent versions of Moodle (3.11 onwards) include a configuration file for the PHP CodeSniffer, which will set the standard when run within a Moodle directory. -### IDE Integration +Additional configuration can be generated automatically to have PHP CodeSniffer ignore any third-party library code. This can be generated by running: -#### PhpStorm - -1. Open PhpStorm preferences -2. Go to Inspections > PHP > PHP Code Sniffer Validation -3. In the 'coding standard' dropdown, select 'moodle' +```shell +npx grunt ignorefiles +``` -#### Sublime Text +### Using the `moodle-extra` coding style -Find documentation [here](https://docs.moodle.org/dev/Setting_up_Sublime2#Sublime_PHP_CS). +The recommended way of configuring PHP CodeSniffer to use the `moodle-extra` coding style is to provide an additional configuration file. -1. Go in your Sublime Text to Preferences -> Package Control -> Package Control: Install Package -2. Write 'phpcs' in the search field, if you see Phpcs and SublimeLinter-phpcs, click on them to install them. -3. If not, check if they are already installed Preferences -> Package Control -> Package Control: Remove Package. -4. To set your codecheck to moodle standards go to Preferences -> Package Settings -> PHP Code Sniffer -> Settings-User and write: +For Moodle 3.11 onwards you can create a file named `.phpcs.xml` with the following contents: - { "phpcs_additional_args": { - "--standard": "moodle", - "-n": " - }, - } +```xml + + + + + +``` -5. If you don’t have the auto-save plugin turned on, YOU’RE DONE! -6. If you have the auto-save plugin turned on, because the codecheck gets triggered on save, the quick panel will keep popping making it impossible to type. - To stop quick panel from showing go to Settings-User file and add: +This will load the `phpcs.xml` file (generated by `npx grunt ignorefiles`), and apply the `moodle-extra` configuration on top. - "phpcs_show_quick_panel": false, +### Moodle 3.10 and earlier - The line with the error will still get marked and if you’ll click on it you’ll see the error text in the status bar. +The easiset way to have PHP CodeSniffer pick up your preferred style, you can create a file named `phpcs.xml` with the following contents: -#### VSCode +```xml + + + + +``` -Find documentation [here](https://docs.moodle.org/dev/Setting_up_VSCode#PHP_CS). +If you wish to use the `moodle-extra` coding style, then you can use the following content: -1. Install [PHPSniffer](https://marketplace.visualstudio.com/items?itemName=wongjn.php-sniffer). -2. Open VSCode settings.json and add the following setting to define standard PHP CS (if you haven't set it as default in your system): +```xml + + + + +``` - "phpSniffer.standard": "moodle", +Note: Third-party library code will not be ignored with these versions of Moodle. diff --git a/composer.json b/composer.json index cfbd24a..3ec2e09 100644 --- a/composer.json +++ b/composer.json @@ -12,11 +12,16 @@ { "name": "Andrew Lyons", "email": "andrew@nicols.co.uk" + }, + { + "name": "Eloy Lafuente", + "email": "stronk7@moodle.com" } ], "require": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", "squizlabs/php_codesniffer": "^3.7.2", + "phpcsstandards/phpcsextra": "^1.1.0", "phpcompatibility/php-compatibility": "dev-develop#0a17f9ed" }, "config": { diff --git a/moodle-extra/ruleset.xml b/moodle-extra/ruleset.xml new file mode 100644 index 0000000..90efcfe --- /dev/null +++ b/moodle-extra/ruleset.xml @@ -0,0 +1,82 @@ + + + + Best Practices for Moodle development beyond the core Coding Standards + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3aef98b49160516cf9c588e810b1c1d673ad51ab Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 20 Dec 2022 06:20:42 +0800 Subject: [PATCH 06/10] Include Generic.Arrays.DisallowLongArraySyntax - as warning in the moodle standard. - as error in the moodle-extra standard. - to become error in both standards in 1y (#58) --- moodle-extra/ruleset.xml | 5 ++++ moodle/Tests/MoodleStandardTest.php | 27 +++++++++++++++++++ .../generic_array_longarraysyntax.php | 14 ++++++++++ .../generic_array_longarraysyntax.php.fixed | 14 ++++++++++ moodle/ruleset.xml | 4 +++ 5 files changed, 64 insertions(+) create mode 100644 moodle/Tests/fixtures/generic_array_longarraysyntax.php create mode 100644 moodle/Tests/fixtures/generic_array_longarraysyntax.php.fixed diff --git a/moodle-extra/ruleset.xml b/moodle-extra/ruleset.xml index 90efcfe..b392e02 100644 --- a/moodle-extra/ruleset.xml +++ b/moodle-extra/ruleset.xml @@ -9,6 +9,11 @@ + + + error + + diff --git a/moodle/Tests/MoodleStandardTest.php b/moodle/Tests/MoodleStandardTest.php index 4e3b873..074ec67 100644 --- a/moodle/Tests/MoodleStandardTest.php +++ b/moodle/Tests/MoodleStandardTest.php @@ -223,6 +223,33 @@ public function test_moodle_files_linelength() { $this->verify_cs_results(); } + /** + * Test the Generic.Arrays.DisallowLongArraySyntax sniff. + * + * @covers \PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\DisallowLongArraySyntaxSniff + */ + public function test_generic_array_disallowlongarraysyntax(): void { + // Define the standard, sniff and fixture to use. + $this->set_standard('moodle'); + $this->set_sniff('Generic.Arrays.DisallowLongArraySyntax'); + $this->set_fixture(__DIR__ . '/fixtures/generic_array_longarraysyntax.php'); + + // Define expected results (errors and warnings). Format, array of: + // - line => number of problems, or + // - line => array of contents for message / source problem matching. + // - line => string of contents for message / source problem matching (only 1). + $this->set_errors([ + 3 => 'Short array syntax must be used to define arrays @Source: Generic.Arrays.DisallowLongArraySyntax.Found', + 5 => 'Short array syntax must be used to define arrays @Source: Generic.Arrays.DisallowLongArraySyntax.Found', + 9 => 'Short array syntax must be used to define arrays @Source: Generic.Arrays.DisallowLongArraySyntax.Found', + ]); + + $this->set_warnings(array()); + + // Let's do all the hard work! + $this->verify_cs_results(); + } + /** * Test the Generic.Files.LineEndings sniff. * diff --git a/moodle/Tests/fixtures/generic_array_longarraysyntax.php b/moodle/Tests/fixtures/generic_array_longarraysyntax.php new file mode 100644 index 0000000..1c1693d --- /dev/null +++ b/moodle/Tests/fixtures/generic_array_longarraysyntax.php @@ -0,0 +1,14 @@ + + + warning + + From 9292e56a1d1f1f33dc2ef68780023c110bdfd6c2 Mon Sep 17 00:00:00 2001 From: "Eloy Lafuente (stronk7)" Date: Fri, 15 Sep 2023 13:15:51 +0200 Subject: [PATCH 07/10] Prepare the v3.3.6 release --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9e644..83a64c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ The format of this change log follows the advice given at [Keep a CHANGELOG](htt ## [Unreleased] +## [v3.3.6] - 2023-09-15 ### Added - -- A new moodle-extra coding standard which moves towards a more PSR-12 compliant coding style +- A new `moodle-extra` coding standard which moves towards a more PSR-12 compliant coding style. +- Enforce the use of the short array syntax (`[]`), warning about the long alternative (`array()`): `Generic.Arrays.DisallowLongArraySyntax`. This will be raised from `warning` to `error` in 1 year. ## [v3.3.5] - 2023-08-28 ### Changed @@ -60,7 +61,8 @@ All features are maintained and no new features have been introduced to either t All the details about [previous releases] can be found in [local_codechecker](https://github.com/moodlehq/moodle-local_codechecker) own change log. -[Unreleased]: https://github.com/moodlehq/moodle-cs/compare/v3.3.5...main +[Unreleased]: https://github.com/moodlehq/moodle-cs/compare/v3.3.6...main +[v3.3.6]: https://github.com/moodlehq/moodle-cs/compare/v3.3.5...v3.3.6 [v3.3.5]: https://github.com/moodlehq/moodle-cs/compare/v3.3.4...v3.3.5 [v3.3.4]: https://github.com/moodlehq/moodle-cs/compare/v3.3.3...v3.3.4 [v3.3.3]: https://github.com/moodlehq/moodle-cs/compare/v3.3.2...v3.3.3 From 5f76734c62d1f5210c11efafec289163cedc0c93 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 17 Sep 2023 13:49:30 +0800 Subject: [PATCH 08/10] Require trailing commas in multi-line Arrays Fixes #25 --- moodle-extra/ruleset.xml | 3 --- moodle/ruleset.xml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/moodle-extra/ruleset.xml b/moodle-extra/ruleset.xml index b392e02..c952d44 100644 --- a/moodle-extra/ruleset.xml +++ b/moodle-extra/ruleset.xml @@ -48,9 +48,6 @@ - - - diff --git a/moodle/ruleset.xml b/moodle/ruleset.xml index ac4628d..fbb1752 100644 --- a/moodle/ruleset.xml +++ b/moodle/ruleset.xml @@ -13,6 +13,16 @@ warning + + + @@ -90,6 +100,8 @@ + + 0 From e3dae34c2684c6553bf6edc6ae0583c02d418b1c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 17 Sep 2023 14:52:57 +0800 Subject: [PATCH 09/10] Check namespaces and class imports for leading backslash Fixes #20 --- .phplint.yml | 1 + .../Namespaces/NamespaceStatementSniff.php | 71 +++++++++++++++++ moodle/Tests/MoodleCSBaseTestCase.php | 55 +++----------- .../NamespaceStatementSniffTest.php | 76 +++++++++++++++++++ .../Namespaces/fixtures/correct_namespace.php | 8 ++ .../Namespaces/fixtures/curly_namespace.php | 5 ++ .../fixtures/curly_namespace.php.fixed | 5 ++ .../fixtures/curly_namespace_correct.php | 5 ++ .../Namespaces/fixtures/leading_backslash.php | 8 ++ .../fixtures/leading_backslash.php.fixed | 8 ++ moodle/ruleset.xml | 6 ++ 11 files changed, 203 insertions(+), 45 deletions(-) create mode 100644 moodle/Sniffs/Namespaces/NamespaceStatementSniff.php create mode 100644 moodle/Tests/Sniffs/Namespaces/NamespaceStatementSniffTest.php create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/correct_namespace.php create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/curly_namespace.php create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/curly_namespace.php.fixed create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/curly_namespace_correct.php create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/leading_backslash.php create mode 100644 moodle/Tests/Sniffs/Namespaces/fixtures/leading_backslash.php.fixed diff --git a/.phplint.yml b/.phplint.yml index 35dda97..fd69190 100644 --- a/.phplint.yml +++ b/.phplint.yml @@ -2,3 +2,4 @@ path: ./ exclude: - vendor - moodle/Tests/fixtures + - moodle/Tests/Sniffs/Namespaces/fixtures diff --git a/moodle/Sniffs/Namespaces/NamespaceStatementSniff.php b/moodle/Sniffs/Namespaces/NamespaceStatementSniff.php new file mode 100644 index 0000000..19923b3 --- /dev/null +++ b/moodle/Sniffs/Namespaces/NamespaceStatementSniff.php @@ -0,0 +1,71 @@ +. + +/** + * Checks that each file contains the standard GPL comment. + * + * @package moodle-cs + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace MoodleHQ\MoodleCS\moodle\Sniffs\Namespaces; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; + +// phpcs:disable moodle.NamingConventions + +class NamespaceStatementSniff implements Sniff { + public function register() + { + return [ + T_NAMESPACE, + ]; + } + + public function process(File $file, $stackPtr) + { + $tokens = $file->getTokens(); + // Format should be: + // - T_NAMESPACE + // - T_WHITESPACE + // - T_STRING + + $checkPtr = $stackPtr + 2; + $token = $tokens[$checkPtr]; + if ($token['code'] === T_NS_SEPARATOR) { + $fqdn = ''; + $stop = $file->findNext(Tokens::$emptyTokens, ($stackPtr + 2)); + for ($i = $stackPtr + 2; $i < $stop; $i++) { + $fqdn .= $tokens[$i]['content']; + } + $fix = $file->addFixableError( + 'Namespace should not start with a slash: %s', + $checkPtr, + 'LeadingSlash', + [$fqdn] + ); + + if ($fix) { + $file->fixer->beginChangeset(); + $file->fixer->replaceToken($checkPtr, ''); + $file->fixer->endChangeset(); + } + } + } +} diff --git a/moodle/Tests/MoodleCSBaseTestCase.php b/moodle/Tests/MoodleCSBaseTestCase.php index a2dfaaf..623e15c 100644 --- a/moodle/Tests/MoodleCSBaseTestCase.php +++ b/moodle/Tests/MoodleCSBaseTestCase.php @@ -80,53 +80,18 @@ public function set_component_mapping(array $mapping): void { * * @param string $standard name of the standard to be tested. */ - protected function set_standard($standard) { - $installedStandards = \PHP_CodeSniffer\Util\Standards::getInstalledStandardDetails(); - - foreach (array_keys($installedStandards) as $standard) { - if (\PHP_CodeSniffer\Util\Standards::isInstalledStandard($standard) === false) { - // They didn't select a valid coding standard, so help them - // out by letting them know which standards are installed. - $error = 'ERROR: the "'.$standard.'" coding standard is not installed. '; - ob_start(); - \PHP_CodeSniffer\Util\Standards::printInstalledStandards(); - $error .= ob_get_contents(); - ob_end_clean(); - throw new \PHP_CodeSniffer\Exceptions\DeepExitException($error, 3); - } + protected function set_standard(string$standard) { + if (\PHP_CodeSniffer\Util\Standards::isInstalledStandard($standard) === false) { + // They didn't select a valid coding standard, so help them + // out by letting them know which standards are installed. + $error = "ERROR: the '{$standard}' coding standard is not installed.\n"; + ob_start(); + \PHP_CodeSniffer\Util\Standards::printInstalledStandards(); + $error .= ob_get_contents(); + ob_end_clean(); + throw new \PHP_CodeSniffer\Exceptions\DeepExitException($error, 3); } $this->standard = $standard; - return; - // Since 2.9 arbitrary standard directories are not allowed by default, - // only those under the CodeSniffer/Standards dir are detected. Other base - // dirs containing standards can be added using CodeSniffer.conf or the - // PHP_CODESNIFFER_CONFIG_DATA global (installed_paths setting). - // We are using the global way here to avoid changes in the phpcs import. - // phpcs:disable - if (!isset($GLOBALS['PHP_CODESNIFFER_CONFIG_DATA']['installed_paths'])) { - $localcodecheckerpath = realpath(__DIR__ . '/../'); - $GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = ['installed_paths' => $localcodecheckerpath]; - } - // phpcs:enable - - // Basic search of standards in the allowed directories. - $stdsearch = array( - __DIR__ . '/../phpcs/src/Standards', // PHPCS standards dir. - __DIR__ . '/..', // Plugin local_codechecker dir, allowed above via global. - ); - - foreach ($stdsearch as $stdpath) { - $stdpath = realpath($stdpath . '/' . $standard); - $stdfile = $stdpath . '/ruleset.xml'; - if (file_exists($stdfile)) { - $this->standard = $stdpath; // Need to pass the path here. - break; - } - } - // Standard not found, fail. - if ($this->standard === null) { - $this->fail('Standard "' . $standard . '" not found.'); - } } /** diff --git a/moodle/Tests/Sniffs/Namespaces/NamespaceStatementSniffTest.php b/moodle/Tests/Sniffs/Namespaces/NamespaceStatementSniffTest.php new file mode 100644 index 0000000..d2a0d98 --- /dev/null +++ b/moodle/Tests/Sniffs/Namespaces/NamespaceStatementSniffTest.php @@ -0,0 +1,76 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Namespaces; + +use MoodleHQ\MoodleCS\moodle\Tests\MoodleCSBaseTestCase; + +// phpcs:disable moodle.NamingConventions + +/** + * Test the NoLeadingSlash sniff. + * + * @package moodle-cs + * @category test + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\Namespaces\NoLeadingSlashSniff + */ +class NamespaceStatementSniffTest extends MoodleCSBaseTestCase +{ + public static function leading_slash_provider(): array + { + return [ + [ + 'fixture' => 'correct_namespace', + 'warnings' => [], + 'errors' => [], + ], + [ + 'fixture' => 'leading_backslash', + 'warnings' => [], + 'errors' => [ + 3 => 'Namespace should not start with a slash: \MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Namespaces', + ], + ], + [ + 'fixture' => 'curly_namespace', + 'warnings' => [], + 'errors' => [ + 3 => 'Namespace should not start with a slash: \MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Namespaces', + ], + ], + ]; + } + /** + * @dataProvider leading_slash_provider + */ + public function test_leading_slash( + string $fixture, + array $warnings, + array $errors + ): void + { + $this->set_standard('moodle'); + $this->set_sniff('moodle.Namespaces.NamespaceStatement'); + $this->set_fixture(sprintf("%s/fixtures/%s.php", __DIR__, $fixture)); + $this->set_warnings($warnings); + $this->set_errors($errors); + + $this->verify_cs_results(); + } +} diff --git a/moodle/Tests/Sniffs/Namespaces/fixtures/correct_namespace.php b/moodle/Tests/Sniffs/Namespaces/fixtures/correct_namespace.php new file mode 100644 index 0000000..26c4931 --- /dev/null +++ b/moodle/Tests/Sniffs/Namespaces/fixtures/correct_namespace.php @@ -0,0 +1,8 @@ +0 + + + + From ae762af288065e6782734d14c24c3224bf8f9286 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 15 Sep 2023 23:21:13 +0800 Subject: [PATCH 10/10] Check validity of unit test dataProviders This change includes: - detection of private providers - detection of providers which do not exist - detection of incorrect casing of the dataProvider declaration - detection of providers which do not have a correct return type - detection of providers which are not static - detection of providers whose names start with test_ Fixes #42 --- moodle-extra/ruleset.xml | 15 + moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php | 31 +- moodle/Sniffs/PHPUnit/TestCaseNamesSniff.php | 12 +- .../Sniffs/PHPUnit/TestCaseProviderSniff.php | 366 ++++++++++++++++++ moodle/Tests/MoodleCSBaseTestCase.php | 2 +- moodle/Tests/MoodleUtilTest.php | 196 ++++++++++ moodle/Tests/PHPUnitTestCaseProviderTest.php | 152 ++++++++ .../moodleutil/test_with_methods_to_find.php | 23 ++ .../provider/complex_provider_test.php | 43 ++ .../provider/complex_provider_test.php.fixed | 43 ++ .../phpunit/provider/correct_test.php | 22 ++ .../phpunit/provider/provider_casing_test.php | 15 + .../provider/provider_casing_test.php.fixed | 15 + .../provider/provider_not_found_test.php | 18 + .../phpunit/provider/provider_prefix_test.php | 15 + .../provider/provider_returntype_test.php | 53 +++ .../provider/provider_visibility_test.php | 37 ++ .../provider_visibility_test.php.fixed | 37 ++ .../provider/static_providers_fix_test.php | 49 +++ .../static_providers_fix_test.php.fixed | 49 +++ .../provider/static_providers_test.php | 46 +++ .../provider/static_providers_test.php.fixed | 46 +++ moodle/Util/MoodleUtil.php | 97 +++++ moodle/ruleset.xml | 10 +- 24 files changed, 1359 insertions(+), 33 deletions(-) create mode 100644 moodle/Sniffs/PHPUnit/TestCaseProviderSniff.php create mode 100644 moodle/Tests/PHPUnitTestCaseProviderTest.php create mode 100644 moodle/Tests/fixtures/moodleutil/test_with_methods_to_find.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/complex_provider_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/complex_provider_test.php.fixed create mode 100644 moodle/Tests/fixtures/phpunit/provider/correct_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_casing_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_casing_test.php.fixed create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_not_found_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_prefix_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_returntype_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_visibility_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/provider_visibility_test.php.fixed create mode 100644 moodle/Tests/fixtures/phpunit/provider/static_providers_fix_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/static_providers_fix_test.php.fixed create mode 100644 moodle/Tests/fixtures/phpunit/provider/static_providers_test.php create mode 100644 moodle/Tests/fixtures/phpunit/provider/static_providers_test.php.fixed diff --git a/moodle-extra/ruleset.xml b/moodle-extra/ruleset.xml index c952d44..5a894bb 100644 --- a/moodle-extra/ruleset.xml +++ b/moodle-extra/ruleset.xml @@ -81,4 +81,19 @@ --> + + + + + + + diff --git a/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php b/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php index 78c7094..37b3367 100644 --- a/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php +++ b/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php @@ -49,11 +49,16 @@ public function process(File $file, $pointer) { // Before starting any check, let's look for various things. - // Get the moodle branch being analysed. - $moodleBranch = MoodleUtil::getMoodleBranch($file); + // If we aren't checking Moodle 4.0dev (400) and up, nothing to check. + // Make and exception for codechecker phpunit tests, so they are run always. + if (!MoodleUtil::meetsMinimumMoodleVersion($file, 400) && !MoodleUtil::isUnitTestRunning()) { + return; // @codeCoverageIgnore + } - // Detect if we are running PHPUnit. - $runningPHPUnit = defined('PHPUNIT_TEST') && PHPUNIT_TEST; + // If the file is not a unit test file, nothing to check. + if (!MoodleUtil::isUnitTest($file) && !MoodleUtil::isUnitTestRunning()) { + return; // @codeCoverageIgnore + } // We have all we need from core, let's start processing the file. @@ -70,24 +75,6 @@ public function process(File $file, $pointer) { return; // @codeCoverageIgnore } - // If we aren't checking Moodle 4.0dev (400) and up, nothing to check. - // Make and exception for codechecker phpunit tests, so they are run always. - if (isset($moodleBranch) && $moodleBranch < 400 && !$runningPHPUnit) { - return; // @codeCoverageIgnore - } - - // If the file isn't under tests directory, nothing to check. - if (stripos($file->getFilename(), '/tests/') === false) { - return; // @codeCoverageIgnore - } - - // If the file isn't called, _test.php, nothing to check. - // Make an exception for codechecker own phpunit fixtures here, allowing any name for them. - $fileName = basename($file->getFilename()); - if (substr($fileName, -9) !== '_test.php' && !$runningPHPUnit) { - return; // @codeCoverageIgnore - } - // Iterate over all the classes (hopefully only one, but that's not this sniff problem). $cStart = $pointer; while ($cStart = $file->findNext(T_CLASS, $cStart + 1)) { diff --git a/moodle/Sniffs/PHPUnit/TestCaseNamesSniff.php b/moodle/Sniffs/PHPUnit/TestCaseNamesSniff.php index c15da81..7b57de3 100644 --- a/moodle/Sniffs/PHPUnit/TestCaseNamesSniff.php +++ b/moodle/Sniffs/PHPUnit/TestCaseNamesSniff.php @@ -68,7 +68,7 @@ public function process(File $file, $pointer) { $moodleComponent = MoodleUtil::getMoodleComponent($file); // Detect if we are running PHPUnit. - $runningPHPUnit = defined('PHPUNIT_TEST') && PHPUNIT_TEST; + $runningPHPUnit = MoodleUtil::isUnitTestRunning(); // We have all we need from core, let's start processing the file. @@ -81,17 +81,11 @@ public function process(File $file, $pointer) { return; // @codeCoverageIgnore } - // If the file isn't under tests directory, nothing to check. - if (stripos($file->getFilename(), '/tests/') === false) { + // If the file is not a unit test file, nothing to check. + if (!MoodleUtil::isUnitTest($file) && !$runningPHPUnit) { return; // @codeCoverageIgnore } - - // If the file isn't called, _test.php, nothing to check. - // Make an exception for codechecker own phpunit fixtures here, allowing any name for them. $fileName = basename($file->getFilename()); - if (substr($fileName, -9) !== '_test.php' && !$runningPHPUnit) { - return; // @codeCoverageIgnore - } // In order to cover the duplicates detection, we need to set some // properties (caches) here. It's extremely hard to do diff --git a/moodle/Sniffs/PHPUnit/TestCaseProviderSniff.php b/moodle/Sniffs/PHPUnit/TestCaseProviderSniff.php new file mode 100644 index 0000000..b396072 --- /dev/null +++ b/moodle/Sniffs/PHPUnit/TestCaseProviderSniff.php @@ -0,0 +1,366 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit; + +// phpcs:disable moodle.NamingConventions + +use MoodleHQ\MoodleCS\moodle\Util\MoodleUtil; +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Utils\FunctionDeclarations; + +/** + * Checks that a test file has the @coversxxx annotations properly defined. + * + * @package local_codechecker + * @copyright 2022 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class TestCaseProviderSniff implements Sniff { + + /** + * Whether to autofix static providers. + * + * @var bool + */ + public $autofixStaticProviders = false; + + /** + * Register for open tag (only process once per file). + */ + public function register(): array + { + return [ + T_OPEN_TAG, + ]; + } + + /** + * Processes php files and perform various checks with file. + * + * @param File $file The file being scanned. + * @param int $pointer The position in the stack. + */ + public function process(File $file, $pointer): void + { + // Before starting any check, let's look for various things. + + // If we aren't checking Moodle 4.0dev (400) and up, nothing to check. + // Make and exception for codechecker phpunit tests, so they are run always. + if (!MoodleUtil::meetsMinimumMoodleVersion($file, 400) && !MoodleUtil::isUnitTestRunning()) { + return; // @codeCoverageIgnore + } + + // If the file is not a unit test file, nothing to check. + if (!MoodleUtil::isUnitTest($file) && !MoodleUtil::isUnitTestRunning()) { + return; // @codeCoverageIgnore + } + + // We have all we need from core, let's start processing the file. + + // Get the file tokens, for ease of use. + $tokens = $file->getTokens(); + + // In various places we are going to ignore class/method prefixes (private, abstract...) + // and whitespace, create an array for all them. + $skipTokens = Tokens::$methodPrefixes + [T_WHITESPACE => T_WHITESPACE]; + + // Iterate over all the classes (hopefully only one, but that's not this sniff problem). + $cStart = $pointer; + while ($cStart = $file->findNext(T_CLASS, $cStart + 1)) { + $class = $file->getDeclarationName($cStart); + + // Only if the class is extending something. + // TODO: We could add a list of valid classes once we have a class-map available. + if (!$file->findNext(T_EXTENDS, $cStart + 1, $tokens[$cStart]['scope_opener'])) { + continue; + } + + // Ignore any classname which does not end in "_test". + if (substr($class, -5) !== '_test') { + continue; + } + + // Iterate over all the methods in the class. + $mStart = $cStart; + while ($mStart = $file->findNext(T_FUNCTION, $mStart + 1, $tokens[$cStart]['scope_closer'])) { + $method = $file->getDeclarationName($mStart); + + // Ignore non test_xxxx() methods. + if (strpos($method, 'test_') !== 0) { + continue; + } + + // Let's see if the method has any phpdoc block (first non skip token must be end of phpdoc comment). + $docPointer = $file->findPrevious($skipTokens, $mStart - 1, null, true); + + // Found a phpdoc block, let's look for @dataProvider tag. + if ($tokens[$docPointer]['code'] === T_DOC_COMMENT_CLOSE_TAG) { + $docStart = $tokens[$docPointer]['comment_opener']; + while ($docPointer) { // Let's look upwards, until the beginning of the phpdoc block. + $docPointer = $file->findPrevious(T_DOC_COMMENT_TAG, $docPointer - 1, $docStart); + if ($docPointer) { + $docTag = trim($tokens[$docPointer]['content']); + $docTagLC = strtolower($docTag); + switch ($docTagLC) { + case '@dataprovider': + // Validate basic syntax (FQCN or ::). + $this->checkDataProvider($file, $docPointer); + break; + } + } + } + } + + // Advance until the end of the method, if possible, to find the next one quicker. + $mStart = $tokens[$mStart]['scope_closer'] ?? $pointer + 1; + } + } + } + + /** + * Perform a basic syntax cheking of the values of the @dataProvider tag. + * + * @param File $file The file being scanned + * @param int $pointer pointer to the token that contains the tag. Calculations are based on that. + * @return void + */ + protected function checkDataProvider( + File $file, + int $pointer + ) { + // Get the file tokens, for ease of use. + $tokens = $file->getTokens(); + $tag = $tokens[$pointer]['content']; + $methodName = $tokens[$pointer + 2]['content']; + $testPointer = $file->findNext(T_FUNCTION, $pointer + 2); + $testName = FunctionDeclarations::getName($file, $testPointer); + + if ($tag !== '@dataProvider') { + $fix = $file->addFixableError( + 'Wrong @dataProvider tag: %s provided, @dataProvider expected', + $pointer, + 'dataProviderNaming', + [$tag] + ); + + if ($fix) { + $file->fixer->beginChangeset(); + $file->fixer->replaceToken($pointer, '@dataProvider'); + $file->fixer->endChangeset(); + } + } + + if ($tokens[$pointer + 2]['code'] !== T_DOC_COMMENT_STRING) { + $file->addError( + 'Wrong @dataProvider tag specified for test %s, it must be followed by a space and a method name.', + $pointer + 2, + 'dataProviderSyntaxMethodnameMissing', + [ + $testName, + ] + ); + + // The remaining checks all relate to the method name, so we can't continue. + return; + } + + // Check that the method name is valid. + // It must _not_ start with `test_`. + if (substr($methodName, 0, 5) === 'test_') { + $file->addError( + 'Data provider must not start with "test_". "%s" provided.', + $pointer + 2, + 'dataProviderSyntaxMethodnameInvalid', + [ + $methodName, + ] + ); + } + + // Find the method itself. + $classPointer = $file->findPrevious(T_CLASS, $pointer - 1); + $providerPointer = MoodleUtil::findClassMethodPointer($file, $classPointer, $methodName); + if ($providerPointer === null) { + $file->addError( + 'Data provider method "%s" not found.', + $pointer + 2, + 'dataProviderSyntaxMethodNotFound', + [ + $methodName, + ] + ); + + return; + } + + // https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html#data-providers + // A data provider method must be public and either return an array of arrays + // or an object that implements the Iterator interface and yields an array for + // each iteration step. For each array that is part of the collection the test + // method will be called with the contents of the array as its arguments. + + // Check that the method is public. + $methodProps = $file->getMethodProperties($providerPointer); + if (!$methodProps['scope_specified']) { + $fix = $file->addFixableError( + 'Data provider method "%s" visibility should be specified.', + $providerPointer, + 'dataProviderSyntaxMethodVisibilityNotSpecified', + [ + $methodName, + ] + ); + + if ($fix) { + $file->fixer->beginChangeset(); + if ($methodProps['is_static']) { + $staticPointer = $file->findPrevious(T_STATIC, $providerPointer - 1); + $file->fixer->addContentBefore($staticPointer, 'public '); + } else { + $file->fixer->addContentBefore($providerPointer, 'public '); + } + $file->fixer->endChangeset(); + } + } else if ($methodProps['scope'] !== 'public') { + $scopePointer = $file->findPrevious(Tokens::$scopeModifiers, $providerPointer - 1); + $fix = $file->addFixableError( + 'Data provider method "%s" must be public.', + $scopePointer, + 'dataProviderSyntaxMethodNotPublic', + [ + $methodName, + ] + ); + + if ($fix) { + $file->fixer->beginChangeset(); + $file->fixer->replaceToken($scopePointer, 'public'); + $file->fixer->endChangeset(); + } + } + + // Check the return type. + switch ($methodProps['return_type']) { + case 'array': + case 'Generator': + case 'Iterable': + // All valid + break; + default: + $file->addError( + 'Data provider method "%s" must return an array, a Generator or an Iterable.', + $pointer + 2, + 'dataProviderSyntaxMethodInvalidReturnType', + [ + $methodName, + ] + ); + } + + // In preparation for PHPUnit 10, we want to recommend that data providers are statically defined. + if (!$methodProps['is_static']) { + $supportAutomatedFix = true; + if (!$this->autofixStaticProviders) { + $supportAutomatedFix = false; + } else { + // We can make this fixable if the method does not contain any `$this`. + // Search the body. + $currentPointer = $tokens[$providerPointer]['scope_opener'] + 1; + $bodyEnd = $tokens[$providerPointer]['scope_closer'] - 1; + while ($token = $file->findNext(T_VARIABLE, $currentPointer, $bodyEnd)) { + if ($tokens[$token]['content'] === '$this') { + $supportAutomatedFix = false; + break; + } + $currentPointer = $token + 1; + } + } + + if (!$supportAutomatedFix) { + $file->addWarning( + 'Data provider method "%s" will need to be converted to static in future.', + $pointer + 2, + 'dataProviderNotStatic', + [ + $methodName, + ] + ); + } else { + $fix = $file->addFixableWarning( + 'Data provider method "%s" will need to be converted to static in future.', + $pointer + 2, + 'dataProviderNotStatic', + [ + $methodName, + ] + ); + + if ($fix) { + $file->fixer->beginChangeset(); + $file->fixer->addContentBefore($providerPointer, "static "); + $uses = self::findMethodCalls($file, $classPointer, $methodName); + foreach ($uses as $use) { + $file->fixer->replaceToken($use['start'], 'self::' . $methodName); + $file->fixer->replaceToken($use['start'] + 1, ''); + $file->fixer->replaceToken($use['start'] + 2, ''); + } + $file->fixer->endChangeset(); + } + } + } + } + + + /** + * Find all calls to a method. + * @param File $phpcsFile + * @param int $classPtr + * @param string $methodName + * @return array + */ + protected static function findMethodCalls( + File $phpcsFile, + int $classPtr, + string $methodName + ): array + { + $data = []; + + $mStart = $classPtr; + $tokens = $phpcsFile->getTokens(); + while ($mStart = $phpcsFile->findNext(T_VARIABLE, $mStart + 1, $tokens[$classPtr]['scope_closer'])) { + if ($tokens[$mStart]['content'] !== '$this') { + continue; + } + if ($tokens[$mStart + 1]['code'] !== T_OBJECT_OPERATOR) { + continue; + } + if ($tokens[$mStart + 2]['content'] !== $methodName) { + continue; + } + + $data[] = [ + 'start' => $mStart, + 'end' => $mStart + 2, + ]; + } + + return $data; + } +} diff --git a/moodle/Tests/MoodleCSBaseTestCase.php b/moodle/Tests/MoodleCSBaseTestCase.php index 623e15c..f052a56 100644 --- a/moodle/Tests/MoodleCSBaseTestCase.php +++ b/moodle/Tests/MoodleCSBaseTestCase.php @@ -80,7 +80,7 @@ public function set_component_mapping(array $mapping): void { * * @param string $standard name of the standard to be tested. */ - protected function set_standard(string$standard) { + protected function set_standard(string $standard) { if (\PHP_CodeSniffer\Util\Standards::isInstalledStandard($standard) === false) { // They didn't select a valid coding standard, so help them // out by letting them know which standards are installed. diff --git a/moodle/Tests/MoodleUtilTest.php b/moodle/Tests/MoodleUtilTest.php index c49c5cf..651d587 100644 --- a/moodle/Tests/MoodleUtilTest.php +++ b/moodle/Tests/MoodleUtilTest.php @@ -466,4 +466,200 @@ protected function cleanMoodleUtilCaches() { $moodleComponents->setAccessible(true); $moodleComponents->setValue([]); } + + /** + * Data provider for testIsUnitTest. + * + * @return array + */ + public static function isUnitTestProvider(): array + { + return [ + 'Not in tests directory' => [ + 'value' => '/path/to/standard/file.php', + 'return' => false, + ], + 'In tests directory' => [ + 'value' => '/path/to/standard/tests/file.php', + 'return' => true, + ], + 'In test sub-directory' => [ + 'value' => '/path/to/standard/tests/sub/file.php', + 'return' => true, + ], + 'Generator' => [ + 'value' => '/path/to/standard/tests/generator/file.php', + 'return' => false, + ], + 'Fixture' => [ + 'value' => '/path/to/standard/tests/fixtures/file.php', + 'return' => false, + ], + 'Behat' => [ + 'value' => '/path/to/standard/tests/behat/behat_test_file.php', + 'return' => false, + ], + ]; + } + + /** + * @dataProvider isUnitTestProvider + */ + public function testIsUnitTest( + string $filepath, + bool $expected + ): void + { + $phpcsConfig = new Config(); + $phpcsRuleset = new Ruleset($phpcsConfig); + $file = new File($filepath, $phpcsRuleset, $phpcsConfig); + + $this->assertEquals($expected, MoodleUtil::isUnitTest($file)); + } + + /** + * Data provider for testMeetsMinimumMoodleVersion. + * + * @return array + */ + public static function meetsMinimumMoodleVersionProvider(): array + { + return [ + // Setting up moodleBranch config/runtime option. + 'moodleBranch_not_integer' => [ + 'moodleVersion' => 'noint', + 'minVersion' => 311, + 'return' => ['exception' => DeepExitException::class, 'message' => 'Value in not an integer'], + ], + 'moodleBranch_big' => [ + 'moodleVersion' => '10000', + 'minVersion' => 311, + 'return' => ['exception' => DeepExitException::class, 'message' => 'Value must be 4 digit max'], + ], + 'moodleBranch_valid_meets_minimum' => [ + 'moodleVersion' => 999, + 'minVersion' => 311, + 'return' => ['value' => true], + ], + 'moodleBranch_valid_equals_minimum' => [ + 'moodleVersion' => 311, + 'minVersion' => 311, + 'return' => ['value' => true], + ], + 'moodleBranch_valid_does_not_meet_minimum' => [ + 'moodleVersion' => 311, + 'minVersion' => 402, + 'return' => ['value' => false], + ], + 'moodleBranch_valid_but_empty' => [ + 'moodleVersion' => 0, + 'minVersion' => 311, + 'return' => ['value' => null], + ], + ]; + } + + /** + * @dataProvider meetsMinimumMoodleVersionProvider + * @param string|int $moodleVersion + * @param int $minversion + * @param array $return + */ + public function testMeetsMinimumMoodleVersion( + $moodleVersion, + int $minVersion, + array $return + ): void + { + Config::setConfigData('moodleBranch', $moodleVersion, true); + + $phpcsConfig = new Config(); + $phpcsRuleset = new Ruleset($phpcsConfig); + $file = new File('/path/to/tests/file.php', $phpcsRuleset, $phpcsConfig); + + // Exception is coming, let's verify it happens. + if (isset($return['exception'])) { + try { + MoodleUtil::getMoodleBranch($file); + } catch (\Exception $e) { + $this->assertInstanceOf($return['exception'], $e); + $this->assertStringContainsString($return['message'], $e->getMessage()); + } + } else if (array_key_exists('value', $return)) { + // Normal asserting result. + $this->assertSame($return['value'], MoodleUtil::meetsMinimumMoodleVersion($file, $minVersion)); + } + + // Do we want to reset any information cached (by default we do). + $this->cleanMoodleUtilCaches(); + + // We need to unset all config options when passed. + Config::setConfigData('moodleBranch', null, true); + } + + public static function findClassMethodPointerProvider(): array + { + return [ + [ + 'instance_method', + true, + ], + [ + 'protected_method', + true, + ], + [ + 'private_method', + true, + ], + [ + 'static_method', + true, + ], + [ + 'protected_static_method', + true, + ], + [ + 'private_static_method', + true, + ], + [ + 'not_found_method', + false, + ], + ]; + } + + /** + * @dataProvider findClassMethodPointerProvider + */ + public function testFindClassMethodPointer( + string $methodName, + bool $found + ): void + { + $phpcsConfig = new Config(); + $phpcsRuleset = new Ruleset($phpcsConfig); + $phpcsFile = new \PHP_CodeSniffer\Files\LocalFile( + __DIR__ . '/fixtures/moodleutil/test_with_methods_to_find.php', + $phpcsRuleset, + $phpcsConfig + ); + + $phpcsFile->process(); + $classPointer = $phpcsFile->findNext(T_CLASS, 0); + + $pointer = MoodleUtil::findClassMethodPointer( + $phpcsFile, + $classPointer, + $methodName + ); + + if ($found) { + $this->assertGreaterThan(0, $pointer); + } else { + $this->assertNull($pointer); + } + } } diff --git a/moodle/Tests/PHPUnitTestCaseProviderTest.php b/moodle/Tests/PHPUnitTestCaseProviderTest.php new file mode 100644 index 0000000..12e5e67 --- /dev/null +++ b/moodle/Tests/PHPUnitTestCaseProviderTest.php @@ -0,0 +1,152 @@ +. + +namespace MoodleHQ\MoodleCS\moodle\Tests; + +use MoodleHQ\MoodleCS\moodle\Util\MoodleUtil; + +// phpcs:disable moodle.NamingConventions + +/** + * Test the TestCaseCoversSniff sniff. + * + * @package local_codechecker + * @category test + * @copyright 2022 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \MoodleHQ\MoodleCS\moodle\Sniffs\PHPUnit\TestCaseProviderSniff + */ +class PHPUnitTestCaseProviderTest extends MoodleCSBaseTestCase { + + /** + * Data provider for self::test_phpunit_test_providers + */ + public function provider_phpunit_data_providers() { + return [ + 'Correct' => [ + 'fixture' => 'fixtures/phpunit/provider/correct_test.php', + 'errors' => [], + 'warnings' => [], + ], + 'Provider Casing' => [ + 'fixture' => 'fixtures/phpunit/provider/provider_casing_test.php', + 'errors' => [ + 6 => 'Wrong @dataProvider tag: @dataprovider provided, @dataProvider expected', + ], + 'warnings' => [ + ], + ], + 'Provider Visibility' => [ + 'fixture' => 'fixtures/phpunit/provider/provider_visibility_test.php', + 'errors' => [ + 12 => 'Data provider method "provider" must be public.', + 23 => 'Data provider method "provider_without_visibility" visibility should be specified.', + 34 => 'Data provider method "static_provider_without_visibility" visibility should be specified.', + ], + 'warnings' => [ + 17 => 'Data provider method "provider_without_visibility" will need to be converted to static in future.', + ], + ], + 'Provider Naming conflicts with test names' => [ + 'fixture' => 'fixtures/phpunit/provider/provider_prefix_test.php', + 'errors' => [ + 6 => 'Data provider must not start with "test_". "test_provider" provided.', + ], + 'warnings' => [ + ], + ], + 'Static Providers' => [ + 'fixture' => 'fixtures/phpunit/provider/static_providers_test.php', + 'errors' => [ + ], + 'warnings' => [ + 12 => 'Data provider method "fixable_provider" will need to be converted to static in future.', + 23 => 'Data provider method "unfixable_provider" will need to be converted to static in future.', + 34 => 'Data provider method "partially_fixable_provider" will need to be converted to static in future.', + ], + ], + 'Static Providers Applying fixes' => [ + 'fixture' => 'fixtures/phpunit/provider/static_providers_fix_test.php', + 'errors' => [ + ], + 'warnings' => [ + 13 => 'Data provider method "fixable_provider" will need to be converted to static in future.', + 24 => 'Data provider method "unfixable_provider" will need to be converted to static in future.', + 35 => 'Data provider method "partially_fixable_provider" will need to be converted to static in future.', + ], + ], + 'Provider Return Type checks' => [ + 'fixture' => 'fixtures/phpunit/provider/provider_returntype_test.php', + 'errors' => [ + 6 => 'Data provider method "provider_no_return" must return an array, a Generator or an Iterable.', + 17 => 'Data provider method "provider_wrong_return" must return an array, a Generator or an Iterable.', + 28 => 'Data provider method "provider_returns_generator" must return an array, a Generator or an Iterable.', + 41 => 'Data provider method "provider_returns_iterator" must return an array, a Generator or an Iterable.', + ], + 'warnings' => [ + ], + ], + 'Provider not found' => [ + 'fixture' => 'fixtures/phpunit/provider/provider_not_found_test.php', + 'errors' => [ + 6 => 'Data provider method "provider" not found.', + 14 => 'Wrong @dataProvider tag specified for test test_two, it must be followed by a space and a method name.', + ], + 'warnings' => [ + ], + ], + 'Complex test with multiple classes' => [ + 'fixture' => 'fixtures/phpunit/provider/complex_provider_test.php', + 'errors' => [ + 7 => 'Data provider method "provider" not found.', + ], + 'warnings' => [ + 14 => 'Data provider method "second_provider" will need to be converted to static in future.', + ], + ], + ]; + } + + /** + * Test the moodle.PHPUnit.TestCaseCovers sniff + * + * @param string $fixture relative path to fixture to use. + * @param array $errors array of errors expected. + * @param array $warnings array of warnings expected. + * @dataProvider provider_phpunit_data_providers + */ + public function test_phpunit_test_providers( + string $fixture, + array $errors, + array $warnings + ): void { + // Define the standard, sniff and fixture to use. + $this->set_standard('moodle'); + $this->set_sniff('moodle.PHPUnit.TestCaseProvider'); + $this->set_fixture(__DIR__ . '/' . $fixture); + + // Define expected results (errors and warnings). Format, array of: + // - line => number of problems, or + // - line => array of contents for message / source problem matching. + // - line => string of contents for message / source problem matching (only 1). + $this->set_errors($errors); + $this->set_warnings($warnings); + + // Let's do all the hard work! + $this->verify_cs_results(); + } +} diff --git a/moodle/Tests/fixtures/moodleutil/test_with_methods_to_find.php b/moodle/Tests/fixtures/moodleutil/test_with_methods_to_find.php new file mode 100644 index 0000000..401aa91 --- /dev/null +++ b/moodle/Tests/fixtures/moodleutil/test_with_methods_to_find.php @@ -0,0 +1,23 @@ +second_provider(); + } +} diff --git a/moodle/Tests/fixtures/phpunit/provider/complex_provider_test.php.fixed b/moodle/Tests/fixtures/phpunit/provider/complex_provider_test.php.fixed new file mode 100644 index 0000000..890f08d --- /dev/null +++ b/moodle/Tests/fixtures/phpunit/provider/complex_provider_test.php.fixed @@ -0,0 +1,43 @@ +// phpcs:set moodle.PHPUnit.TestCaseProvider autofixStaticProviders true +second_provider(); + } +} diff --git a/moodle/Tests/fixtures/phpunit/provider/correct_test.php b/moodle/Tests/fixtures/phpunit/provider/correct_test.php new file mode 100644 index 0000000..99de326 --- /dev/null +++ b/moodle/Tests/fixtures/phpunit/provider/correct_test.php @@ -0,0 +1,22 @@ +provider(); + } + + /** + * @dataProvider partially_fixable_provider + */ + public function test_partially_fixable(): void { + // Nothing to test. + } + + public function partially_fixable_provider(): array { + $foo = $this->call_something(); + $foo->bar(); + $foo(); + $this(); + $this; + return $this->fixable_provider(); + } +} diff --git a/moodle/Tests/fixtures/phpunit/provider/static_providers_fix_test.php.fixed b/moodle/Tests/fixtures/phpunit/provider/static_providers_fix_test.php.fixed new file mode 100644 index 0000000..d8cfbf1 --- /dev/null +++ b/moodle/Tests/fixtures/phpunit/provider/static_providers_fix_test.php.fixed @@ -0,0 +1,49 @@ +// phpcs:set moodle.PHPUnit.TestCaseProvider autofixStaticProviders true +provider(); + } + + /** + * @dataProvider partially_fixable_provider + */ + public function test_partially_fixable(): void { + // Nothing to test. + } + + public function partially_fixable_provider(): array { + $foo = $this->call_something(); + $foo->bar(); + $foo(); + $this(); + $this; + return self::fixable_provider(); + } +} diff --git a/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php b/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php new file mode 100644 index 0000000..f9ba938 --- /dev/null +++ b/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php @@ -0,0 +1,46 @@ +provider(); + } + + /** + * @dataProvider partially_fixable_provider + */ + public function test_partially_fixable(): void { + // Nothing to test. + } + + public function partially_fixable_provider(): array { + $foo = $this->call_something(); + $foo->bar(); + $foo(); + return $this->fixable_provider(); + } +} diff --git a/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php.fixed b/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php.fixed new file mode 100644 index 0000000..f9ba938 --- /dev/null +++ b/moodle/Tests/fixtures/phpunit/provider/static_providers_test.php.fixed @@ -0,0 +1,46 @@ +provider(); + } + + /** + * @dataProvider partially_fixable_provider + */ + public function test_partially_fixable(): void { + // Nothing to test. + } + + public function partially_fixable_provider(): array { + $foo = $this->call_something(); + $foo->bar(); + $foo(); + return $this->fixable_provider(); + } +} diff --git a/moodle/Util/MoodleUtil.php b/moodle/Util/MoodleUtil.php index 2f10b92..8befc45 100644 --- a/moodle/Util/MoodleUtil.php +++ b/moodle/Util/MoodleUtil.php @@ -18,6 +18,7 @@ use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Exceptions\DeepExitException; +use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\DummyFile; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Ruleset; @@ -370,4 +371,100 @@ public static function getMoodleRoot(File $file = null, bool $selfPath = true) { self::$moodleRoot = null; return self::$moodleRoot; } + + /** + * Whether this file is a unit test file. + * + * This does not include test fixtures, generators, or behat files. + * + * Any file which is not correctly named will be ignored. + * + * @param File $phpcsFile + * @return bool + */ + public static function isUnitTest(File $phpcsFile): bool + { + // If the file isn't under tests directory, nothing to check. + if (stripos($phpcsFile->getFilename(), '/tests/') === false) { + return false; + } + + // If the file is in a fixture directory, ignore it. + if (stripos($phpcsFile->getFilename(), '/tests/fixtures/') !== false) { + return false; + } + + // If the file is in a generator directory, ignore it. + if (stripos($phpcsFile->getFilename(), '/tests/generator/') !== false) { + return false; + } + + // If the file is in a behat directory, ignore it. + if (stripos($phpcsFile->getFilename(), '/tests/behat/') !== false) { + return false; + } + + return true; + } + + /** + * Whether we are running PHPUnit. + * + * @return bool + * @codeCoverageIgnore + */ + public static function isUnitTestRunning(): bool + { + // Detect if we are running PHPUnit. + return defined('PHPUNIT_TEST') && PHPUNIT_TEST; + } + + /** + * Whether the file belongs to a version of Moodle meeting the specifeid minimum version. + * + * If a version could not be determined, null is returned. + * + * @param File $phpcsFile The file to check + * @param int The minimum version to check against as a 2, or 3 digit number. + * @return null|bool + */ + public static function meetsMinimumMoodleVersion( + File $phpcsFile, + int $version + ): ?bool + { + $moodleBranch = self::getMoodleBranch($phpcsFile); + if (!isset($moodleBranch)) { + // We cannot determine the moodle branch, so we cannot determine if the version is met. + return null; + } + + return ($moodleBranch >= $version); + } + + /** + * Find the pointer to a method in a class. + * + * @param File $phpcsFile + * @param int $classPtr + * @param string $methodName + * @return null|int + */ + public static function findClassMethodPointer( + File $phpcsFile, + int $classPtr, + string $methodName + ): ?int + { + $mStart = $classPtr; + $tokens = $phpcsFile->getTokens(); + while ($mStart = $phpcsFile->findNext(T_FUNCTION, $mStart + 1, $tokens[$classPtr]['scope_closer'])) { + $method = $phpcsFile->getDeclarationName($mStart); + if ($method === $methodName) { + return $mStart; + } + } + + return null; + } } diff --git a/moodle/ruleset.xml b/moodle/ruleset.xml index 46cc140..983f893 100644 --- a/moodle/ruleset.xml +++ b/moodle/ruleset.xml @@ -100,7 +100,15 @@ - + +