diff --git a/composer.json b/composer.json index de42e103..a50c8a44 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "GPL-3.0-or-later", "require": { "php": ">=7.4", - "moodlehq/moodle-cs": "^v3.4.7", + "moodlehq/moodle-cs": "^v3.4.8", "phpcompatibility/php-compatibility": "dev-develop#96072c30" }, "config": { diff --git a/composer.lock b/composer.lock index d97745b9..166057fc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "557c6dacc8e1efaa1f0ff81060ceddb6", + "content-hash": "bd0a96a20590db32325fc04f2993298a", "packages": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -86,16 +86,16 @@ }, { "name": "moodlehq/moodle-cs", - "version": "v3.4.7", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/moodlehq/moodle-cs.git", - "reference": "4f1bc63551da69675d9f5d17efbf35a458ca3da8" + "reference": "91661a17a23ed17e7ae4276f8c19df789b8882c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moodlehq/moodle-cs/zipball/4f1bc63551da69675d9f5d17efbf35a458ca3da8", - "reference": "4f1bc63551da69675d9f5d17efbf35a458ca3da8", + "url": "https://api.github.com/repos/moodlehq/moodle-cs/zipball/91661a17a23ed17e7ae4276f8c19df789b8882c2", + "reference": "91661a17a23ed17e7ae4276f8c19df789b8882c2", "shasum": "" }, "require": { @@ -152,7 +152,7 @@ "source": "https://github.com/moodlehq/moodle-cs", "wiki": "https://github.com/moodlehq/moodle-cs/wiki" }, - "time": "2024-05-31T16:28:39+00:00" + "time": "2024-06-14T14:47:25+00:00" }, { "name": "phpcompatibility/php-compatibility", @@ -170,8 +170,8 @@ }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.0.9", - "squizlabs/php_codesniffer": "^3.9.0" + "phpcsstandards/phpcsutils": "^1.0.12", + "squizlabs/php_codesniffer": "^3.10.0" }, "replace": { "wimg/php-compatibility": "*" @@ -242,7 +242,7 @@ "type": "open_collective" } ], - "time": "2024-04-30T23:24:59+00:00" + "time": "2024-06-07T09:46:11+00:00" }, { "name": "phpcsstandards/phpcsextra", diff --git a/vendor/autoload.php b/vendor/autoload.php index c772c200..39f3cf97 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -22,4 +22,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit557c6dacc8e1efaa1f0ff81060ceddb6::getLoader(); +return ComposerAutoloaderInitbd0a96a20590db32325fc04f2993298a::getLoader(); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 5a444cb8..d43dec46 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit557c6dacc8e1efaa1f0ff81060ceddb6 +class ComposerAutoloaderInitbd0a96a20590db32325fc04f2993298a { private static $loader; @@ -24,12 +24,12 @@ public static function getLoader() require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit557c6dacc8e1efaa1f0ff81060ceddb6', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitbd0a96a20590db32325fc04f2993298a', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); - spl_autoload_unregister(array('ComposerAutoloaderInit557c6dacc8e1efaa1f0ff81060ceddb6', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitbd0a96a20590db32325fc04f2993298a', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitbd0a96a20590db32325fc04f2993298a::getInitializer($loader)); $loader->register(true); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 355deead..7e9c208f 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6 +class ComposerStaticInitbd0a96a20590db32325fc04f2993298a { public static $prefixLengthsPsr4 = array ( 'P' => @@ -72,9 +72,9 @@ class ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit557c6dacc8e1efaa1f0ff81060ceddb6::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitbd0a96a20590db32325fc04f2993298a::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitbd0a96a20590db32325fc04f2993298a::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitbd0a96a20590db32325fc04f2993298a::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 2a74772b..23987323 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -83,17 +83,17 @@ }, { "name": "moodlehq/moodle-cs", - "version": "v3.4.7", - "version_normalized": "3.4.7.0", + "version": "v3.4.8", + "version_normalized": "3.4.8.0", "source": { "type": "git", "url": "https://github.com/moodlehq/moodle-cs.git", - "reference": "4f1bc63551da69675d9f5d17efbf35a458ca3da8" + "reference": "91661a17a23ed17e7ae4276f8c19df789b8882c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moodlehq/moodle-cs/zipball/4f1bc63551da69675d9f5d17efbf35a458ca3da8", - "reference": "4f1bc63551da69675d9f5d17efbf35a458ca3da8", + "url": "https://api.github.com/repos/moodlehq/moodle-cs/zipball/91661a17a23ed17e7ae4276f8c19df789b8882c2", + "reference": "91661a17a23ed17e7ae4276f8c19df789b8882c2", "shasum": "" }, "require": { @@ -116,7 +116,7 @@ "sebastian/phpcpd": "^6.0", "thor-juhasz/phpunit-coverage-check": "^0.3.0" }, - "time": "2024-05-31T16:28:39+00:00", + "time": "2024-06-14T14:47:25+00:00", "type": "phpcodesniffer-standard", "installation-source": "dist", "autoload": { @@ -171,8 +171,8 @@ }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsutils": "^1.0.9", - "squizlabs/php_codesniffer": "^3.9.0" + "phpcsstandards/phpcsutils": "^1.0.12", + "squizlabs/php_codesniffer": "^3.10.0" }, "replace": { "wimg/php-compatibility": "*" @@ -188,7 +188,7 @@ "suggest": { "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, - "time": "2024-04-30T23:24:59+00:00", + "time": "2024-06-07T09:46:11+00:00", "default-branch": true, "type": "phpcodesniffer-standard", "extra": { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index fd29dbe8..bc24b234 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'moodlehq/local_codechecker', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '6317d1361ee40d2ebcdc40c36b40cfa9fb3f98a9', + 'reference' => '3ec6941b73874fb0bc33ded196e8a5c6fe80be6f', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,16 +22,16 @@ 'moodlehq/local_codechecker' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '6317d1361ee40d2ebcdc40c36b40cfa9fb3f98a9', + 'reference' => '3ec6941b73874fb0bc33ded196e8a5c6fe80be6f', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'moodlehq/moodle-cs' => array( - 'pretty_version' => 'v3.4.7', - 'version' => '3.4.7.0', - 'reference' => '4f1bc63551da69675d9f5d17efbf35a458ca3da8', + 'pretty_version' => 'v3.4.8', + 'version' => '3.4.8.0', + 'reference' => '91661a17a23ed17e7ae4276f8c19df789b8882c2', 'type' => 'phpcodesniffer-standard', 'install_path' => __DIR__ . '/../moodlehq/moodle-cs', 'aliases' => array(), diff --git a/vendor/moodlehq/moodle-cs/moodle/Sniffs/Commenting/MissingDocblockSniff.php b/vendor/moodlehq/moodle-cs/moodle/Sniffs/Commenting/MissingDocblockSniff.php index 8999a59a..82bc55f6 100644 --- a/vendor/moodlehq/moodle-cs/moodle/Sniffs/Commenting/MissingDocblockSniff.php +++ b/vendor/moodlehq/moodle-cs/moodle/Sniffs/Commenting/MissingDocblockSniff.php @@ -101,7 +101,7 @@ protected function processScopes(File $phpcsFile, int $stackPtr): void { if ($fileblock === null) { $objectName = TokenUtil::getObjectName($phpcsFile, $stackPtr); - $phpcsFile->addError('Missing docblock for file %s', $stackPtr, 'Missing', [$objectName]); + $phpcsFile->addError('Missing docblock for file %s', $stackPtr, 'File', [$objectName]); } } @@ -110,7 +110,7 @@ protected function processScopes(File $phpcsFile, int $stackPtr): void { $objectName = TokenUtil::getObjectName($phpcsFile, $typePtr); $objectType = TokenUtil::getObjectType($phpcsFile, $typePtr); - $phpcsFile->addError('Missing docblock for %s %s', $typePtr, 'Missing', [$objectType, $objectName]); + $phpcsFile->addError('Missing docblock for %s %s', $typePtr, ucfirst($objectType), [$objectType, $objectName]); } if ($artifactCount === 1) { @@ -208,7 +208,7 @@ protected function processFunctions(File $phpcsFile, int $stackPtr): void { ); } } else { - $phpcsFile->addError('Missing docblock for %s %s', $typePtr, 'Missing', [$objectType, $objectName]); + $phpcsFile->addError('Missing docblock for %s %s', $typePtr, ucfirst($objectType), [$objectType, $objectName]); } } } @@ -255,14 +255,14 @@ protected function processConstants(File $phpcsFile, int $stackPtr): void { $phpcsFile->addError( 'Missing docblock for constant %s::%s', $typePtr, - 'Missing', + 'Constant', [$containerName, $objectName] ); } else { $phpcsFile->addError( 'Missing docblock for constant %s', $typePtr, - 'Missing', + 'Constant', [$objectName] ); } diff --git a/vendor/moodlehq/moodle-cs/moodle/Sniffs/Files/BoilerplateCommentSniff.php b/vendor/moodlehq/moodle-cs/moodle/Sniffs/Files/BoilerplateCommentSniff.php index 57efe20b..eb5f9870 100644 --- a/vendor/moodlehq/moodle-cs/moodle/Sniffs/Files/BoilerplateCommentSniff.php +++ b/vendor/moodlehq/moodle-cs/moodle/Sniffs/Files/BoilerplateCommentSniff.php @@ -30,7 +30,7 @@ class BoilerplateCommentSniff implements Sniff { - protected static $comment = [ + protected static array $comment = [ "// This file is part of", "//", "// Moodle is free software: you can redistribute it and/or modify", @@ -44,80 +44,231 @@ class BoilerplateCommentSniff implements Sniff "// GNU General Public License for more details.", "//", "// You should have received a copy of the GNU General Public License", - "// along with Moodle. If not, see .", + "// along with Moodle. If not, see .", ]; - public function register() { + + public string $productName = 'Moodle'; + + public string $firstLinePostfix = ' - https://moodle.org/'; + + public function register(): array + { return [T_OPEN_TAG]; } - public function process(File $file, $stackptr) { + public function process(File $phpcsFile, $stackPtr): void + { // We only want to do this once per file. - $prevopentag = $file->findPrevious(T_OPEN_TAG, $stackptr - 1); + $prevopentag = $phpcsFile->findPrevious(T_OPEN_TAG, $stackPtr - 1); if ($prevopentag !== false) { return; // @codeCoverageIgnore } - if ($stackptr > 0) { - $file->addError('The first thing in a PHP file must be the 0) { + $phpcsFile->addError('The first thing in a PHP file must be the getTokens(); + $tokens = $phpcsFile->getTokens(); // Allow T_PHPCS_XXX comment annotations in the first line (skip them). - if ($commentptr = $file->findNext(Tokens::$phpcsCommentTokens, $stackptr + 1, $stackptr + 3)) { - $stackptr = $commentptr; + if ($commentptr = $phpcsFile->findNext(Tokens::$phpcsCommentTokens, $stackPtr + 1, $stackPtr + 3)) { + $stackPtr = $commentptr; } - // Find count the number of newlines after the opening findNext(T_COMMENT, $expectedafter + 1); + + // Check that it appears to be a Moodle boilerplate comment. + $regex = $this->regexForLine(self::$comment[0]); + $boilerplatefound = ($firstcommentptr !== false) && preg_match($regex, $tokens[$firstcommentptr]['content']); - if ($numnewlines > 0) { - $file->addError( - 'The opening addFixableError( + 'Moodle boilerplate not found', + $stackPtr, + 'NoBoilerplateComment' ); + + if ($fix) { + $this->insertBoilerplate($phpcsFile, $expectedafter); + } return; } - $offset = $stackptr + $numnewlines + 1; // Now check the text of the comment. + $textfixed = false; + $tokenptr = $firstcommentptr; foreach (self::$comment as $lineindex => $line) { - $tokenptr = $offset + $lineindex; + // We already checked the first line. + if ($lineindex === 0) { + continue; + } + + $tokenptr = $firstcommentptr + $lineindex; + $iseof = $tokenptr >= $phpcsFile->numTokens; + + if ($iseof || $tokens[$tokenptr]['code'] != T_COMMENT || strpos($tokens[$tokenptr]['content'], '//') !== 0) { + $errorline = $iseof ? $tokenptr - 1 : $tokenptr; + + $fix = $phpcsFile->addFixableError( + 'Comment does not contain full Moodle boilerplate', + $errorline, + 'CommentEndedTooSoon' + ); + + if ($fix) { + $this->completeBoilerplate($phpcsFile, $tokenptr - 1, $lineindex); + return; + } - if (!array_key_exists($tokenptr, $tokens)) { - $file->addError('Reached the end of the file before finding ' . - 'all of the opening comment.', $tokenptr - 1, 'FileTooShort'); + // No point checking whitespace after comment if it is incomplete. return; } - $regex = str_replace( - ['Moodle', 'http\\:'], - ['.*', 'https?\\:'], - '/^' . preg_quote($line, '/') . '/' - ); + $regex = $this->regexForLine($line); - if ( - $tokens[$tokenptr]['code'] != T_COMMENT || - !preg_match($regex, $tokens[$tokenptr]['content']) - ) { - $file->addError( + if (!preg_match($regex, $tokens[$tokenptr]['content'])) { + $fix = $phpcsFile->addFixableError( 'Line %s of the opening comment must start "%s".', $tokenptr, 'WrongLine', [$lineindex + 1, $line] ); + + if ($fix) { + $phpcsFile->fixer->replaceToken($tokenptr, $line . "\n"); + $textfixed = true; + } + } + } + + if ($firstcommentptr !== $expectedafter + 1) { + $fix = $phpcsFile->addFixableError( + 'Moodle boilerplate not found at first line', + $expectedafter + 1, + 'NotAtFirstLine' + ); + + // If the boilerplate comment has been changed we need to commit the fixes before + // moving it. + if ($fix && !$textfixed) { + $this->moveBoilerplate($phpcsFile, $firstcommentptr, $expectedafter); } + + // There's no point in checking the whitespace after the boilerplate + // if it's not in the right place. + return; + } + + if ($tokenptr === $phpcsFile->numTokens - 1) { + return; + } + + $tokenptr++; + + $nextnonwhitespace = $phpcsFile->findNext(T_WHITESPACE, $tokenptr, null, true); + + // Allow indentation. + if ($nextnonwhitespace !== false && strpos($tokens[$nextnonwhitespace - 1]['content'], "\n") === false) { + $nextnonwhitespace--; } + + if ( + ($nextnonwhitespace === false) && array_key_exists($tokenptr + 1, $tokens) || + ($nextnonwhitespace !== false && $nextnonwhitespace !== $tokenptr + 1) + ) { + $fix = $phpcsFile->addFixableError( + 'Boilerplate comment must be followed by a single blank line or end of file', + $tokenptr, + 'SingleTrailingNewLine' + ); + + if ($fix) { + if ($nextnonwhitespace === false) { + while (array_key_exists(++$tokenptr, $tokens)) { + $phpcsFile->fixer->replaceToken($tokenptr, ''); + } + } elseif ($nextnonwhitespace === $tokenptr) { + $phpcsFile->fixer->addContentBefore($tokenptr, "\n"); + } else { + while (++$tokenptr < $nextnonwhitespace) { + if ($tokens[$tokenptr]['content'][-1] === "\n") { + $phpcsFile->fixer->replaceToken($tokenptr, ''); + } + } + } + } + } + } + + private function fullComment(): array + { + $result = []; + foreach (self::$comment as $lineindex => $line) { + if ($lineindex === 0) { + $result[] = $line . ' ' . $this->productName . $this->firstLinePostfix; + } else { + $result[] = str_replace('Moodle', $this->productName, $line); + } + } + return $result; + } + + private function insertBoilerplate(File $file, int $stackptr): void + { + $prefix = substr($file->getTokens()[$stackptr]['content'], -1) === "\n" ? '' : "\n"; + $file->fixer->addContent($stackptr, $prefix . implode("\n", $this->fullComment()) . "\n"); + } + + private function moveBoilerplate(File $file, int $start, int $target): void + { + $tokens = $file->getTokens(); + + $file->fixer->beginChangeset(); + + // If we have only whitespace between expected location and first comment, just remove it. + $nextnonwhitespace = $file->findPrevious(T_WHITESPACE, $start - 1, $target, true); + + if ($nextnonwhitespace === false || $nextnonwhitespace === $target) { + foreach (range($target + 1, $start - 1) as $whitespaceptr) { + $file->fixer->replaceToken($whitespaceptr, ''); + } + $file->fixer->endChangeset(); + return; + } + + // Otherwise shift existing comment to correct place. + $existingboilerplate = []; + foreach (range(0, count(self::$comment)) as $lineindex) { + $tokenptr = $start + $lineindex; + + $existingboilerplate[] = $tokens[$tokenptr]['content']; + + $file->fixer->replaceToken($tokenptr, ''); + } + + $file->fixer->addContent($target, implode("", $existingboilerplate) . "\n"); + + $file->fixer->endChangeset(); + } + + private function completeBoilerplate(File $file, $stackptr, int $lineindex): void + { + $file->fixer->addContent($stackptr, implode("\n", array_slice($this->fullComment(), $lineindex)) . "\n"); + } + + /** + * @param string $line + * @return string + */ + private function regexForLine(string $line): string + { + return str_replace( + ['Moodle', 'https\\:'], + ['.*', 'https?\\:'], + '/^' . preg_quote($line, '/') . '/' + ); } } diff --git a/vendor/moodlehq/moodle-cs/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php b/vendor/moodlehq/moodle-cs/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php index de5dece5..0d77d5f4 100644 --- a/vendor/moodlehq/moodle-cs/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php +++ b/vendor/moodlehq/moodle-cs/moodle/Sniffs/PHPUnit/TestCaseCoversSniff.php @@ -85,6 +85,7 @@ public function process(File $file, $pointer) { $class = $file->getDeclarationName($cStart); $classCovers = false; // To control when the class has a @covers tag. $classCoversNothing = false; // To control when the class has a @coversNothing tag. + $classCoversDefaultClass = []; // To annotate all the existing @coversDefaultClass tags. // Only if the class is extending something. // TODO: We could add a list of valid classes once we have a class-map available. @@ -121,12 +122,30 @@ public function process(File $file, $pointer) { case '@coversDefaultClass': // Validate basic syntax (FQCN). $this->checkCoversTagsSyntax($file, $docPointer, '@coversDefaultClass'); + $classCoversDefaultClass[] = $docPointer; // Annotated for later checks. break; } } } } + // If we have found more than one @coversDefaultClass, that's an error. + if (count($classCoversDefaultClass) > 1) { + // We have to reverse the array to get them in correct order and then + // remove the 1st one that is correct/allowed. + $classCoversDefaultClass = array_reverse($classCoversDefaultClass); + array_shift($classCoversDefaultClass); + // Report the remaining ones. + foreach ($classCoversDefaultClass as $classCoversDefaultClassPointer) { + $file->addError( + 'Class %s has more than one @coversDefaultClass tag, only one allowed', + $classCoversDefaultClassPointer, + 'MultipleDefaultClass', + [$class] + ); + } + } + // Both @covers and @coversNothing, that's a mistake. 2 errors. if ($classCovers && $classCoversNothing) { $file->addError(