Setting the scene
PHP 8.0 introduced attributes via the #[...] syntax. PHPCSExtra already has a set of attribute sniffs in the Universal.Attributes category, and there are a few issues still open to cover remaining attribute-formatting rules from PER-CS 2.0. But as far as I can see, there is not an issue or sniff covering the rule for attributes on parameters placement.
Proposed new sniff: Universal.Attributes.ParameterAttributePlacement
Section 12.2 of PER-CS 2.0 defines where an attribute must be placed relative to the structure it describes. The rule for classes, methods, functions, constants and properties is tracked by #389.
The rule for parameter attributes is different and is addressed in this issue:
For attributes on parameters, if the parameter list is presented on a single line, the attribute MUST be placed inline with the parameter it describes, separated by a single space. If the parameter list is split into multiple lines, the attribute MUST be placed on its own line prior to the parameter, indented the same as the parameter. A blank line MAY be included between one parameter and the attributes of the following parameter.
The indentation part of the multi-line case is already tracked separately by #393, so this issue is only about the placement of parameter attributes: inline when the parameter list is on a single line separated by a space, on their own line when the parameter list is split across multiple lines.
Notes for the implementation
Register [T_ATTRIBUTE] as the entry point. Use AttributeBlock::appliesTo() to classify the target: the sniff is in scope only when it returns a T_VARIABLE inside a T_FUNCTION/T_CLOSURE/T_FN owned parenthesis. appliesTo() returns the parameter variable, which may sit on a different line from the attribute for example when the type and variable are split across lines, so it is used only for classification. The placement check is measured against the first non-empty token after the attribute closer (the start of the parameter declaration: its type, modifier, or the variable when untyped).
Single vs. multi-line detection: compare the line of the parameter-list opening parenthesis with its parenthesis_closer. Same line enforces rule 1 (exactly one space between the attribute closer and the start of the parameter), different lines enforces rule 2 (the attribute on its own line).
Suggested error codes:
InlineIncorrectSpacing: single-line parameter list, but the attribute is not separated from the parameter by exactly one space.
NotOnOwnLine: multi-line parameter list, but the attribute is not on its own line prior to the parameter.
Indentation of the own-line case is intentionally not checked here, that is #393 responsibility, so the NotOnOwnLine fixer should only add the newline. PER explicitly allows a blank line between one parameter and the next parameter's attribute, so NotOnOwnLine must not flag that.
Describe the solution you'd like
A new sniff as outlined above.
The sniff should be able to flag and in some cases auto-fix the following:
// OK: single-line parameter list, attribute inline with a single space.
function foo(#[Attr] int $a, #[Other] string $b): void {}
// OK: multi-line parameter list, attribute on its own line prior to the parameter.
function bar(
#[Attr]
int $a,
#[Other]
string $b,
) {}
// OK: multiple attributes and a promoted constructor property on a single-line list.
function __construct(#[A] #[B] private int $x) {}
// OK: the compound form #[A, B] is equivalent.
function __construct(#[A, B] private int $x) {}
// OK: blank line allowed between one parameter and the next parameter's attribute (multi-line).
function baz(
int $a,
#[Attr]
int $b,
) {}
// Error (InlineIncorrectSpacing): single-line parameter list, but more than one space between the attribute and the parameter.
function foo(#[Attr] int $a, #[Other] string $b): void {}
// Error (NotOnOwnLine): multi-line parameter list, attribute inline with the parameter.
function bar(
#[Attr] int $a,
#[Other] string $b,
) {}
// Error (NotOnOwnLine, non-fixable): multi-line list with the attribute inline and a comment
// between it and the parameter.
function qux(
#[Attr] /* comment */ int $a,
#[Other] string $b,
) {}
Also see the examples outlined in the PER documents (rules + migration guide).
Additional context (optional)
This ticket is part of a series of tickets related to PHP attributes and is the result of a detailed analysis of the rules as outlined in PER-CS 2.0, as well as a critical look at what's still missing rule-wise.
Setting the scene
PHP 8.0 introduced attributes via the
#[...]syntax. PHPCSExtra already has a set of attribute sniffs in theUniversal.Attributescategory, and there are a few issues still open to cover remaining attribute-formatting rules from PER-CS 2.0. But as far as I can see, there is not an issue or sniff covering the rule for attributes on parameters placement.Proposed new sniff:
Universal.Attributes.ParameterAttributePlacementSection 12.2 of PER-CS 2.0 defines where an attribute must be placed relative to the structure it describes. The rule for classes, methods, functions, constants and properties is tracked by #389.
The rule for parameter attributes is different and is addressed in this issue:
The indentation part of the multi-line case is already tracked separately by #393, so this issue is only about the placement of parameter attributes: inline when the parameter list is on a single line separated by a space, on their own line when the parameter list is split across multiple lines.
Notes for the implementation
Register
[T_ATTRIBUTE]as the entry point. UseAttributeBlock::appliesTo()to classify the target: the sniff is in scope only when it returns aT_VARIABLEinside aT_FUNCTION/T_CLOSURE/T_FNowned parenthesis.appliesTo()returns the parameter variable, which may sit on a different line from the attribute for example when the type and variable are split across lines, so it is used only for classification. The placement check is measured against the first non-empty token after the attribute closer (the start of the parameter declaration: its type, modifier, or the variable when untyped).Single vs. multi-line detection: compare the line of the parameter-list opening parenthesis with its
parenthesis_closer. Same line enforces rule 1 (exactly one space between the attribute closer and the start of the parameter), different lines enforces rule 2 (the attribute on its own line).Suggested error codes:
InlineIncorrectSpacing: single-line parameter list, but the attribute is not separated from the parameter by exactly one space.NotOnOwnLine: multi-line parameter list, but the attribute is not on its own line prior to the parameter.Indentation of the own-line case is intentionally not checked here, that is #393 responsibility, so the
NotOnOwnLinefixer should only add the newline. PER explicitly allows a blank line between one parameter and the next parameter's attribute, soNotOnOwnLinemust not flag that.Describe the solution you'd like
A new sniff as outlined above.
The sniff should be able to flag and in some cases auto-fix the following:
Also see the examples outlined in the PER documents (rules + migration guide).
Additional context (optional)
This ticket is part of a series of tickets related to PHP attributes and is the result of a detailed analysis of the rules as outlined in PER-CS 2.0, as well as a critical look at what's still missing rule-wise.