Skip to content

Commit d6c5f35

Browse files
JanTvrdikclaude
andcommitted
Extract RuleTestCase from shipmonk/phpstan-rules
- Extract RuleTestCase class from tests/ to src/ with ShipMonk\PHPStanDev namespace - Create composer package shipmonk/phpstan-dev for PHPStan rule testing utilities - Copy and adapt QA tools configuration (PHPCS, PHPStan, EditorConfig, etc.) - Add comprehensive README with usage examples and development workflow - Set up GitHub Actions CI workflows - Configure git attributes for proper package exports 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 17918c4 commit d6c5f35

File tree

14 files changed

+4394
-0
lines changed

14 files changed

+4394
-0
lines changed

.editorconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[*]
2+
charset = utf-8
3+
end_of_line = lf
4+
trim_trailing_whitespace = true
5+
insert_final_newline = true
6+
indent_style = space
7+
indent_size = 4

.gitattributes

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
* text=auto
2+
3+
/cache export-ignore
4+
/.editorconfig export-ignore
5+
/.gitattributes export-ignore
6+
/.github export-ignore
7+
/.gitignore export-ignore
8+
/temp-clone export-ignore
9+
/composer-dependency-analyser.php export-ignore
10+
/phpcs.xml.dist export-ignore
11+
/phpstan.neon.dist export-ignore
12+
/phpunit.xml.dist export-ignore

.github/workflows/checks.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Checks
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- "master"
7+
- "v[0-9]"
8+
jobs:
9+
checks:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: false
13+
steps:
14+
-
15+
name: Checkout code
16+
uses: actions/checkout@v4
17+
-
18+
name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: 8.4
22+
-
23+
name: Install dependencies
24+
run: composer install --no-progress --prefer-dist --no-interaction
25+
26+
-
27+
name: Run checks
28+
run: composer check

