Skip to content

Stop the function-name sniffs rewriting attribute names - #78

Merged
dereuromark merged 1 commit into
masterfrom
attribute-false-positives
Aug 6, 2026
Merged

Stop the function-name sniffs rewriting attribute names#78
dereuromark merged 1 commit into
masterfrom
attribute-false-positives

Conversation

@dereuromark

Copy link
Copy Markdown
Contributor

The five function-name sniffs rewrote PHP attribute names into function calls.

class Foo
{
    #[Pos(1), Chop(2)]
    #[\Join(3)]
    public int $x = 1;
}

Before:

 6 | ERROR | [x] Function name Pos() found, should be current().
 7 | ERROR | [x] Function name \Join() found, should be \implode().

Those are marked fixable, so phpcbf applied them - turning an attribute class Pos into current(). The result does not compile.

Why the existing guard missed it

Each sniff rejects a match whose previous non-whitespace token is T_FUNCTION, T_OBJECT_OPERATOR, T_NEW or T_DOUBLE_COLON. An attribute name is preceded by #[, which is in none of them - and in a grouped attribute the second name follows a comma, so even adding T_ATTRIBUTE to that list would still miss Chop above.

Every token between an attribute opener and its closer carries attribute_opener, including names after a comma, so one check in the shared lookup in AbstractSniff covers all five sniffs and every position.

After, with a real call added to prove nothing was over-corrected:

 13 | ERROR | [x] Function name pos() found, should be current().

Scope

This predates #72 for plain names - #[Pos(1)] was matched as T_STRING before. Adding fully-qualified support widened it to #[\Join(3)]. Both forms are now in the fixtures.

An attribute name sits in front of a parenthesis exactly like a call does, so
the five function-name sniffs matched it. An attribute happening to share a
name with a function alias was reported, and phpcbf rewrote it: an attribute
class Pos became current(), which does not compile.

The previous-token guard did not help. It rejects T_FUNCTION, T_OBJECT_OPERATOR,
T_NEW and T_DOUBLE_COLON, none of which precede an attribute name, and in a
grouped attribute the second name follows a comma rather than the opener.

Tokens between an attribute opener and its closer all carry the opener, so a
single check in the shared lookup covers every one of them, grouped names
included.

This existed for plain names before fully qualified ones were added, which
widened it to the leading-backslash form. The fixtures gain both.
Copilot AI lite review requested due to automatic review settings August 6, 2026 11:44
@dereuromark
dereuromark merged commit 82af74e into master Aug 6, 2026
6 checks passed
@dereuromark
dereuromark deleted the attribute-false-positives branch August 6, 2026 11:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR prevents the global function-name sniffs (e.g., RemoveFunctionAlias, NoIsNull, etc.) from misidentifying PHP 8 attributes as function calls and applying autofixes that rewrite attribute class names into function names, producing invalid PHP.

Changes:

  • Add an early guard in the shared AbstractSniff::getGlobalFunctionName() lookup to ignore tokens that are part of an attribute (attribute_opener).
  • Add fixtures to RemoveFunctionAlias tests covering both grouped and fully-qualified attribute names (#[Pos(1), Chop(2)] and #[\Join(3)]).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tests/_data/RemoveFunctionAlias/before.php Adds attribute examples to ensure attribute class names are not treated as function calls.
tests/_data/RemoveFunctionAlias/after.php Keeps expected fixed output unchanged for attributes while still validating normal alias fixes.
PhpCollective/Sniffs/AbstractSniffs/AbstractSniff.php Adds a shared attribute guard so function-name sniffs don’t rewrite attribute names.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants