Skip to content

feat(phpstan): add OneThingPerLineRule#73

Open
simbig wants to merge 6 commits intomasterfrom
one-thing-per-line-phpstan-rule
Open

feat(phpstan): add OneThingPerLineRule#73
simbig wants to merge 6 commits intomasterfrom
one-thing-per-line-phpstan-rule

Conversation

@simbig
Copy link
Contributor

@simbig simbig commented Feb 18, 2026

Summary

  • Adds OneThingPerLineRule PHPStan rule that detects method chains where multiple ->method() or ?->method() calls appear on the same line
  • Each call in a chain must be on its own line for readability
  • Not registered in rules.neon — intended to be enabled per-module in consumer projects (like limes-api)

Rule details

Identifier: mll.oneThingPerLine

Test plan

  • make passes (rector, php-cs-fixer, phpstan, phpunit)
  • Integration test covers violations with expected line numbers
  • Integration test covers correct code with zero false positives
  • Edge cases: null-safe chains, mixed ?->/->, multiline args, chains inside closures, chains as arguments

🤖 Generated with Claude Code

Simon Bigelmayr and others added 4 commits February 23, 2026 14:02
Detects method chains where multiple ->method() or ?->method() calls
appear on the same line. Each call in a chain must be on its own line.

- Registers on CallLike, handles both MethodCall and NullsafeMethodCall
- Compares method name start lines to correctly handle multiline expressions
- Reports errors at the method name line via RuleErrorBuilder::line()
- Not registered in rules.neon — enabled per-module in consumer projects

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split nullsafe operator (?->) test fixtures into separate files
conditionally loaded on PHP >= 8.0, since PHP 7.4 cannot parse
that syntax. Add return type ignore for PHPStan 1.x compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow method chains inside string interpolation, skipping error detection for such cases. Adds caching of file contents for efficient parsing and extends the node type handling in the rule.
@simbig simbig force-pushed the one-thing-per-line-phpstan-rule branch from f9d1643 to b5c5ea0 Compare February 23, 2026 13:02
…ineRule

A method call without arguments (e.g. ->relation()) is treated like
a property access and does not count as "a thing". This allows
patterns like $item->magnaPure()->associate($x) on a single line.

Also excludes method chains inside string interpolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@simbig simbig force-pushed the one-thing-per-line-phpstan-rule branch from b5c5ea0 to 9175bf9 Compare February 23, 2026 13:06
@simbig simbig requested a review from spawnia February 23, 2026 13:07
The previous cleanup accidentally removed the php-below-8.0.neon
inclusion and the OneThingPerLineRule return type ignore for
older PHPStan versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

public function getNodeType(): string
{
return CallLike::class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eine lange Kette von Property-Zugriffen würde für mich auch unter OneThingPerLine fallen, ist aber nicht CallLike. PHPStan Regeln gehen aber immer nur auf einen NodeType, oder? Daher würde ich den Klassennamen spezifischer wählen. Den Identifier mll.oneThingPerLine finde ich in Ordnung, den könnten wir in mehreren Klassen mit verschiedener Message verwenden.

Comment on lines +76 to +83
$startPos = $root->getStartFilePos();
if ($startPos <= 0) {
return false;
}

$this->fileContentsCache[$file] ??= file_get_contents($file);

return $this->fileContentsCache[$file][$startPos - 1] === '{';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Können wir nochmal evaluieren ob das anders geht, auf AST Ebene? Mir fallen einige Edge-Cases ein in denen das so implementiert nicht passt:

if ($cond) {$bar = 123;} // false-positive
return "bar is $bar"; // false-negative

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants