-
-
Notifications
You must be signed in to change notification settings - Fork 0
Added 3 new checks. #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@coderabbitai |
📝 WalkthroughWalkthroughAdds three new PHP_CodeSniffer sniffs enforcing PHPUnit data-provider conventions (prefix, provider name matching test name, provider/test ordering), updates ruleset to enable them, and introduces extensive unit/functional tests plus fixtures demonstrating valid and invalid cases. Changes
Sequence Diagram(s)sequenceDiagram
participant PHPCS as PHP_CodeSniffer
participant Sniff as DataProvider Sniff (Prefix/Match/Order)
participant File as File (token stream)
participant Reporter as PHP_CodeSniffer Reporter
PHPCS->>Sniff: register() → tokens (T_FUNCTION / T_CLASS)
PHPCS->>File: Tokenize source
loop for each registered token
PHPCS->>Sniff: process(File, tokenPtr)
Sniff->>File: Determine if inside test class / is test method
alt test method detected
Sniff->>File: Read docblock `@dataProvider` / attribute DataProvider(...)
alt provider found and local
Sniff->>Sniff: Validate (prefix / name endsWith testName / ordering)
alt validation fails
Sniff->>Reporter: addError(code, message, position)
else
Sniff->>Sniff: no error
end
else no provider or external -> skip
end
else not test context -> skip
end
end
PHPCS->>Reporter: Collect & output violations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 3 6 +3
Lines 164 370 +206
==========================================
+ Hits 164 370 +206 ☔ View full report in Codecov by Sentry. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1383 🏁 Script executed: Length of output: 31150 🏁 Script executed: Length of output: 12469 @AlexSkrypnyk, here are the descriptions with examples for the three new rules: DrevOps.TestingPractices.DataProviderPrefixPurposeEnforces naming conventions for PHPUnit data provider methods. This sniff ensures that data provider methods start with a configurable prefix (default: Configuration
Examples✓ Valid: class UserTest extends TestCase {
/**
* @dataProvider dataProviderValidCases
*/
public function testSomething($input, $expected): void {
$this->assertEquals($expected, $input);
}
public function dataProviderValidCases(): array {
return [['input1', 'expected1']];
}
}✗ Invalid: class UserTest extends TestCase {
/**
* @dataProvider providerInvalidCases
*/
public function testSomething($input, $expected): void {
$this->assertEquals($expected, $input);
}
// ERROR: Data provider method "providerInvalidCases" should start with
// prefix "dataProvider", suggested name: "dataProviderInvalidCases"
public function providerInvalidCases(): array {
return [['input1', 'expected1']];
}
/**
* @dataProvider casesForAnotherTest
*/
public function testAnother($value): void {
$this->assertNotEmpty($value);
}
// ERROR: Data provider method "casesForAnotherTest" should start with
// prefix "dataProvider", suggested name: "dataProviderForAnotherTest"
public function casesForAnotherTest(): array {
return [['value1']];
}
/**
* @dataProvider provideDataForTest
*/
public function testWithProvide($data): void {
$this->assertIsString($data);
}
// ERROR: Data provider method "provideDataForTest" should start with
// prefix "dataProvider", suggested name: "dataProviderDataForTest"
public function provideDataForTest(): array {
return [['data1']];
}
}DrevOps.TestingPractices.DataProviderMatchesTestNamePurposeEnforces that data provider method names match the test method names they serve. The provider name must end with the exact test name (after removing the "test" prefix). This improves code readability and maintains a clear relationship between tests and their data providers. ConfigurationNone Examples✓ Valid: class UserTest extends TestCase {
/**
* @dataProvider dataProviderUserLogin
*/
public function testUserLogin($user, $pass): void {
$this->assertNotEmpty($user);
}
public function dataProviderUserLogin(): array {
return [['user1', 'pass1']];
}
/**
* @dataProvider providerEmailValidation
*/
public function testEmailValidation($email): void {
$this->assertNotEmpty($email);
}
// Different prefix is OK, as long as it ends with "EmailValidation"
public function providerEmailValidation(): array {
return [['test@example.com']];
}
/**
* @dataProvider AuthenticationScenarios
*/
public function testAuthenticationScenarios($scenario): void {
$this->assertIsString($scenario);
}
// No prefix is OK, as long as it matches exactly
public function AuthenticationScenarios(): array {
return [['scenario1']];
}
#[DataProvider('dataProviderPasswordValidation')]
public function testPasswordValidation($password): void {
$this->assertNotEmpty($password);
}
// Works with PHP 8 attributes too
public function dataProviderPasswordValidation(): array {
return [['password123']];
}
}✗ Invalid: class UserTest extends TestCase {
/**
* @dataProvider dataProviderLogin
*/
public function testUserLogin($user, $pass): void {
$this->assertNotEmpty($user);
}
// ERROR: Data provider method "dataProviderLogin" does not match test
// method "testUserLogin". Expected provider name to end with "UserLogin"
public function dataProviderLogin(): array {
return [['user1', 'pass1']];
}
/**
* @dataProvider dataProviderEmailValidationCases
*/
public function testEmailValidation($email): void {
$this->assertNotEmpty($email);
}
// ERROR: Data provider method "dataProviderEmailValidationCases" does not
// match test method "testEmailValidation". Expected provider name to end
// with "EmailValidation"
public function dataProviderEmailValidationCases(): array {
return [['test@example.com']];
}
/**
* @dataProvider providerAuth
*/
public function testAuthenticationScenarios($scenario): void {
$this->assertIsString($scenario);
}
// ERROR: Data provider method "providerAuth" does not match test method
// "testAuthenticationScenarios". Expected provider name to end with
// "AuthenticationScenarios"
public function providerAuth(): array {
return [['scenario1']];
}
#[DataProvider('dataProviderPass')]
public function testPasswordValidation($password): void {
$this->assertNotEmpty($password);
}
// ERROR: Data provider method "dataProviderPass" does not match test
// method "testPasswordValidation". Expected provider name to end with
// "PasswordValidation"
public function dataProviderPass(): array {
return [['password123']];
}
}DrevOps.TestingPractices.DataProviderOrderPurposeEnforces ordering of data provider and test methods in test files. This ensures structural organization and improves code readability. By default, data providers should be defined after their test methods. Helper methods between tests and providers are allowed. Configuration
Examples✓ Valid (providerPosition="after"): class UserTest extends TestCase {
/**
* @dataProvider dataProviderUserLogin
*/
public function testUserLogin($user, $pass): void {
$this->assertNotEmpty($user);
}
// Provider after test - CORRECT
public function dataProviderUserLogin(): array {
return [['user1', 'pass1']];
}
/**
* @dataProvider dataProviderEmailValidation
*/
public function testEmailValidation($email): void {
$this->assertNotEmpty($email);
}
// Helper methods between test and provider are allowed
private function helperValidate(): void {
// Helper logic
}
// Provider still after test - CORRECT
public function dataProviderEmailValidation(): array {
return [['test@example.com']];
}
#[DataProvider('dataProviderAuthentication')]
public function testAuthentication($scenario): void {
$this->assertIsString($scenario);
}
// Works with PHP 8 attributes too
public function dataProviderAuthentication(): array {
return [['scenario1']];
}
}✗ Invalid (providerPosition="after"): class UserTest extends TestCase {
// Provider before test - WRONG ORDER
public function dataProviderUserLogin(): array {
return [['user1', 'pass1']];
}
/**
* @dataProvider dataProviderUserLogin
*/
// ERROR: Data provider method "dataProviderUserLogin" (line 10) appears
// before test method "testUserLogin" (line 17). Providers should be
// defined after their test methods
public function testUserLogin($user, $pass): void {
$this->assertNotEmpty($user);
}
// Another provider before test - WRONG ORDER
public function dataProviderEmailValidation(): array {
return [['test@example.com']];
}
/**
* @dataProvider dataProviderEmailValidation
*/
// ERROR: Data provider method "dataProviderEmailValidation" (line 24)
// appears before test method "testEmailValidation" (line 31). Providers
// should be defined after their test methods
public function testEmailValidation($email): void {
$this->assertNotEmpty($email);
}
}Note: All three rules:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (16)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php(1 hunks)src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php(1 hunks)src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php(1 hunks)src/DrevOps/ruleset.xml(1 hunks)tests/Fixtures/DataProviderInvalid.php(1 hunks)tests/Fixtures/DataProviderMatchingInvalid.php(1 hunks)tests/Fixtures/DataProviderMatchingValid.php(1 hunks)tests/Fixtures/DataProviderOrderInvalid.php(1 hunks)tests/Fixtures/DataProviderOrderValid.php(1 hunks)tests/Fixtures/DataProviderValid.php(1 hunks)tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php(1 hunks)tests/Functional/DataProviderOrderSniffFunctionalTest.php(1 hunks)tests/Functional/DataProviderPrefixSniffFunctionalTest.php(1 hunks)tests/Unit/DataProviderMatchesTestNameSniffTest.php(1 hunks)tests/Unit/DataProviderOrderSniffTest.php(1 hunks)tests/Unit/DataProviderPrefixSniffTest.php(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
tests/Fixtures/DataProviderValid.php (1)
tests/Fixtures/DataProviderInvalid.php (2)
testSomething(19-21)testAnother(40-42)
tests/Fixtures/DataProviderMatchingValid.php (3)
tests/Fixtures/DataProviderMatchingInvalid.php (2)
DataProvider(81-84)DataProvider(101-104)tests/Fixtures/DataProviderOrderInvalid.php (1)
DataProvider(70-73)tests/Fixtures/DataProviderOrderValid.php (1)
DataProvider(66-69)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php (2)
tests/Functional/DataProviderOrderSniffFunctionalTest.php (2)
CoversNothing(13-62)Group(21-24)tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (1)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (3)
register(35-37)process(42-82)isTestClass(95-133)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (2)
register(35-37)isTestClass(95-133)src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
register(51-53)isTestClass(124-162)
tests/Functional/DataProviderOrderSniffFunctionalTest.php (2)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php (2)
CoversNothing(13-72)Group(21-24)tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
tests/Functional/DataProviderPrefixSniffFunctionalTest.php (1)
tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (3)
register(49-51)process(56-81)isTestClass(94-124)src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (3)
register(51-53)process(58-111)isTestClass(124-162)
tests/Fixtures/DataProviderOrderValid.php (1)
tests/Fixtures/DataProviderOrderInvalid.php (6)
DataProvider(70-73)testUserLogin(32-34)dataProviderUserLogin(20-25)testEmailValidation(52-54)dataProviderEmailValidation(41-45)dataProviderAuthentication(61-65)
tests/Fixtures/DataProviderOrderInvalid.php (1)
tests/Fixtures/DataProviderOrderValid.php (6)
DataProvider(66-69)dataProviderUserLogin(29-34)testUserLogin(20-22)dataProviderEmailValidation(57-61)testEmailValidation(41-43)dataProviderAuthentication(76-80)
tests/Fixtures/DataProviderMatchingInvalid.php (1)
tests/Fixtures/DataProviderMatchingValid.php (5)
DataProvider(81-84)DataProvider(101-104)testUserLogin(20-22)testEmailValidation(41-43)testAuthenticationScenarios(62-64)
tests/Unit/DataProviderMatchesTestNameSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (2)
DataProviderMatchesTestNameSniff(25-296)register(35-37)tests/Unit/UnitTestCase.php (3)
UnitTestCase(18-152)processCode(46-53)findFunctionToken(85-93)
tests/Unit/DataProviderOrderSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (2)
DataProviderOrderSniff(27-421)register(49-51)tests/Unit/UnitTestCase.php (4)
UnitTestCase(18-152)processCode(46-53)findClassToken(104-112)findFunctionToken(85-93)
tests/Unit/DataProviderPrefixSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
DataProviderPrefixSniff(17-292)register(51-53)tests/Unit/UnitTestCase.php (3)
UnitTestCase(18-152)processCode(46-53)findFunctionToken(85-93)
🪛 GitHub Actions: Test PHP
tests/Unit/DataProviderPrefixSniffTest.php
[error] 513-513: PHPUnit error: The data provider specified for DrevOps\PhpcsStandard\Tests\Unit\DataProviderPrefixSniffTest::testFindDataProvidersWithEmptyMethodName is invalid. Method DrevOps\PhpcsStandard\Tests\Unit\DataProviderPrefixSniffTest::without method name.() does not exist.
[warning] 1-1: PHPUnit deprecation: Metadata found in doc-comment for method DrevOps\PhpcsStandard\Tests\Unit\DataProviderPrefixSniffTest::testFindDataProvidersWithEmptyMethodName(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead.
[error] 1-1: ERROR: Script phpunit --no-coverage handling the test event returned with error code 2
🪛 PHPMD (2.15.0)
tests/Fixtures/DataProviderMatchingValid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
71-76: The method AuthenticationScenarios is not named in camelCase. (undefined)
(CamelCaseMethodName)
111-116: The method TokenGeneration is not named in camelCase. (undefined)
(CamelCaseMethodName)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php
14-72: Avoid excessively long class names like DataProviderMatchesTestNameSniffFunctionalTest. Keep class name length under 40. (undefined)
(LongClassName)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php
78-78: The variable $function_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
86-86: The variable $function_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
99-99: The variable $suggested_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
128-128: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
134-134: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
142-142: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
150-150: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
152-152: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
154-154: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
188-188: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
193-193: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
232-232: The variable $common_prefixes is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $common_prefix is not named in camelCase. (undefined)
(CamelCaseVariableName)
266-290: The method fixProviderName() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
274-274: The variable $prev_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
284-284: The variable $prev_tag is not named in camelCase. (undefined)
(CamelCaseVariableName)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php
27-421: The class DataProviderOrderSniff has an overall complexity of 50 which is very high. The configured complexity threshold is 50. (undefined)
(ExcessiveClassComplexity)
65-65: The variable $class_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
66-66: The variable $class_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
98-98: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
104-104: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
112-112: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
114-114: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
116-116: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
145-145: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
148-148: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
154-154: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
162-162: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
173-173: The variable $test_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
202-202: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
205-205: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
211-211: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
230-301: The method validateOrder() has a Cyclomatic Complexity of 14. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
230-301: The method validateOrder() has an NPath complexity of 361. The configured NPath complexity threshold is 200. (undefined)
(NPathComplexity)
232-232: The variable $checked_providers is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_info is not named in camelCase. (undefined)
(CamelCaseVariableName)
235-235: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $test_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
258-258: The variable $provider_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
261-261: The variable $has_violation is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $error_code is not named in camelCase. (undefined)
(CamelCaseVariableName)
263-263: The variable $error_message is not named in camelCase. (undefined)
(CamelCaseVariableName)
288-288: The variable $error_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
318-318: The variable $comment_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
323-323: The variable $comment_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
341-341: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
347-347: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
377-377: The variable $attribute_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
389-389: The variable $name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
396-396: The variable $open_paren is not named in camelCase. (undefined)
(CamelCaseVariableName)
403-403: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
410-410: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php
49-49: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
55-55: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
63-63: The variable $test_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
66-66: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
99-99: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
105-105: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
111-111: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
120-120: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
122-122: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
124-124: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
177-177: The variable $comment_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
182-182: The variable $comment_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
200-200: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
206-206: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $attribute_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
248-248: The variable $name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
255-255: The variable $open_paren is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
269-269: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
tests/Fixtures/DataProviderOrderValid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
48-50: Avoid unused private methods such as 'helperValidate'. (undefined)
(UnusedPrivateMethod)
tests/Fixtures/DataProviderOrderInvalid.php
32-32: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
tests/Fixtures/DataProviderMatchingInvalid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
111-116: The method TokenGenerationCases is not named in camelCase. (undefined)
(CamelCaseMethodName)
tests/Unit/DataProviderMatchesTestNameSniffTest.php
15-649: The class DataProviderMatchesTestNameSniffTest has 28 non-getter- and setter-methods. Consider refactoring DataProviderMatchesTestNameSniffTest to keep number of methods under 25. (undefined)
(TooManyMethods)
15-649: The class DataProviderMatchesTestNameSniffTest has 28 public methods. Consider refactoring DataProviderMatchesTestNameSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
46-46: Missing class import via use statement (line '46', column '23'). (undefined)
(MissingImport)
58-58: Missing class import via use statement (line '58', column '23'). (undefined)
(MissingImport)
76-76: Missing class import via use statement (line '76', column '23'). (undefined)
(MissingImport)
91-91: Missing class import via use statement (line '91', column '23'). (undefined)
(MissingImport)
109-109: Missing class import via use statement (line '109', column '23'). (undefined)
(MissingImport)
138-138: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
140-140: Missing class import via use statement (line '140', column '23'). (undefined)
(MissingImport)
163-163: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
165-165: Missing class import via use statement (line '165', column '23'). (undefined)
(MissingImport)
185-185: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
187-187: Missing class import via use statement (line '187', column '23'). (undefined)
(MissingImport)
237-237: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
238-238: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
239-239: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
302-302: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
304-304: Missing class import via use statement (line '304', column '23'). (undefined)
(MissingImport)
324-324: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
326-326: Missing class import via use statement (line '326', column '23'). (undefined)
(MissingImport)
346-346: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
348-348: Missing class import via use statement (line '348', column '23'). (undefined)
(MissingImport)
369-369: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
371-371: Missing class import via use statement (line '371', column '23'). (undefined)
(MissingImport)
396-396: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
398-398: Missing class import via use statement (line '398', column '23'). (undefined)
(MissingImport)
421-421: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
423-423: Missing class import via use statement (line '423', column '23'). (undefined)
(MissingImport)
444-444: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
446-446: Missing class import via use statement (line '446', column '23'). (undefined)
(MissingImport)
466-466: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
468-468: Missing class import via use statement (line '468', column '23'). (undefined)
(MissingImport)
494-494: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
496-496: Missing class import via use statement (line '496', column '23'). (undefined)
(MissingImport)
518-518: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
520-520: Missing class import via use statement (line '520', column '23'). (undefined)
(MissingImport)
541-541: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
543-543: Missing class import via use statement (line '543', column '23'). (undefined)
(MissingImport)
564-564: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
566-566: Missing class import via use statement (line '566', column '23'). (undefined)
(MissingImport)
587-587: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
589-589: Missing class import via use statement (line '589', column '23'). (undefined)
(MissingImport)
635-635: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
636-636: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
637-637: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
tests/Unit/DataProviderOrderSniffTest.php
15-524: The class DataProviderOrderSniffTest has 22 public methods. Consider refactoring DataProviderOrderSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
89-89: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
90-90: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
91-91: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
237-237: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
239-239: Missing class import via use statement (line '239', column '23'). (undefined)
(MissingImport)
259-259: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
261-261: Missing class import via use statement (line '261', column '23'). (undefined)
(MissingImport)
281-281: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
283-283: Missing class import via use statement (line '283', column '23'). (undefined)
(MissingImport)
303-303: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
305-305: Missing class import via use statement (line '305', column '23'). (undefined)
(MissingImport)
341-341: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
343-343: Missing class import via use statement (line '343', column '23'). (undefined)
(MissingImport)
368-368: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
370-370: Missing class import via use statement (line '370', column '23'). (undefined)
(MissingImport)
393-393: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
395-395: Missing class import via use statement (line '395', column '23'). (undefined)
(MissingImport)
421-421: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
423-423: Missing class import via use statement (line '423', column '23'). (undefined)
(MissingImport)
445-445: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
447-447: Missing class import via use statement (line '447', column '23'). (undefined)
(MissingImport)
468-468: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
470-470: Missing class import via use statement (line '470', column '23'). (undefined)
(MissingImport)
491-491: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
493-493: Missing class import via use statement (line '493', column '23'). (undefined)
(MissingImport)
514-514: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
516-516: Missing class import via use statement (line '516', column '23'). (undefined)
(MissingImport)
tests/Unit/DataProviderPrefixSniffTest.php
15-574: The class DataProviderPrefixSniffTest has 25 public methods. Consider refactoring DataProviderPrefixSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
54-54: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
56-56: Missing class import via use statement (line '56', column '23'). (undefined)
(MissingImport)
76-76: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
78-78: Missing class import via use statement (line '78', column '23'). (undefined)
(MissingImport)
99-99: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
101-101: Missing class import via use statement (line '101', column '23'). (undefined)
(MissingImport)
121-121: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
123-123: Missing class import via use statement (line '123', column '23'). (undefined)
(MissingImport)
160-160: Missing class import via use statement (line '160', column '23'). (undefined)
(MissingImport)
194-194: Missing class import via use statement (line '194', column '23'). (undefined)
(MissingImport)
210-210: Missing class import via use statement (line '210', column '23'). (undefined)
(MissingImport)
222-222: Missing class import via use statement (line '222', column '23'). (undefined)
(MissingImport)
236-236: Missing class import via use statement (line '236', column '23'). (undefined)
(MissingImport)
248-248: Missing class import via use statement (line '248', column '23'). (undefined)
(MissingImport)
260-260: Missing class import via use statement (line '260', column '23'). (undefined)
(MissingImport)
272-272: Missing class import via use statement (line '272', column '23'). (undefined)
(MissingImport)
284-284: Missing class import via use statement (line '284', column '23'). (undefined)
(MissingImport)
296-296: Missing class import via use statement (line '296', column '23'). (undefined)
(MissingImport)
354-354: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
355-355: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
356-356: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
427-427: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
428-428: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
429-429: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
473-473: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
475-475: Missing class import via use statement (line '475', column '23'). (undefined)
(MissingImport)
501-501: Missing class import via use statement (line '501', column '23'). (undefined)
(MissingImport)
526-526: Missing class import via use statement (line '526', column '23'). (undefined)
(MissingImport)
539-539: Missing class import via use statement (line '539', column '23'). (undefined)
(MissingImport)
🔇 Additional comments (7)
tests/Fixtures/DataProviderMatchingValid.php (1)
13-116: Valid matching fixture looks consistent with sniff rulesEach test’s provider name ends with the expected test name (including attribute-based and no‑prefix cases), and providers are placed after their tests, so this fixture accurately models “valid” scenarios for the new sniffs. Static-analysis warnings about unused parameters and method-name casing are acceptable here as this is test-only fixture code.
tests/Fixtures/DataProviderValid.php (1)
12-84: Fixture correctly exercises valid provider naming and reuseThe tests and their providers are wired up coherently, including a shared provider used by multiple tests, which is useful for exercising both prefix and ordering sniffs. No functional issues spotted.
tests/Fixtures/DataProviderMatchingInvalid.php (1)
13-116: Invalid matching fixture correctly covers negative scenariosAll providers here intentionally violate the “ends with test name” rule (partial matches, extra suffixes, different prefixes, and attribute-based cases), which is exactly what the negative fixture should do. No code issues from the fixture itself.
tests/Unit/DataProviderOrderSniffTest.php (1)
14-524: Unit coverage forDataProviderOrderSniffis comprehensive and alignedThe tests exercise all key paths: correct/incorrect ordering (including attributes and helpers), shared providers, external providers, non-test classes,
isTestClassvariants, providerPosition defaults/overrides, and annotation/attribute edge cases. They appear consistent with the sniff’s implementation and should guard against regressions effectively.tests/Unit/DataProviderMatchesTestNameSniffTest.php (3)
1-32: LGTM: Well-structured test class setup.The class structure, imports, and setUp method are correctly implemented. The configuration properly isolates the DataProviderMatchesTestName sniff for testing.
37-121: LGTM: Comprehensive unit tests for private methods.The reflection-based tests appropriately validate the behavior of private helper methods with good coverage of both valid and invalid inputs.
126-595: LGTM: Excellent edge case coverage.The tests comprehensively cover:
- Both annotation (
@dataProvider) and attribute (#[DataProvider]) styles- External provider detection and skipping
- Various malformed or missing provider declarations
- Class hierarchy validation
This thorough coverage ensures the sniff behaves correctly across diverse scenarios.
| private function findDataProviderAnnotation(File $phpcsFile, int $functionPtr): ?string { | ||
| $tokens = $phpcsFile->getTokens(); | ||
|
|
||
| // Search backward for docblock before function. | ||
| $comment_end = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $functionPtr - 1); | ||
| if ($comment_end === FALSE) { | ||
| return NULL; | ||
| } | ||
|
|
||
| $comment_start = $tokens[$comment_end]['comment_opener'] ?? NULL; | ||
| // @codeCoverageIgnoreStart | ||
| if ($comment_start === NULL) { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Look for @dataProvider tag in the docblock. | ||
| for ($i = $comment_start; $i <= $comment_end; $i++) { | ||
| // @codeCoverageIgnoreStart | ||
| if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) { | ||
| continue; | ||
| } | ||
|
|
||
| if ($tokens[$i]['content'] !== '@dataProvider') { | ||
| continue; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Find the method name after the tag. | ||
| $string_ptr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $i + 1, $i + 3); | ||
| // @codeCoverageIgnoreStart | ||
| if ($string_ptr === FALSE) { | ||
| continue; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| $method_name = trim($tokens[$string_ptr]['content']); | ||
|
|
||
| // Skip external providers (ClassName::methodName). | ||
| if (strpos($method_name, '::') !== FALSE) { | ||
| return NULL; | ||
| } | ||
|
|
||
| return $method_name; | ||
| } | ||
|
|
||
| // @codeCoverageIgnoreStart | ||
| return NULL; | ||
| // @codeCoverageIgnoreEnd | ||
| } | ||
|
|
||
| /** | ||
| * Finds data provider from #[DataProvider] attribute. | ||
| * | ||
| * @param \PHP_CodeSniffer\Files\File $phpcsFile | ||
| * The file being scanned. | ||
| * @param int $functionPtr | ||
| * The position of the function token. | ||
| * | ||
| * @return string|null | ||
| * The provider method name, or NULL if not found or external. | ||
| */ | ||
| private function findDataProviderAttribute(File $phpcsFile, int $functionPtr): ?string { | ||
| $tokens = $phpcsFile->getTokens(); | ||
|
|
||
| // Search backward for attribute before function. | ||
| $attribute_ptr = $phpcsFile->findPrevious(T_ATTRIBUTE, $functionPtr - 1); | ||
| if ($attribute_ptr === FALSE) { | ||
| return NULL; | ||
| } | ||
|
|
||
| // Check if attribute is close enough to function (within 10 tokens). | ||
| // @codeCoverageIgnoreStart | ||
| if ($functionPtr - $attribute_ptr > 10) { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Find the attribute name. | ||
| $name_ptr = $phpcsFile->findNext(T_STRING, $attribute_ptr + 1, $functionPtr); | ||
| // @codeCoverageIgnoreStart | ||
| if ($name_ptr === FALSE || $tokens[$name_ptr]['content'] !== 'DataProvider') { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Find the opening parenthesis of attribute. | ||
| $open_paren = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $name_ptr + 1, $functionPtr); | ||
| // @codeCoverageIgnoreStart | ||
| if ($open_paren === FALSE) { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Find the string inside attribute (provider method name). | ||
| $string_ptr = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $open_paren + 1, $functionPtr); | ||
| // @codeCoverageIgnoreStart | ||
| if ($string_ptr === FALSE) { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Extract method name from string (remove quotes). | ||
| $method_name = trim($tokens[$string_ptr]['content'], '\'"'); | ||
|
|
||
| // Skip external providers (ClassName::methodName). | ||
| // @codeCoverageIgnoreStart | ||
| if (strpos($method_name, '::') !== FALSE) { | ||
| return NULL; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| return $method_name; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Provider extraction logic duplicated with DataProviderOrderSniff
findDataProviderAnnotation() and findDataProviderAttribute() here are effectively the same as in DataProviderOrderSniff. That’s fine functionally, but it does mean any future change to how providers are discovered (e.g., new attribute forms, different “closeness” rules, or external-provider handling) must be kept in sync manually.
If you plan to evolve these sniffs further, consider extracting this logic into a shared helper/trait so both sniffs stay aligned with a single implementation.
🧰 Tools
🪛 PHPMD (2.15.0)
177-177: The variable $comment_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
182-182: The variable $comment_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
200-200: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
206-206: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $attribute_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
248-248: The variable $name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
255-255: The variable $open_paren is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
269-269: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
🤖 Prompt for AI Agents
In src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php
lines 173-278 the provider-extraction logic in findDataProviderAnnotation() and
findDataProviderAttribute() duplicates the same code in DataProviderOrderSniff;
extract this logic into a single shared helper (either a new utility class or a
trait, e.g. src/DrevOps/Utils/DataProviderResolver.php or
src/DrevOps/Sniffs/TestingPractices/DataProviderTrait.php) that exposes a method
like resolveProviderName(File $phpcsFile, int $functionPtr): ?string and move
the annotation/attribute parsing into it, then update both sniffs to call that
shared method and remove the duplicated implementations; ensure the helper
preserves existing behavior (docblock and attribute parsing, proximity checks,
external provider filtering) and update imports/namespace references
accordingly.
| private function findDataProviders(File $phpcsFile): array { | ||
| $tokens = $phpcsFile->getTokens(); | ||
| $providers = []; | ||
|
|
||
| // Search for @dataProvider annotations in doc comments. | ||
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | ||
| if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) { | ||
| continue; | ||
| } | ||
|
|
||
| if ($tokens[$i]['content'] !== '@dataProvider') { | ||
| continue; | ||
| } | ||
|
|
||
| // Find the method name after the tag. | ||
| $string_ptr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $i + 1, $i + 3); | ||
| if ($string_ptr === FALSE) { | ||
| continue; | ||
| } | ||
|
|
||
| $method_name = trim($tokens[$string_ptr]['content']); | ||
|
|
||
| // Handle method names that might include class references. | ||
| // E.g., "ClassName::methodName" - we only want "methodName". | ||
| if (strpos($method_name, '::') !== FALSE) { | ||
| continue; | ||
| } | ||
|
|
||
| $providers[$method_name] = TRUE; | ||
| } | ||
|
|
||
| return $providers; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add attribute-aware provider discovery.
findDataProviders() only looks at @dataProvider tags, so any provider referenced via PHPUnit attributes (now the preferred metadata vehicle) is never checked. Because PHPUnit reads attributes before docblocks, the sniff silently misses every attribute-only provider, letting non-compliant names through and undermining the new rule.(docs.phpunit.de)
@@
- // Search for @dataProvider annotations in doc comments.
- for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
+ // Search for @dataProvider annotations in doc comments.
+ for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
continue;
}
@@
$providers[$method_name] = TRUE;
}
+ // Search for #[DataProvider(...)] attributes.
+ for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
+ if ($tokens[$i]['code'] !== T_ATTRIBUTE) {
+ continue;
+ }
+
+ $attribute_closer = $tokens[$i]['attribute_closer'] ?? NULL;
+ if ($attribute_closer === NULL) {
+ continue;
+ }
+
+ $attribute_name_ptr = $phpcsFile->findNext(T_STRING, $i + 1, $attribute_closer);
+ if ($attribute_name_ptr === FALSE) {
+ continue;
+ }
+
+ $attribute_name = $tokens[$attribute_name_ptr]['content'];
+ if (!in_array($attribute_name, ['DataProvider', 'DataProviderExternal'], TRUE)) {
+ continue;
+ }
+
+ $string_ptr = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $attribute_name_ptr + 1, $attribute_closer);
+ if ($string_ptr === FALSE) {
+ continue;
+ }
+
+ $method_name = trim($tokens[$string_ptr]['content'], '\'"');
+ if (strpos($method_name, '::') !== FALSE) {
+ continue;
+ }
+
+ $providers[$method_name] = TRUE;
+ }
+
return $providers;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private function findDataProviders(File $phpcsFile): array { | |
| $tokens = $phpcsFile->getTokens(); | |
| $providers = []; | |
| // Search for @dataProvider annotations in doc comments. | |
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | |
| if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) { | |
| continue; | |
| } | |
| if ($tokens[$i]['content'] !== '@dataProvider') { | |
| continue; | |
| } | |
| // Find the method name after the tag. | |
| $string_ptr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $i + 1, $i + 3); | |
| if ($string_ptr === FALSE) { | |
| continue; | |
| } | |
| $method_name = trim($tokens[$string_ptr]['content']); | |
| // Handle method names that might include class references. | |
| // E.g., "ClassName::methodName" - we only want "methodName". | |
| if (strpos($method_name, '::') !== FALSE) { | |
| continue; | |
| } | |
| $providers[$method_name] = TRUE; | |
| } | |
| return $providers; | |
| } | |
| private function findDataProviders(File $phpcsFile): array { | |
| $tokens = $phpcsFile->getTokens(); | |
| $providers = []; | |
| // Search for @dataProvider annotations in doc comments. | |
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | |
| if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) { | |
| continue; | |
| } | |
| if ($tokens[$i]['content'] !== '@dataProvider') { | |
| continue; | |
| } | |
| // Find the method name after the tag. | |
| $string_ptr = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $i + 1, $i + 3); | |
| if ($string_ptr === FALSE) { | |
| continue; | |
| } | |
| $method_name = trim($tokens[$string_ptr]['content']); | |
| // Handle method names that might include class references. | |
| // E.g., "ClassName::methodName" - we only want "methodName". | |
| if (strpos($method_name, '::') !== FALSE) { | |
| continue; | |
| } | |
| $providers[$method_name] = TRUE; | |
| } | |
| // Search for #[DataProvider(...)] attributes. | |
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | |
| if ($tokens[$i]['code'] !== T_ATTRIBUTE) { | |
| continue; | |
| } | |
| $attribute_closer = $tokens[$i]['attribute_closer'] ?? NULL; | |
| if ($attribute_closer === NULL) { | |
| continue; | |
| } | |
| $attribute_name_ptr = $phpcsFile->findNext(T_STRING, $i + 1, $attribute_closer); | |
| if ($attribute_name_ptr === FALSE) { | |
| continue; | |
| } | |
| $attribute_name = $tokens[$attribute_name_ptr]['content']; | |
| if (!in_array($attribute_name, ['DataProvider', 'DataProviderExternal'], TRUE)) { | |
| continue; | |
| } | |
| $string_ptr = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $attribute_name_ptr + 1, $attribute_closer); | |
| if ($string_ptr === FALSE) { | |
| continue; | |
| } | |
| $method_name = trim($tokens[$string_ptr]['content'], '\'"'); | |
| if (strpos($method_name, '::') !== FALSE) { | |
| continue; | |
| } | |
| $providers[$method_name] = TRUE; | |
| } | |
| return $providers; | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
188-188: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
193-193: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
| private function fixProviderName(File $phpcsFile, string $oldName, string $newName): void { | ||
| $tokens = $phpcsFile->getTokens(); | ||
|
|
||
| // Fix the method declaration. | ||
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | ||
| // Fix method name in function declaration. | ||
| if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === $oldName) { | ||
| // Check if this is a function declaration. | ||
| $prev_ptr = $phpcsFile->findPrevious(T_WHITESPACE, $i - 1, NULL, TRUE); | ||
| if ($prev_ptr !== FALSE && $tokens[$prev_ptr]['code'] === T_FUNCTION) { | ||
| $phpcsFile->fixer->replaceToken($i, $newName); | ||
| } | ||
| } | ||
|
|
||
| // Fix @dataProvider annotations. | ||
| if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { | ||
| $content = $tokens[$i]['content']; | ||
| // Check if this string is part of @dataProvider annotation. | ||
| $prev_tag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, $i - 1, $i - 3); | ||
| if ($prev_tag !== FALSE && $tokens[$prev_tag]['content'] === '@dataProvider' && trim($content) === $oldName) { | ||
| $phpcsFile->fixer->replaceToken($i, $newName); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixer leaves attribute references stale.
When the fixer renames a provider, any #[DataProvider('oldName')] attribute still points at the old method. PHPUnit will then look up the stale identifier and crash the test run, turning a fixable violation into a runtime failure.(docs.phpunit.de)
@@
- // Fix @dataProvider annotations.
+ // Fix @dataProvider annotations.
if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
$content = $tokens[$i]['content'];
// Check if this string is part of @dataProvider annotation.
$prev_tag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, $i - 1, $i - 3);
if ($prev_tag !== FALSE && $tokens[$prev_tag]['content'] === '@dataProvider' && trim($content) === $oldName) {
$phpcsFile->fixer->replaceToken($i, $newName);
}
}
+
+ // Fix #[DataProvider('...')] attributes.
+ if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING) {
+ $clean = trim($tokens[$i]['content'], '\'"');
+ if ($clean !== $oldName) {
+ continue;
+ }
+
+ $attribute_ptr = $phpcsFile->findPrevious(T_ATTRIBUTE, $i);
+ $attribute_closer = $attribute_ptr !== FALSE ? ($tokens[$attribute_ptr]['attribute_closer'] ?? NULL) : NULL;
+ if ($attribute_ptr === FALSE || $attribute_closer === NULL || $attribute_closer < $i) {
+ continue;
+ }
+
+ $attribute_name_ptr = $phpcsFile->findNext(T_STRING, $attribute_ptr + 1, $attribute_closer);
+ if ($attribute_name_ptr === FALSE) {
+ continue;
+ }
+
+ $attribute_name = $tokens[$attribute_name_ptr]['content'];
+ if (!in_array($attribute_name, ['DataProvider', 'DataProviderExternal'], TRUE)) {
+ continue;
+ }
+
+ $quote = $tokens[$i]['content'][0];
+ if ($quote === '\'' || $quote === '"') {
+ $phpcsFile->fixer->replaceToken($i, $quote . $newName . $quote);
+ }
+ }
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private function fixProviderName(File $phpcsFile, string $oldName, string $newName): void { | |
| $tokens = $phpcsFile->getTokens(); | |
| // Fix the method declaration. | |
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | |
| // Fix method name in function declaration. | |
| if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === $oldName) { | |
| // Check if this is a function declaration. | |
| $prev_ptr = $phpcsFile->findPrevious(T_WHITESPACE, $i - 1, NULL, TRUE); | |
| if ($prev_ptr !== FALSE && $tokens[$prev_ptr]['code'] === T_FUNCTION) { | |
| $phpcsFile->fixer->replaceToken($i, $newName); | |
| } | |
| } | |
| // Fix @dataProvider annotations. | |
| if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { | |
| $content = $tokens[$i]['content']; | |
| // Check if this string is part of @dataProvider annotation. | |
| $prev_tag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, $i - 1, $i - 3); | |
| if ($prev_tag !== FALSE && $tokens[$prev_tag]['content'] === '@dataProvider' && trim($content) === $oldName) { | |
| $phpcsFile->fixer->replaceToken($i, $newName); | |
| } | |
| } | |
| } | |
| private function fixProviderName(File $phpcsFile, string $oldName, string $newName): void { | |
| $tokens = $phpcsFile->getTokens(); | |
| // Fix the method declaration. | |
| for ($i = 0; $i < $phpcsFile->numTokens; $i++) { | |
| // Fix method name in function declaration. | |
| if ($tokens[$i]['code'] === T_STRING && $tokens[$i]['content'] === $oldName) { | |
| // Check if this is a function declaration. | |
| $prev_ptr = $phpcsFile->findPrevious(T_WHITESPACE, $i - 1, NULL, TRUE); | |
| if ($prev_ptr !== FALSE && $tokens[$prev_ptr]['code'] === T_FUNCTION) { | |
| $phpcsFile->fixer->replaceToken($i, $newName); | |
| } | |
| } | |
| // Fix @dataProvider annotations. | |
| if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { | |
| $content = $tokens[$i]['content']; | |
| // Check if this string is part of @dataProvider annotation. | |
| $prev_tag = $phpcsFile->findPrevious(T_DOC_COMMENT_TAG, $i - 1, $i - 3); | |
| if ($prev_tag !== FALSE && $tokens[$prev_tag]['content'] === '@dataProvider' && trim($content) === $oldName) { | |
| $phpcsFile->fixer->replaceToken($i, $newName); | |
| } | |
| } | |
| // Fix #[DataProvider('...')] attributes. | |
| if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING) { | |
| $clean = trim($tokens[$i]['content'], '\'"'); | |
| if ($clean !== $oldName) { | |
| continue; | |
| } | |
| $attribute_ptr = $phpcsFile->findPrevious(T_ATTRIBUTE, $i); | |
| $attribute_closer = $attribute_ptr !== FALSE ? ($tokens[$attribute_ptr]['attribute_closer'] ?? NULL) : NULL; | |
| if ($attribute_ptr === FALSE || $attribute_closer === NULL || $attribute_closer < $i) { | |
| continue; | |
| } | |
| $attribute_name_ptr = $phpcsFile->findNext(T_STRING, $attribute_ptr + 1, $attribute_closer); | |
| if ($attribute_name_ptr === FALSE) { | |
| continue; | |
| } | |
| $attribute_name = $tokens[$attribute_name_ptr]['content']; | |
| if (!in_array($attribute_name, ['DataProvider', 'DataProviderExternal'], TRUE)) { | |
| continue; | |
| } | |
| $quote = $tokens[$i]['content'][0]; | |
| if ($quote === '\'' || $quote === '"') { | |
| $phpcsFile->fixer->replaceToken($i, $quote . $newName . $quote); | |
| } | |
| } | |
| } | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
266-290: The method fixProviderName() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
274-274: The variable $prev_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
284-284: The variable $prev_tag is not named in camelCase. (undefined)
(CamelCaseVariableName)
| * Tests isTestMethod with valid test method. | ||
| */ | ||
| public function testIsTestMethodWithValidTest(): void { | ||
| $reflection = new \ReflectionClass($this->sniff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider importing ReflectionClass.
The \ReflectionClass is used frequently throughout the test. Consider adding it to the imports at the top of the file for cleaner code.
Add to imports:
use DrevOps\Sniffs\TestingPractices\DataProviderMatchesTestNameSniff;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\Attributes\CoversClass;
+use ReflectionClass;Then replace inline usage:
- $reflection = new \ReflectionClass($this->sniff);
+ $reflection = new ReflectionClass($this->sniff);Also applies to: 58-58, 76-76, 91-91, 109-109, 140-140, 165-165, 187-187, 304-304, 326-326, 348-348, 371-371, 398-398, 423-423, 446-446, 468-468, 496-496, 520-520, 543-543, 566-566, 589-589
🧰 Tools
🪛 PHPMD (2.15.0)
46-46: Missing class import via use statement (line '46', column '23'). (undefined)
(MissingImport)
🤖 Prompt for AI Agents
tests/Unit/DataProviderMatchesTestNameSniffTest.php around lines 46 (and also at
58, 76, 91, 109, 140, 165, 187, 304, 326, 348, 371, 398, 423, 446, 468, 496,
520, 543, 566, 589): import ReflectionClass at the top of the file (add "use
ReflectionClass;") and replace all occurrences of the fully-qualified
"\ReflectionClass" with the short "ReflectionClass" to clean up the code and
avoid repeated global namespace qualifiers.
| $error_messages = []; | ||
| foreach ($errors as $line_errors) { | ||
| foreach ($line_errors as $column_errors) { | ||
| foreach ($column_errors as $error) { | ||
| $error_messages[] = $error['message']; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Extract duplicated error message extraction logic.
The error message extraction code is duplicated in two test methods. Consider extracting this into a helper method to improve maintainability.
Add a helper method to the test class:
/**
* Extracts error messages from PHPCS file errors.
*
* @param array $errors
* The errors array from $file->getErrors().
*
* @return array
* Flat array of error messages.
*/
private function extractErrorMessages(array $errors): array {
$error_messages = [];
foreach ($errors as $line_errors) {
foreach ($line_errors as $column_errors) {
foreach ($column_errors as $error) {
$error_messages[] = $error['message'];
}
}
}
return $error_messages;
}Then replace the duplicated loops:
- $error_messages = [];
- foreach ($errors as $line_errors) {
- foreach ($line_errors as $column_errors) {
- foreach ($column_errors as $error) {
- $error_messages[] = $error['message'];
- }
- }
- }
+ $error_messages = $this->extractErrorMessages($errors);Also applies to: 635-642
🧰 Tools
🪛 PHPMD (2.15.0)
237-237: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
238-238: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
239-239: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
🤖 Prompt for AI Agents
In tests/Unit/DataProviderMatchesTestNameSniffTest.php around lines 237-244 (and
similarly 635-642), duplicated nested loops extract error messages from the
PHPCS $file->getErrors() structure; add a private helper method on the test
class named extractErrorMessages(array $errors): array that flattens the
three-level $errors array into a simple array of message strings (iterate the
same nested levels and collect $error['message'] into a result array and return
it), then replace both duplicated loop blocks with a call to
$this->extractErrorMessages($errors).
``` DrevOps.TestingPractices.DataProviderPrefix DrevOps.TestingPractices.DataProviderMatchesTestName DrevOps.TestingPractices.DataProviderOrder ```
7c3e2d3 to
1ebe62a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (3)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
173-205: Missing support for PHP 8 attributes in provider discovery.The
findDataProviders()method only searches for@dataProviderannotations in docblocks but ignores PHP 8's#[DataProvider(...)]attributes. This means providers referenced via attributes will not be validated by this sniff, creating an incomplete enforcement of the naming convention.Several fixtures in this PR use attribute syntax (e.g.,
DataProviderMatchingValid.phplines 81, 101;DataProviderOrderValid.phpline 66), highlighting the importance of attribute support.Based on learnings
266-290: Fixer doesn't update attribute references.When
fixProviderName()renames a provider method, it only updates@dataProviderannotations but leaves#[DataProvider('oldName')]attributes unchanged. This creates a mismatch where attributes still reference the old method name, breaking the test suite at runtime.Based on learnings
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (1)
162-278: Provider discovery logic duplicated with DataProviderOrderSniff
findDataProviderAnnotation()andfindDataProviderAttribute()here are effectively the same as inDataProviderOrderSniff, which means any future change in how providers are discovered (additional attributes, proximity rules, external‑provider handling) must be applied in two places.If you expect these sniffs to evolve, consider extracting a shared helper (e.g. a trait or utility class) that encapsulates provider resolution and is reused by both sniffs, keeping behavior in sync.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (16)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php(1 hunks)src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php(1 hunks)src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php(1 hunks)src/DrevOps/ruleset.xml(1 hunks)tests/Fixtures/DataProviderInvalid.php(1 hunks)tests/Fixtures/DataProviderMatchingInvalid.php(1 hunks)tests/Fixtures/DataProviderMatchingValid.php(1 hunks)tests/Fixtures/DataProviderOrderInvalid.php(1 hunks)tests/Fixtures/DataProviderOrderValid.php(1 hunks)tests/Fixtures/DataProviderValid.php(1 hunks)tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php(1 hunks)tests/Functional/DataProviderOrderSniffFunctionalTest.php(1 hunks)tests/Functional/DataProviderPrefixSniffFunctionalTest.php(1 hunks)tests/Unit/DataProviderMatchesTestNameSniffTest.php(1 hunks)tests/Unit/DataProviderOrderSniffTest.php(1 hunks)tests/Unit/DataProviderPrefixSniffTest.php(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
tests/Fixtures/DataProviderMatchingValid.php (3)
tests/Fixtures/DataProviderMatchingInvalid.php (2)
DataProvider(81-84)DataProvider(101-104)tests/Fixtures/DataProviderOrderInvalid.php (1)
DataProvider(70-73)tests/Fixtures/DataProviderOrderValid.php (1)
DataProvider(66-69)
tests/Fixtures/DataProviderValid.php (1)
tests/Fixtures/DataProviderInvalid.php (2)
testSomething(19-21)testAnother(40-42)
tests/Functional/DataProviderPrefixSniffFunctionalTest.php (1)
tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
tests/Fixtures/DataProviderInvalid.php (1)
tests/Fixtures/DataProviderValid.php (2)
testSomething(19-21)testAnother(40-42)
tests/Fixtures/DataProviderOrderInvalid.php (1)
tests/Fixtures/DataProviderOrderValid.php (6)
DataProvider(66-69)dataProviderUserLogin(29-34)testUserLogin(20-22)dataProviderEmailValidation(57-61)testEmailValidation(41-43)dataProviderAuthentication(76-80)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (4)
process(56-81)isTestClass(94-124)findDataProviderAnnotation(314-360)findDataProviderAttribute(373-419)src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
process(58-111)isTestClass(124-162)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php (1)
tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
tests/Functional/DataProviderOrderSniffFunctionalTest.php (2)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php (2)
CoversNothing(13-72)Group(21-24)tests/Functional/FunctionalTestCase.php (2)
FunctionalTestCase(17-142)runPhpcs(62-87)
tests/Fixtures/DataProviderMatchingInvalid.php (1)
tests/Fixtures/DataProviderMatchingValid.php (2)
DataProvider(81-84)DataProvider(101-104)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (1)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (3)
isTestClass(95-133)findDataProviderAnnotation(173-219)findDataProviderAttribute(232-278)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (3)
register(35-37)process(42-82)isTestClass(95-133)src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (3)
register(49-51)process(56-81)isTestClass(94-124)
tests/Unit/DataProviderPrefixSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php (2)
DataProviderPrefixSniff(17-292)register(51-53)tests/Unit/UnitTestCase.php (3)
UnitTestCase(18-152)processCode(46-53)findFunctionToken(85-93)
tests/Unit/DataProviderOrderSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php (2)
DataProviderOrderSniff(27-421)register(49-51)tests/Unit/UnitTestCase.php (4)
UnitTestCase(18-152)processCode(46-53)findClassToken(104-112)findFunctionToken(85-93)
tests/Unit/DataProviderMatchesTestNameSniffTest.php (2)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php (2)
DataProviderMatchesTestNameSniff(25-296)register(35-37)tests/Unit/UnitTestCase.php (3)
UnitTestCase(18-152)processCode(46-53)findFunctionToken(85-93)
🪛 PHPMD (2.15.0)
tests/Fixtures/DataProviderMatchingValid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
71-76: The method AuthenticationScenarios is not named in camelCase. (undefined)
(CamelCaseMethodName)
111-116: The method TokenGeneration is not named in camelCase. (undefined)
(CamelCaseMethodName)
tests/Fixtures/DataProviderOrderInvalid.php
32-32: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php
49-49: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
55-55: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
63-63: The variable $test_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
66-66: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
99-99: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
105-105: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
111-111: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
120-120: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
122-122: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
124-124: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
177-177: The variable $comment_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
182-182: The variable $comment_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
200-200: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
206-206: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $attribute_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
248-248: The variable $name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
255-255: The variable $open_paren is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
269-269: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php
14-72: Avoid excessively long class names like DataProviderMatchesTestNameSniffFunctionalTest. Keep class name length under 40. (undefined)
(LongClassName)
tests/Fixtures/DataProviderMatchingInvalid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
111-116: The method TokenGenerationCases is not named in camelCase. (undefined)
(CamelCaseMethodName)
src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php
27-421: The class DataProviderOrderSniff has an overall complexity of 50 which is very high. The configured complexity threshold is 50. (undefined)
(ExcessiveClassComplexity)
65-65: The variable $class_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
66-66: The variable $class_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
98-98: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
104-104: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
112-112: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
114-114: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
116-116: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
145-145: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
148-148: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
154-154: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
162-162: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
173-173: The variable $test_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
202-202: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
205-205: The variable $method_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
211-211: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
230-301: The method validateOrder() has a Cyclomatic Complexity of 14. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
230-301: The method validateOrder() has an NPath complexity of 361. The configured NPath complexity threshold is 200. (undefined)
(NPathComplexity)
232-232: The variable $checked_providers is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_info is not named in camelCase. (undefined)
(CamelCaseVariableName)
235-235: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $test_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
258-258: The variable $provider_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
261-261: The variable $has_violation is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $error_code is not named in camelCase. (undefined)
(CamelCaseVariableName)
263-263: The variable $error_message is not named in camelCase. (undefined)
(CamelCaseVariableName)
288-288: The variable $error_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
318-318: The variable $comment_end is not named in camelCase. (undefined)
(CamelCaseVariableName)
323-323: The variable $comment_start is not named in camelCase. (undefined)
(CamelCaseVariableName)
341-341: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
347-347: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
377-377: The variable $attribute_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
389-389: The variable $name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
396-396: The variable $open_paren is not named in camelCase. (undefined)
(CamelCaseVariableName)
403-403: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
410-410: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
src/DrevOps/Sniffs/TestingPractices/DataProviderPrefixSniff.php
78-78: The variable $function_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
86-86: The variable $function_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
99-99: The variable $suggested_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
128-128: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
134-134: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
142-142: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
150-150: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
152-152: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
154-154: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
188-188: The variable $string_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
193-193: The variable $method_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
232-232: The variable $common_prefixes is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $common_prefix is not named in camelCase. (undefined)
(CamelCaseVariableName)
266-290: The method fixProviderName() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
274-274: The variable $prev_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
284-284: The variable $prev_tag is not named in camelCase. (undefined)
(CamelCaseVariableName)
tests/Fixtures/DataProviderOrderValid.php
20-20: Avoid unused parameters such as '$pass'. (undefined)
(UnusedFormalParameter)
48-50: Avoid unused private methods such as 'helperValidate'. (undefined)
(UnusedPrivateMethod)
tests/Unit/DataProviderPrefixSniffTest.php
15-574: The class DataProviderPrefixSniffTest has 25 public methods. Consider refactoring DataProviderPrefixSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
54-54: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
56-56: Missing class import via use statement (line '56', column '23'). (undefined)
(MissingImport)
76-76: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
78-78: Missing class import via use statement (line '78', column '23'). (undefined)
(MissingImport)
99-99: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
101-101: Missing class import via use statement (line '101', column '23'). (undefined)
(MissingImport)
121-121: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
123-123: Missing class import via use statement (line '123', column '23'). (undefined)
(MissingImport)
160-160: Missing class import via use statement (line '160', column '23'). (undefined)
(MissingImport)
194-194: Missing class import via use statement (line '194', column '23'). (undefined)
(MissingImport)
210-210: Missing class import via use statement (line '210', column '23'). (undefined)
(MissingImport)
222-222: Missing class import via use statement (line '222', column '23'). (undefined)
(MissingImport)
236-236: Missing class import via use statement (line '236', column '23'). (undefined)
(MissingImport)
248-248: Missing class import via use statement (line '248', column '23'). (undefined)
(MissingImport)
260-260: Missing class import via use statement (line '260', column '23'). (undefined)
(MissingImport)
272-272: Missing class import via use statement (line '272', column '23'). (undefined)
(MissingImport)
284-284: Missing class import via use statement (line '284', column '23'). (undefined)
(MissingImport)
296-296: Missing class import via use statement (line '296', column '23'). (undefined)
(MissingImport)
354-354: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
355-355: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
356-356: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
427-427: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
428-428: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
429-429: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
473-473: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
475-475: Missing class import via use statement (line '475', column '23'). (undefined)
(MissingImport)
501-501: Missing class import via use statement (line '501', column '23'). (undefined)
(MissingImport)
526-526: Missing class import via use statement (line '526', column '23'). (undefined)
(MissingImport)
539-539: Missing class import via use statement (line '539', column '23'). (undefined)
(MissingImport)
tests/Unit/DataProviderOrderSniffTest.php
15-524: The class DataProviderOrderSniffTest has 22 public methods. Consider refactoring DataProviderOrderSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
89-89: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
90-90: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
91-91: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
237-237: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
239-239: Missing class import via use statement (line '239', column '23'). (undefined)
(MissingImport)
259-259: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
261-261: Missing class import via use statement (line '261', column '23'). (undefined)
(MissingImport)
281-281: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
283-283: Missing class import via use statement (line '283', column '23'). (undefined)
(MissingImport)
303-303: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
305-305: Missing class import via use statement (line '305', column '23'). (undefined)
(MissingImport)
341-341: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
343-343: Missing class import via use statement (line '343', column '23'). (undefined)
(MissingImport)
368-368: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
370-370: Missing class import via use statement (line '370', column '23'). (undefined)
(MissingImport)
393-393: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
395-395: Missing class import via use statement (line '395', column '23'). (undefined)
(MissingImport)
421-421: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
423-423: Missing class import via use statement (line '423', column '23'). (undefined)
(MissingImport)
445-445: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
447-447: Missing class import via use statement (line '447', column '23'). (undefined)
(MissingImport)
468-468: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
470-470: Missing class import via use statement (line '470', column '23'). (undefined)
(MissingImport)
491-491: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
493-493: Missing class import via use statement (line '493', column '23'). (undefined)
(MissingImport)
514-514: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
516-516: Missing class import via use statement (line '516', column '23'). (undefined)
(MissingImport)
tests/Unit/DataProviderMatchesTestNameSniffTest.php
15-649: The class DataProviderMatchesTestNameSniffTest has 28 non-getter- and setter-methods. Consider refactoring DataProviderMatchesTestNameSniffTest to keep number of methods under 25. (undefined)
(TooManyMethods)
15-649: The class DataProviderMatchesTestNameSniffTest has 28 public methods. Consider refactoring DataProviderMatchesTestNameSniffTest to keep number of public methods under 10. (undefined)
(TooManyPublicMethods)
46-46: Missing class import via use statement (line '46', column '23'). (undefined)
(MissingImport)
58-58: Missing class import via use statement (line '58', column '23'). (undefined)
(MissingImport)
76-76: Missing class import via use statement (line '76', column '23'). (undefined)
(MissingImport)
91-91: Missing class import via use statement (line '91', column '23'). (undefined)
(MissingImport)
109-109: Missing class import via use statement (line '109', column '23'). (undefined)
(MissingImport)
138-138: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
140-140: Missing class import via use statement (line '140', column '23'). (undefined)
(MissingImport)
163-163: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
165-165: Missing class import via use statement (line '165', column '23'). (undefined)
(MissingImport)
185-185: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
187-187: Missing class import via use statement (line '187', column '23'). (undefined)
(MissingImport)
237-237: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
238-238: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
239-239: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
302-302: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
304-304: Missing class import via use statement (line '304', column '23'). (undefined)
(MissingImport)
324-324: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
326-326: Missing class import via use statement (line '326', column '23'). (undefined)
(MissingImport)
346-346: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
348-348: Missing class import via use statement (line '348', column '23'). (undefined)
(MissingImport)
369-369: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
371-371: Missing class import via use statement (line '371', column '23'). (undefined)
(MissingImport)
396-396: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
398-398: Missing class import via use statement (line '398', column '23'). (undefined)
(MissingImport)
421-421: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
423-423: Missing class import via use statement (line '423', column '23'). (undefined)
(MissingImport)
444-444: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
446-446: Missing class import via use statement (line '446', column '23'). (undefined)
(MissingImport)
466-466: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
468-468: Missing class import via use statement (line '468', column '23'). (undefined)
(MissingImport)
494-494: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
496-496: Missing class import via use statement (line '496', column '23'). (undefined)
(MissingImport)
518-518: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
520-520: Missing class import via use statement (line '520', column '23'). (undefined)
(MissingImport)
541-541: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
543-543: Missing class import via use statement (line '543', column '23'). (undefined)
(MissingImport)
564-564: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
566-566: Missing class import via use statement (line '566', column '23'). (undefined)
(MissingImport)
587-587: The variable $function_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
589-589: Missing class import via use statement (line '589', column '23'). (undefined)
(MissingImport)
635-635: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
636-636: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
637-637: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
🔇 Additional comments (13)
src/DrevOps/ruleset.xml (1)
10-26: LGTM! Well-structured ruleset configuration.The three new rules are properly configured with sensible defaults:
dataProviderprefix andafterpositioning for providers. The XML structure follows PHPCS conventions correctly.tests/Functional/DataProviderOrderSniffFunctionalTest.php (1)
1-62: LGTM! Comprehensive functional test coverage.The functional test properly exercises smoke, valid, and invalid ordering scenarios. The test structure follows the established pattern and includes detailed violation assertions with exact error messages and sources.
tests/Fixtures/DataProviderValid.php (1)
1-86: LGTM! Well-designed fixture demonstrating valid patterns.The fixture comprehensively demonstrates valid data provider usage including the
dataProviderprefix convention, proper typing, and shared provider scenarios. This provides good coverage for testing the sniffs.tests/Fixtures/DataProviderOrderValid.php (1)
1-82: LGTM! Fixture correctly demonstrates valid provider ordering.The fixture properly illustrates that providers should appear after their tests and that helper methods between them are acceptable. The use of both annotation (
@dataProvider) and attribute (#[DataProvider]) syntax is important for comprehensive testing.Note: The PHPMD warnings about unused parameters and methods are expected for test fixtures and can be safely ignored.
tests/Fixtures/DataProviderMatchingValid.php (1)
1-118: LGTM! Fixture comprehensively covers valid provider/test name matching.The fixture demonstrates valid matching patterns with various prefixes and both annotation/attribute syntax. The intentional use of non-camelCase provider names (
AuthenticationScenarios,TokenGeneration) correctly tests that the matching logic prioritizes name alignment over camelCase conventions.Note: PHPMD warnings are false positives for this test fixture.
tests/Fixtures/DataProviderOrderInvalid.php (1)
1-75: LGTM! Fixture clearly demonstrates ordering violations.The fixture properly illustrates invalid ordering by placing providers before their tests. The clear violation markers in comments and the use of both annotation/attribute syntax provide comprehensive coverage for testing the order enforcement logic.
Note: PHPMD warning about unused parameter is expected for test fixtures.
tests/Functional/DataProviderPrefixSniffFunctionalTest.php (1)
1-75: LGTM! Functional tests provide solid coverage for annotation-based providers.The tests comprehensively validate smoke, valid, and invalid scenarios with detailed violation assertions. The expected violations correctly verify the prefix enforcement and name suggestion logic.
However, these tests currently only cover
@dataProviderannotations. Once attribute support is added to the sniff (as flagged in previous comments), consider adding test cases for#[DataProvider(...)]attributes to ensure comprehensive coverage.tests/Fixtures/DataProviderInvalid.php (1)
14-127: Fixture structure is consistent with prefix-sniff scenariosThe fixture cleanly exercises a variety of non‑
dataProvider*prefixes (none,provide,get, shared provider), which matches the intended negative cases for the prefix sniff without introducing extra complexity. No changes needed.tests/Functional/DataProviderMatchesTestNameSniffFunctionalTest.php (1)
16-69: Functional coverage and expectations look correctThe functional test wires
$sniffSourcecorrectly, exercises both the all‑clear path and concrete failure cases, and asserts full message/source/fixable triplets that match the sniff’s error template and the invalid fixture. This should give good confidence the sniff behaves as intended across docblock and attribute providers.tests/Fixtures/DataProviderMatchingInvalid.php (1)
15-116: Invalid matching scenarios are well‑coveredThis fixture systematically exercises the different ways provider names can fail to match their tests (partial, suffix, completely different, and attribute‑based), which aligns with what the matching sniff is validating. Looks good as a negative test fixture.
tests/Unit/DataProviderPrefixSniffTest.php (1)
25-572: Unit tests give thorough coverage of DataProviderPrefixSniff behaviorThe test class systematically covers class detection, provider discovery (including external references and missing names), prefix validation/customization, name suggestion edge cases, and process() behavior across valid/invalid/irrelevant scenarios. The previous PHPUnit metadata issue around literal
@dataProviderin docblocks is also avoided now. No changes needed.tests/Unit/DataProviderOrderSniffTest.php (1)
1-524: Excellent comprehensive test coverage!The test suite thoroughly validates DataProviderOrderSniff behavior across numerous scenarios: correct/incorrect ordering, helper methods, shared providers, external providers, PHP 8 attributes, class detection, and edge cases for annotation/attribute parsing. The use of reflection to test private methods is appropriate for unit testing.
tests/Unit/DataProviderMatchesTestNameSniffTest.php (1)
1-649: Excellent comprehensive test coverage!The test suite provides thorough validation of DataProviderMatchesTestNameSniff across 28 test methods, covering registration, test method detection, name extraction, provider matching logic, annotation/attribute parsing, process flow scenarios, class detection, and numerous edge cases. The structure mirrors DataProviderOrderSniffTest appropriately.
Note: Past review comments have already identified helpful maintainability improvements (ReflectionClass import, error message extraction helper). These suggestions remain valid and would benefit both test files.
| /** | ||
| * Determines if the current file contains a test class. | ||
| * | ||
| * @param \PHP_CodeSniffer\Files\File $phpcsFile | ||
| * The file being scanned. | ||
| * @param int $stackPtr | ||
| * The position of the current token. | ||
| * | ||
| * @return bool | ||
| * TRUE if the file contains a test class, FALSE otherwise. | ||
| */ | ||
| private function isTestClass(File $phpcsFile, int $stackPtr): bool { | ||
| $tokens = $phpcsFile->getTokens(); | ||
|
|
||
| // Find the class token. | ||
| $class_ptr = $phpcsFile->findPrevious(T_CLASS, $stackPtr); | ||
| if ($class_ptr === FALSE) { | ||
| return FALSE; | ||
| } | ||
|
|
||
| // Get the class name. | ||
| $class_name_ptr = $phpcsFile->findNext(T_STRING, $class_ptr + 1, $class_ptr + 3); | ||
| // @codeCoverageIgnoreStart | ||
| if ($class_name_ptr === FALSE) { | ||
| return FALSE; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| $class_name = $tokens[$class_name_ptr]['content']; | ||
|
|
||
| // Check if class name ends with Test or TestCase. | ||
| if (preg_match('/Test(Case)?$/', $class_name) === 1) { | ||
| return TRUE; | ||
| } | ||
|
|
||
| // Check if class extends TestCase or similar. | ||
| // @codeCoverageIgnoreStart | ||
| $extends_ptr = $phpcsFile->findNext(T_EXTENDS, $class_ptr + 1, $tokens[$class_ptr]['scope_opener']); | ||
| if ($extends_ptr !== FALSE) { | ||
| $parent_class_ptr = $phpcsFile->findNext(T_STRING, $extends_ptr + 1, $tokens[$class_ptr]['scope_opener']); | ||
| if ($parent_class_ptr !== FALSE) { | ||
| $parent_class = $tokens[$parent_class_ptr]['content']; | ||
| if (preg_match('/TestCase$/', $parent_class) === 1) { | ||
| return TRUE; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return FALSE; | ||
| // @codeCoverageIgnoreEnd | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isTestClass() can misclassify global test‑style functions as being in a test class
isTestClass() finds the nearest preceding T_CLASS but never checks that the current T_FUNCTION actually lies within that class’s scope. In a file that defines a test class followed by a global function like function testSomething() {}, isTestClass() will return true, so process() will treat that global function as a test method and enforce provider naming on it.
If your intention is to limit this sniff strictly to methods of test classes, consider tightening isTestClass() to ensure $stackPtr is inside the class’s scope_opener/scope_closer range before checking name/extends, or reusing the more robust implementation already used in DataProviderPrefixSniff.
🧰 Tools
🪛 PHPMD (2.15.0)
99-99: The variable $class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
105-105: The variable $class_name_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
111-111: The variable $class_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
120-120: The variable $extends_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
122-122: The variable $parent_class_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
124-124: The variable $parent_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
🤖 Prompt for AI Agents
In src/DrevOps/Sniffs/TestingPractices/DataProviderMatchesTestNameSniff.php
around lines 84 to 133, isTestClass() currently finds the nearest preceding
T_CLASS but doesn't verify that the current token ($stackPtr) is inside that
class's scope, which causes global test-style functions after a class to be
misclassified; fix by ensuring the located class actually encloses $stackPtr:
after finding $class_ptr, check that the class has 'scope_opener' and
'scope_closer' in $tokens[$class_ptr] and return FALSE if they are missing or if
$stackPtr is not strictly between scope_opener and scope_closer, then proceed
with the existing name/extends checks (or, alternatively, walk previous classes
until you find one that contains $stackPtr or return FALSE if none do).
| private function validateOrder(File $phpcsFile, array $tests, array $providers): void { | ||
| // Track which providers we've already checked. | ||
| $checked_providers = []; | ||
|
|
||
| foreach ($tests as $test_name => $test_info) { | ||
| $provider_name = $test_info['provider']; | ||
| $test_line = $test_info['line']; | ||
|
|
||
| // Ensure provider name is a string. | ||
| // @codeCoverageIgnoreStart | ||
| if (!is_string($provider_name)) { | ||
| continue; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| // Skip if provider already checked (shared provider). | ||
| if (isset($checked_providers[$provider_name])) { | ||
| continue; | ||
| } | ||
|
|
||
| // Mark as checked. | ||
| $checked_providers[$provider_name] = TRUE; | ||
|
|
||
| // Check if provider exists in this class. | ||
| // @codeCoverageIgnoreStart | ||
| if (!isset($providers[$provider_name])) { | ||
| continue; | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| $provider_line = $providers[$provider_name]; | ||
|
|
||
| // Validate ordering based on configuration. | ||
| $has_violation = FALSE; | ||
| $error_code = ''; | ||
| $error_message = ''; | ||
|
|
||
| if ($this->providerPosition === 'after') { | ||
| // Provider should be after test. | ||
| if ($provider_line < $test_line) { | ||
| $has_violation = TRUE; | ||
| $error_code = self::CODE_PROVIDER_BEFORE_TEST; | ||
| $error_message = 'Data provider method "%s" (line %d) appears before test method "%s" (line %d). Providers should be defined after their test methods'; | ||
| } | ||
| } | ||
| // @codeCoverageIgnoreStart | ||
| elseif ($this->providerPosition === 'before') { | ||
| // Provider should be before test. | ||
| if ($provider_line > $test_line) { | ||
| $has_violation = TRUE; | ||
| $error_code = self::CODE_PROVIDER_AFTER_TEST; | ||
| $error_message = 'Data provider method "%s" (line %d) appears after test method "%s" (line %d). Providers should be defined before their test methods'; | ||
| } | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
| if ($has_violation) { | ||
| $data = [$provider_name, $provider_line, $test_name, $test_line]; | ||
|
|
||
| // Find the provider function token to report error at. | ||
| $tokens = $phpcsFile->getTokens(); | ||
| $error_ptr = NULL; | ||
| foreach ($tokens as $ptr => $token) { | ||
| if ($token['line'] === $provider_line && $token['code'] === T_STRING) { | ||
| $error_ptr = (int) $ptr; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if ($error_ptr !== NULL) { | ||
| $phpcsFile->addError($error_message, $error_ptr, $error_code, $data); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Tighten error location to the actual provider method name
In validateOrder(), the loop that finds $error_ptr only checks for T_STRING tokens on $provider_line and uses the first match it encounters. On lines that contain other identifiers (e.g., type hints or return types), the error can end up attached to the wrong symbol even though the message mentions the provider.
Consider also checking the token content before assigning $error_ptr, for example:
- foreach ($tokens as $ptr => $token) {
- if ($token['line'] === $provider_line && $token['code'] === T_STRING) {
- $error_ptr = (int) $ptr;
- break;
- }
- }
+ foreach ($tokens as $ptr => $token) {
+ if ($token['line'] === $provider_line
+ && $token['code'] === T_STRING
+ && $token['content'] === $provider_name
+ ) {
+ $error_ptr = (int) $ptr;
+ break;
+ }
+ }This keeps the error anchored to the provider method itself and avoids surprising highlights when additional identifiers are present on the same line.
🧰 Tools
🪛 PHPMD (2.15.0)
230-301: The method validateOrder() has a Cyclomatic Complexity of 14. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
230-301: The method validateOrder() has an NPath complexity of 361. The configured NPath complexity threshold is 200. (undefined)
(NPathComplexity)
232-232: The variable $checked_providers is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
234-234: The variable $test_info is not named in camelCase. (undefined)
(CamelCaseVariableName)
235-235: The variable $provider_name is not named in camelCase. (undefined)
(CamelCaseVariableName)
236-236: The variable $test_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
258-258: The variable $provider_line is not named in camelCase. (undefined)
(CamelCaseVariableName)
261-261: The variable $has_violation is not named in camelCase. (undefined)
(CamelCaseVariableName)
262-262: The variable $error_code is not named in camelCase. (undefined)
(CamelCaseVariableName)
263-263: The variable $error_message is not named in camelCase. (undefined)
(CamelCaseVariableName)
288-288: The variable $error_ptr is not named in camelCase. (undefined)
(CamelCaseVariableName)
🤖 Prompt for AI Agents
In src/DrevOps/Sniffs/TestingPractices/DataProviderOrderSniff.php around lines
230-301, the code finds an error pointer by picking the first T_STRING on the
provider line which can be a type or other identifier; change the search to
ensure the token content matches the provider method name and that it is the
function name (e.g. verify the token's content equals $provider_name and confirm
it is the function identifier by checking the nearest preceding significant
token is T_FUNCTION or by locating the T_FUNCTION token and taking its following
T_STRING). Use that matched pointer as $error_ptr so the reported error anchors
to the actual provider method name.
| $error_messages = []; | ||
| foreach ($errors as $line_errors) { | ||
| foreach ($line_errors as $column_errors) { | ||
| foreach ($column_errors as $error) { | ||
| $error_messages[] = $error['message']; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Extract duplicated error message extraction logic.
This nested loop pattern for extracting error messages from PHPCS errors appears in multiple places (here and in DataProviderMatchesTestNameSniffTest). Consider extracting this into a helper method in the base UnitTestCase class for reuse across test files.
Example helper method for UnitTestCase:
/**
* Extracts error messages from PHPCS file errors.
*
* @param array $errors
* The errors array from $file->getErrors().
*
* @return array
* Flat array of error messages.
*/
protected function extractErrorMessages(array $errors): array {
$error_messages = [];
foreach ($errors as $line_errors) {
foreach ($line_errors as $column_errors) {
foreach ($column_errors as $error) {
$error_messages[] = $error['message'];
}
}
}
return $error_messages;
}Then replace usage:
- $error_messages = [];
- foreach ($errors as $line_errors) {
- foreach ($line_errors as $column_errors) {
- foreach ($column_errors as $error) {
- $error_messages[] = $error['message'];
- }
- }
- }
+ $error_messages = $this->extractErrorMessages($errors);🧰 Tools
🪛 PHPMD (2.15.0)
89-89: The variable $error_messages is not named in camelCase. (undefined)
(CamelCaseVariableName)
90-90: The variable $line_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
91-91: The variable $column_errors is not named in camelCase. (undefined)
(CamelCaseVariableName)
🤖 Prompt for AI Agents
In tests/Unit/DataProviderOrderSniffTest.php around lines 89 to 96, duplicated
nested loops are used to extract PHPCS error messages; add a protected helper
method extractErrorMessages(array $errors): array to the base UnitTestCase that
flattens the $file->getErrors() structure into a simple array of message
strings, then replace the three-level foreach block in this file (and other
tests like DataProviderMatchesTestNameSniffTest) with a single call to
$this->extractErrorMessages($errors) to return the flat array.
| $file = $this->processCode($code); | ||
| $class_ptr = $this->findClassToken($file); | ||
|
|
||
| $reflection = new \ReflectionClass($this->sniff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider importing ReflectionClass.
\ReflectionClass is used frequently throughout this test file. Adding it to the imports at the top would improve code readability and consistency.
Add to imports:
use DrevOps\Sniffs\TestingPractices\DataProviderOrderSniff;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\Attributes\CoversClass;
+use ReflectionClass;Then replace all instances:
- $reflection = new \ReflectionClass($this->sniff);
+ $reflection = new ReflectionClass($this->sniff);Also applies to: 261, 283, 305, 343, 370, 395, 423, 447, 470, 493, 516
🧰 Tools
🪛 PHPMD (2.15.0)
239-239: Missing class import via use statement (line '239', column '23'). (undefined)
(MissingImport)
🤖 Prompt for AI Agents
In tests/Unit/DataProviderOrderSniffTest.php around line 239 (and also at lines
261, 283, 305, 343, 370, 395, 423, 447, 470, 493, 516), the test repeatedly uses
the global class reference \ReflectionClass; add a use statement for
ReflectionClass at the top of the file and then replace each occurrence of
\ReflectionClass with the imported ReflectionClass to improve readability and
consistency across the file.
DrevOps.TestingPractices.DataProviderPrefix
Purpose
Enforces naming conventions for PHPUnit data provider methods. This sniff ensures that data provider methods start with a configurable prefix (default:
dataProvider). It provides auto-fixing capabilities to rename methods and update all references.Configuration
dataProvider)Examples
✓ Valid:
✗ Invalid:
DrevOps.TestingPractices.DataProviderMatchesTestName
Purpose
Enforces that data provider method names match the test method names they serve. The provider name must end with the exact test name (after removing the "test" prefix). This improves code readability and maintains a clear relationship between tests and their data providers.
Configuration
None
Examples
✓ Valid:
✗ Invalid:
DrevOps.TestingPractices.DataProviderOrder
Purpose
Enforces ordering of data provider and test methods in test files. This ensures structural organization and improves code readability. By default, data providers should be defined after their test methods. Helper methods between tests and providers are allowed.
Configuration
after)after: Data providers should appear after their test methods (default)before: Data providers should appear before their test methodsExamples
✓ Valid (providerPosition="after"):
✗ Invalid (providerPosition="after"):
Note: All three rules:
@dataProviderannotations and PHP 8#[DataProvider]attributesClassName::methodNameTestorTestCase, or extendingTestCase)