Skip to content

Lex PHP attributes natively so blink fixers format inside #[...] - #245

Merged
TomasVotruba merged 3 commits into
mainfrom
blink-native-attribute-lexing
Oct 31, 2026
Merged

TomasVotruba merged 3 commits into
mainfrom
blink-native-attribute-lexing

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

Problem

blink's lexer folded a whole #[...] attribute into one opaque Comment token, so no fixer could see the arrays, named arguments or operators inside it. Attribute-internal code therefore diverged from PHP ECS, e.g. concat spacing in #[WithEnv('X', __DIR__.'/y')] was never applied.

Change

Attributes now lex natively:

  • #[ becomes an AttributeStart token.
  • the matching ] is retagged as AttributeEnd (a post-lex pass).
  • the contents lex as ordinary tokens.

This mirrors php-cs-fixer's T_ATTRIBUTE / T_ATTRIBUTE_CLOSE. Both delimiters are distinct token kinds, so generic bracket-depth scans never mistake them for array brackets.

Fixers that reasoned about an attribute as a whole were ported to the native tokens via a small shared helper set (AttributeEnd / AttributeStartOf, attributeStartsAt / attributeEndsAt, prevMeaningfulSkippingAttributes) plus a text bridge that re-lexes the existing string reflows (symfony standalone-line, line-length break, arg collapse, anon-class flatten). The old string hacks on the comment value are removed.

Result

Formatting inside attributes (concat, arrays, named args, =>, operator spacing) now matches PHP ECS byte for byte. Verified on the concat case:

```php
#[WithEnv('A', DIR.'/x.php')] -> #[WithEnv('A', DIR . '/x.php')]
```

Verification

  • full go test -race ./..., plus a new native-attribute lexer test
  • gofmt -s, go vet, gopls modernize, golangci-lint (0 issues), check-sources, rule-counts (207/212)
  • laravel parity harness within the ratchet (exit 0)

The lexer used to fold a whole #[...] attribute into one opaque Comment
token, so no fixer could see the arrays, named arguments or operators
inside it. That left attribute-internal code diverging from PHP ECS
(e.g. concat spacing in #[WithEnv('X', __DIR__.'/y')]).

Attributes now lex natively: "#[" is an AttributeStart token, the
closing "]" is retagged as AttributeEnd, and the contents lex as
ordinary tokens, mirroring php-cs-fixer's T_ATTRIBUTE / T_ATTRIBUTE_CLOSE.
Both delimiters are distinct kinds, so generic bracket-depth scans never
mistake them for array brackets.

Fixers that reasoned about an attribute as a whole were ported to the
native tokens: a shared helper set (AttributeEnd/AttributeStartOf,
attributeStartsAt/EndsAt, prevMeaningfulSkippingAttributes) plus a text
bridge that re-lexes the existing string reflows (symfony standalone
line, line-length break, arg collapse, anon-class flatten). The old
string hacks on the comment value are gone.
Making #[...] lex as real tokens exposed four fixers that did not account for
an attribute in the token stream:

- NoUnusedImports: an attribute name (#[AsCommand(...)]) now counts as a class
  use even with a following (, so its import is not dropped.
- BracesPosition: a class header scan no longer reads a leading attribute's )
  as a multiline signature, so the class brace moves to its own line.
- NoSuperfluousPhpdocTags: parseParams skips a param attribute, so a @PARAM
  covered by the native type is removed even with #[\SensitiveParameter].
- StatementIndentation: a promoted param with a leading attribute is still a
  param, not a class property, so following params keep their indent.
rector-src now matches PHP ECS on all 1568 files, so ratchet the locker from
0.25% to 0% - any future divergence fails CI.
@TomasVotruba
TomasVotruba merged commit 33ecce9 into main Oct 31, 2026
21 checks passed
@TomasVotruba
TomasVotruba deleted the blink-native-attribute-lexing branch October 31, 2026 13:26
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.

1 participant