.github/workflows/lint.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: phpcs.xml lint
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- "master"
7+
- "v[0-9]"
8+
9+
jobs:
10+
xml-linter:
11+
runs-on: ubuntu-latest
12+
steps:
13+
-
14+
name: Checkout code
15+
uses: actions/checkout@v3
16+
17+
-
18+
name: Install dependencies
19+
run: composer install --no-progress --no-interaction
20+
21+
-
22+
name: Lint
23+
uses: ChristophWurst/xmllint-action@v1
24+
with:
25+
xml-file: phpcs.xml.dist
26+
xml-schema-file: vendor/squizlabs/php_codesniffer/phpcs.xsd

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/vendor
2+
/cache/*
3+
!/cache/.gitkeep
4+
/.idea
5+
/phpstan.neon

README.md

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
# PHPStan Development Utilities
2+
3+
Development utilities for PHPStan rules testing, extracted from [shipmonk/phpstan-rules](https://github.com/shipmonk-rnd/phpstan-rules).
4+
5+
This package provides the `RuleTestCase` class - an enhanced testing framework specifically designed for testing PHPStan rules with additional validation and convenience features.
6+
7+
## Installation
8+
9+
```bash
10+
composer require --dev shipmonk/phpstan-dev
11+
```
12+
13+
## Quick Start
14+
15+
### 1. Create Your Rule Test Class
16+
17+
```php
18+
<?php declare(strict_types = 1);
19+
20+
namespace YourNamespace;
21+
22+
use ShipMonk\PHPStanDev\RuleTestCase;
23+
use PHPStan\Rules\Rule;
24+
25+
/**
26+
* @extends RuleTestCase<YourRule>
27+
*/
28+
class YourRuleTest extends RuleTestCase
29+
{
30+
protected function getRule(): Rule
31+
{
32+
return new YourRule();
33+
}
34+
35+
public function testRule(): void
36+
{
37+
$this->analyseFile(__DIR__ . '/data/YourRule/code.php');
38+
}
39+
}
40+
```
41+
42+
### 2. Create Test Data File
43+
44+
Create `tests/Rule/data/YourRule/code.php`:
45+
46+
```php
47+
<?php declare(strict_types = 1);
48+
49+
namespace YourRule;
50+
51+
function test() {
52+
$valid = 'This is valid code';
53+
$invalid = something(); // error: Your custom error message here
54+
}
55+
```
56+
57+
### 3. Run Your Test
58+
59+
```bash
60+
vendor/bin/phpunit tests/Rule/YourRuleTest.php
61+
```
62+
63+
## Key Features
64+
65+
### 🎯 Error Comment System
66+
67+
Use `// error: <message>` comments in test files to specify expected errors:
68+
69+
```php
70+
<?php
71+
72+
$validCode = 'No error expected here';
73+
$invalidCode = forbidden(); // error: This is forbidden
74+
$alsoInvalid = another(); // error: Another error message
75+
```
76+
77+
### 🔧 Autofix Mode
78+
79+
During development, automatically generate error comments:
80+
81+
```php
82+
public function testRule(): void
83+
{
84+
// Set to true temporarily to generate error comments
85+
$this->analyseFile(__DIR__ . '/data/code.php', autofix: true);
86+
}
87+
```
88+
89+
**⚠️ Important**: Remove `autofix: true` before committing - tests will fail if autofix is enabled.
90+
91+
### 🛡️ Automatic Error Validation
92+
93+
Every error is automatically validated:
94+
- ✅ Must have an identifier
95+
- ✅ Identifier must start with `'shipmonk.'`
96+
- ✅ Errors are matched to specific line numbers
97+
98+
## Advanced Usage
99+
100+
### Multiple Test Scenarios
101+
102+
```php
103+
class ComplexRuleTest extends RuleTestCase
104+
{
105+
private bool $strictMode = false;
106+
107+
protected function getRule(): Rule
108+
{
109+
return new ComplexRule($this->strictMode);
110+
}
111+
112+
public function testDefault(): void
113+
{
114+
$this->analyseFile(__DIR__ . '/data/ComplexRule/default.php');
115+
}
116+
117+
public function testStrict(): void
118+
{
119+
$this->strictMode = true;
120+
$this->analyseFile(__DIR__ . '/data/ComplexRule/strict.php');
121+
}
122+
}
123+
```
124+
125+
### PHP Version-Specific Tests
126+
127+
```php
128+
public function testPhp82Features(): void
129+
{
130+
$this->phpVersion = $this->createPhpVersion(80_200);
131+
$this->analyseFile(__DIR__ . '/data/Rule/php82-features.php');
132+
}
133+
```
134+
135+
### Custom PHPStan Configuration
136+
137+
Create `tests/Rule/data/YourRule/config.neon`:
138+
139+
```neon
140+
parameters:
141+
customParameter: value
142+
```
143+
144+
Then reference it in your test:
145+
146+
```php
147+
public static function getAdditionalConfigFiles(): array
148+
{
149+
return array_merge(
150+
parent::getAdditionalConfigFiles(),
151+
[__DIR__ . '/data/YourRule/config.neon'],
152+
);
153+
}
154+
```
155+
156+
### Rules with Dependencies
157+
158+
```php
159+
protected function getRule(): Rule
160+
{
161+
$dependency = self::getContainer()->getByType(SomeService::class);
162+
return new RuleWithDependencies($dependency);
163+
}
164+
```
165+
166+
## File Organization
167+
168+
Recommended directory structure:
169+
170+
```
171+
tests/
172+
├── Rule/
173+
│ ├── YourRuleTest.php
174+
│ ├── AnotherRuleTest.php
175+
│ └── data/
176+
│ ├── YourRule/
177+
│ │ ├── code.php # Main test file
178+
│ │ ├── edge-cases.php # Additional scenarios
179+
│ │ └── config.neon # Optional PHPStan config
180+
│ └── AnotherRule/
181+
│ └── code.php
182+
```
183+
184+
## Common Patterns
185+
186+
### Testing Different Error Scenarios
187+
188+
```php
189+
<?php
190+
191+
// Valid usage - no error comment
192+
$valid = getValue();
193+
194+
// Invalid usage with expected error
195+
$invalid = badFunction(); // error: Function badFunction() is not allowed
196+
197+
// Multiple errors on same line
198+
$a = bad1(); $b = bad2(); // error: Function bad1() is not allowed // error: Function bad2() is not allowed
199+
```
200+
201+
### Testing Complex Error Messages
202+
203+
```php
204+
<?php
205+
206+
function test($param) {
207+
// Error with variable content
208+
return $param + null; // error: Null value involved in binary operation: int + null
209+
210+
// Error with specific types
211+
$array['key'] = $value; // error: Unsafe array key access, key type: mixed
212+
}
213+
```
214+
215+
## Error Comment Rules
216+
217+
1. **Format**: `// error: <message>`
218+
2. **Placement**: At the end of the line that should trigger the error
219+
3. **Multiple errors**: Use separate comments: `// error: First // error: Second`
220+
4. **No error expected**: Don't add any comment
221+
5. **Exact matching**: Error message must match exactly (whitespace sensitive)
222+
223+
## Development Workflow
224+
225+
### 1. Write the Rule
226+
227+
```php
228+
class YourRule implements Rule
229+
{
230+
public function getNodeType(): string
231+
{
232+
return Node\Expr\FuncCall::class;
233+
}
234+
235+
public function processNode(Node $node, Scope $scope): array
236+
{
237+
// Your rule logic
238+
return [
239+
RuleErrorBuilder::message('Error message')
240+
->identifier('shipmonk.yourRule.errorType')
241+
->build(),
242+
];
243+
}
244+
}
245+
```
246+
247+
### 2. Create Test with Autofix
248+
249+
```php
250+
public function testRule(): void
251+
{
252+
$this->analyseFile(__DIR__ . '/data/code.php', autofix: true);
253+
}
254+
```
255+
256+
### 3. Run Test to Generate Comments
257+
258+
```bash
259+
vendor/bin/phpunit tests/Rule/YourRuleTest.php
260+
```
261+
262+
### 4. Review Generated Comments
263+
264+
Check the test data file - error comments will be automatically added.
265+
266+
### 5. Remove Autofix and Commit
267+
268+
```php
269+
public function testRule(): void
270+
{
271+
$this->analyseFile(__DIR__ . '/data/code.php'); // autofix: true removed
272+
}
273+
```
274+
275+
## Development
276+
277+
```bash
278+
# Install dependencies
279+
composer install
280+
281+
# Run all checks
282+
composer check
283+
284+
# Individual checks
285+
composer check:composer # Validate composer.json
286+
composer check:ec # Check EditorConfig compliance
287+
composer check:cs # Check coding standards (PHPCS)
288+
composer check:types # Run PHPStan analysis
289+
composer check:dependencies # Analyze dependencies
290+
composer check:collisions # Check for name collisions
291+
292+
# Fix coding standards
293+
composer fix:cs
294+
```
295+
296+
## Requirements
297+
298+
- PHP 7.4 or higher
299+
- PHPStan 2.1.8 or higher
300+
- PHPUnit 9.6.22 or higher (for testing)
301+
302+
## License
303+
304+
MIT License - see [LICENSE](LICENSE) file.

cache/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)