Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions moodle/Sniffs/Commenting/InlineCommentSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public function process(File $phpcsFile, $stackPtr) {
T_PRIVATE,
T_PROTECTED,
T_FINAL,
T_READONLY,
T_STATIC,
T_ABSTRACT,
T_CONST,
Expand Down
42 changes: 25 additions & 17 deletions moodle/Sniffs/Files/MoodleInternalSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Tokens\Collections;

class MoodleInternalSniff implements Sniff
{
Expand Down Expand Up @@ -347,30 +348,37 @@ private function codeChangesGlobalState(File $file, $start, $end) {
$conditions = [T_IF => T_IF, T_ELSE => T_ELSE, T_ELSEIF => T_ELSEIF];

for ($i = $start; $i <= $end; $i++) {
$token = $tokens[$i];

// Ignore whitespace and comments.
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
if (isset(Tokens::$emptyTokens[$token['code']]) === true) {
continue;
}

// Ignore class prefixes.
if (isset(Collections::classModifierKeywords()[$token['code']]) === true) {
continue;
}

// Ignore function/class prefixes.
if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true) {
// Ignore method prefixes.
if (isset(Tokens::$methodPrefixes[$token['code']]) === true) {
continue;
}

// Ignore anon classes.
if ($tokens[$i]['code'] === T_ANON_CLASS) {
$i = $tokens[$i]['scope_closer'];
if ($token['code'] === T_ANON_CLASS) {
$i = $token['scope_closer'];
continue;
}

switch ($tokens[$i]['code']) {
switch ($token['code']) {
case T_NAMESPACE:
case T_USE:
case T_DECLARE:
case T_CONST:
// Ignore entire namespace, declare, const and use statements.
if (isset($tokens[$i]['scope_opener']) === true) {
$i = $tokens[$i]['scope_closer'];
if (isset($token['scope_opener']) === true) {
$i = $token['scope_closer'];
} else {
$semicolon = $file->findNext(T_SEMICOLON, ($i + 1));
if ($semicolon !== false) {
Expand All @@ -379,14 +387,14 @@ private function codeChangesGlobalState(File $file, $start, $end) {
}
continue 2;
case T_STRING:
if (isset($tokens[$i]['content']) === true) {
if (isset($token['content']) === true) {
// Ignore class_alias as this is no different to declaring a class.
// This will be in the format `class_alias(source, target);` and represented by:
// - T_STRING['content'] = 'class_alias'
// - T_OPEN_PARENTHESIS
// - ...
// - T_CLOSE_PARENTHESIS
if ($tokens[$i]['content'] === 'class_alias') {
if ($token['content'] === 'class_alias') {
$paren = $file->findNext(T_OPEN_PARENTHESIS, ($i + 1));
if ($paren !== false) {
$i = $tokens[$paren]['parenthesis_closer'] + 1;
Expand All @@ -397,10 +405,10 @@ private function codeChangesGlobalState(File $file, $start, $end) {
}

// Detect and skip over symbols.
if (isset($symbols[$tokens[$i]['code']]) === true && isset($tokens[$i]['scope_closer']) === true) {
$i = $tokens[$i]['scope_closer'];
if (isset($symbols[$token['code']]) === true && isset($token['scope_closer']) === true) {
$i = $token['scope_closer'];
continue;
} elseif ($tokens[$i]['code'] === T_STRING && strtolower($tokens[$i]['content']) === 'define') {
} elseif ($token['code'] === T_STRING && strtolower($token['content']) === 'define') {
$prev = $file->findPrevious(T_WHITESPACE, ($i - 1), null, true);
if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR) {
$semicolon = $file->findNext(T_SEMICOLON, ($i + 1));
Expand All @@ -415,17 +423,17 @@ private function codeChangesGlobalState(File $file, $start, $end) {
// Conditional statements are allowed in symbol files as long as the
// contents is only a symbol definition. So don't count these as effects
// in this case.
if (isset($conditions[$tokens[$i]['code']]) === true) {
if (isset($tokens[$i]['scope_opener']) === false) {
if (isset($conditions[$token['code']]) === true) {
if (isset($token['scope_opener']) === false) {
// Probably an "else if", so just ignore.
continue;
}

if ($this->codeChangesGlobalState($file, ($tokens[$i]['scope_opener'] + 1), ($tokens[$i]['scope_closer'] - 1))) {
if ($this->codeChangesGlobalState($file, ($token['scope_opener'] + 1), ($token['scope_closer'] - 1))) {
return true;
}

$i = $tokens[$i]['scope_closer'];
$i = $token['scope_closer'];
continue;
}

Expand Down
6 changes: 6 additions & 0 deletions moodle/Tests/Sniffs/Commenting/InlineCommentSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public static function commentsProvider(): \Generator {
'errors' => [],
'warnings' => [],
];
yield 'Readonly class doc block' => [
'fixture' => 'readonly_with_namespace',
'fixtureFilename' => null,
'errors' => [],
'warnings' => [],
];
yield 'Closing punctuation behaves correctly' => [
'fixture' => 'punctuation',
'fixtureFilename' => null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace MoodleHQ\MoodleCS\moodle\Tests\Sniffs\Commenting\fixtures\InlineComment;

/**
* Readonly class.
*
* @copyright 2026 Daniel Fainberg
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
readonly class readonly_with_namespace {
}
5 changes: 5 additions & 0 deletions moodle/Tests/Sniffs/Files/MoodleInternalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ public static function moodleFilesMoodleInternalProvider(): array {
[],
[],
],
[
'readonly_ok',
[],
[],
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

/**
* Readonly class.
*
* @copyright 2026 Daniel Fainberg
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
readonly class readonly_ok { }