Skip to content

Generic/UpperCaseConstantName: skip when local define() is in scope#1412

Closed
dereuromark wants to merge 2 commits intoPHPCSStandards:4.xfrom
dereuromark:fix/733-uppercase-constant-name-namespaced-define
Closed

Generic/UpperCaseConstantName: skip when local define() is in scope#1412
dereuromark wants to merge 2 commits intoPHPCSStandards:4.xfrom
dereuromark:fix/733-uppercase-constant-name-namespaced-define

Conversation

@dereuromark
Copy link
Copy Markdown

Summary

Fixes #733.

When a file declares a top-level function define(...) or imports one via use function ...\define; (incl. group use and as aliases), unqualified define(...) calls may resolve to that local function instead of the global one. The sniff currently flags lowercase constant names in those calls as a false positive. With this change, the sniff bows out for unqualified define(...) calls in such files.

Fully qualified \define(...) calls (T_NAME_FULLY_QUALIFIED) remain checked as before, since they unambiguously refer to the global function.

Repro

The issue lists several variants. On modern 4.x most of them are already silently handled because qualified names tokenize as T_NAME_QUALIFIED / T_NAME_RELATIVE, which the sniff does not register. The remaining real false positive is the bare define(...) case:

<?php
namespace Foo;

function define(\$name, \$value) {}

define('name', 'value'); // <-- false positive on 4.x without this fix

Approach

Added a private helper fileHasCustomDefine() that scans the file once (cached per file path via a static variable inside the method) for either:

  1. A top-level function define(...) declaration (skips class methods via conditions).
  2. A use function ...\define; import — including group use (use function Foo\{bar, define};) and aliases. Aliases to define count (use function Foo\bar as define;); aliases away from define do not (use function Foo\define as something;).

In process(), when the visited token is a bare T_STRING for define and the helper returns true, the sniff returns early.

Why a static cache and not an instance property

tests/Core/Ruleset/PopulateTokenListenersTest::testDoesntTriggerPropertySettingForNoProperties uses this exact sniff class as the canonical "sniff with no declared properties" sample. Adding an instance property breaks that meta test. A static variable inside the helper method gives the same per-file caching without changing the sniff's reflection footprint. Happy to switch to an instance property + meta-test update if maintainers prefer.

Test changes

  • Removed the aspirational function define(\$param) {} declaration from UpperCaseConstantNameUnitTest.1.inc. With the fix, that declaration would correctly cause the sniff to bow out for the entire file, invalidating the rest of the fixture's expectations. Moved a comparable scenario into a dedicated new fixture instead.
  • Added UpperCaseConstantNameUnitTest.6.inc — local function define() declared in a namespace; only the fully qualified \define() call with a lowercase name is flagged.
  • Added UpperCaseConstantNameUnitTest.7.incuse function Bar\define; and a group use function Bar\{otherFunc, define as renamedDefine};; only the fully qualified \define() call is flagged.
  • Added UpperCaseConstantNameUnitTest.8.incuse function Bar\define as something; aliases away from define, so the local define symbol is unaffected and the unqualified call is still flagged.
  • Updated UpperCaseConstantNameUnitTest.php to register the three new fixtures and adjust one shifted line number in 1.inc (94 → 91, the result of removing two lines from 1.inc).

Verification

  • Full test suite: 4173 tests, 0 failures.
  • phpcs on the modified sniff: clean.
  • Manual repro of the issue's variants and an alias-away regression case all behave correctly.

Notes for reviewers

  • The fix targets only the single remaining false positive on 4.x; the other variants from the issue body are already not flagged on this branch.
  • Marking as draft for early feedback on the cache strategy and the test fixture reorganisation.

When a file declares a top-level `function define(...)` or imports one
via `use function ...\define;` (including group use and `as` aliases),
unqualified `define(...)` calls may resolve to that function instead of
the global one. The sniff should bow out in those cases.

Fully qualified `\define(...)` calls remain checked as before because
they tokenize as T_NAME_FULLY_QUALIFIED and unambiguously refer to the
global function.

Fixes PHPCSStandards#733
@jrfnl
Copy link
Copy Markdown
Member

jrfnl commented Apr 10, 2026

@dereuromark Did you use AI for this PR ?

@dereuromark
Copy link
Copy Markdown
Author

I managed to fix this with own sniff for now. Feel free to reuse if it is deemed useful.

@dereuromark dereuromark deleted the fix/733-uppercase-constant-name-namespaced-define branch April 10, 2026 16:13
@jrfnl
Copy link
Copy Markdown
Member

jrfnl commented Apr 10, 2026

@dereuromark That doesn't answer the question I asked ?

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.

Generic/UpperCaseConstantName: false positives for namespaced function called define()

2 participants