Setting the scene
PER-CS 2.0 added a dedicated section about heredoc and nowdoc notation (section 10). Two of its rules are not enforced by any sniff in PHPCS or PHPCSExtra:
Declared heredocs or nowdocs MUST begin on the same line as the context the declaration is being used in.
Subsequent lines in the heredoc or nowdoc MUST be indented once past the scope indentation they are declared in.
Proposed new sniff: Universal.Strings.HeredocNowdocFormatting
Universal has no Strings category yet, so this proposes adding one (the same approach as the proposed Universal.Enums.*).
Resolving the declaration line
Both rules are anchored to the same reference: the line on which the heredoc/nowdoc is declared (PER's "context" for the opener placement, and "scope indentation" for the body and closer). The sniff must resolve that line from the opener, then require the opener to begin on it and the body and closer to be indented one step past its indentation. Resolving it is the hard part, because the reference depends on where the heredoc/nowdoc sits (an assignment, a call argument, a return, a concatenation operand, and so on). Handling all these positions reliably needs further investigation.
Notes for the implementation
Register [T_START_HEREDOC, T_START_NOWDOC] as the entry point. A heredoc/nowdoc is made up of three token types: the opener (T_START_HEREDOC/T_START_NOWDOC), one body-line token per line (T_HEREDOC/T_NOWDOC), and the closing marker (T_END_HEREDOC/T_END_NOWDOC). Unlike the opener, whose leading indentation is a preceding whitespace token, the leading indentation of each body line and of the closing marker is part of that token's own content.
From the opener, resolve the reference described above and apply both rules against it:
- Rule 1 (opener placement): the opener must begin on the context's line.
- Rule 2 (body + closer indentation): the body lines and closing marker must be indented one step past the scope indentation (the indentation of the line the heredoc is declared on), where one step is the configurable
$indent property (default 4).
Indenting heredoc/nowdoc bodies and closing markers is the "flexible heredoc/nowdoc syntax" introduced in PHP 7.3. Auto-fixing an un-indented heredoc would rewrite it into 7.3+ only syntax and introduce a parse error in code that must still run on PHP < 7.3. This also needs further investigation, but initially I believe that it is safer for this sniff to not support auto-fixing for the indentation errors.
Suggested error codes:
OpenerNotOnSameLine: the opener does not begin on the same line as its context. Whether this can be auto-fixed needs to be investigated.
BodyIndent: a body line is not indented once past the scope indentation.
CloserIndent: the closing marker is not indented once past the scope indentation.
Describe the solution you'd like
A new sniff as outlined above.
The sniff should be able to flag the following:
function allowed()
{
// OK: opener on the assignment line, body + closer one step past the scope.
$allowed = <<<COMPLIANT
This is compliant.
COMPLIANT;
// OK: as a multi-line call argument, opener on its argument line, body one step past it.
my_function(
$param1,
<<<COMPLIANT
Just a test.
COMPLIANT,
);
}
function notAllowed()
{
// Error (OpenerNotOnSameLine): opener pushed onto a separate line from the assignment.
$notAllowed =
<<<'COUNTEREXAMPLE'
This is not allowed.
COUNTEREXAMPLE;
// Error (BodyIndent + CloserIndent): body and closing marker not indented past the scope.
$notAllowed = <<<'COUNTEREXAMPLE'
This
is not allowed.
COUNTEREXAMPLE;
}
Also see the examples outlined in the PER documents (rules + migration guide).
Additional context (optional)
This ticket 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
PER-CS 2.0 added a dedicated section about heredoc and nowdoc notation (section 10). Two of its rules are not enforced by any sniff in PHPCS or PHPCSExtra:
Proposed new sniff:
Universal.Strings.HeredocNowdocFormattingUniversalhas noStringscategory yet, so this proposes adding one (the same approach as the proposedUniversal.Enums.*).Resolving the declaration line
Both rules are anchored to the same reference: the line on which the heredoc/nowdoc is declared (PER's "context" for the opener placement, and "scope indentation" for the body and closer). The sniff must resolve that line from the opener, then require the opener to begin on it and the body and closer to be indented one step past its indentation. Resolving it is the hard part, because the reference depends on where the heredoc/nowdoc sits (an assignment, a call argument, a
return, a concatenation operand, and so on). Handling all these positions reliably needs further investigation.Notes for the implementation
Register
[T_START_HEREDOC, T_START_NOWDOC]as the entry point. A heredoc/nowdoc is made up of three token types: the opener (T_START_HEREDOC/T_START_NOWDOC), one body-line token per line (T_HEREDOC/T_NOWDOC), and the closing marker (T_END_HEREDOC/T_END_NOWDOC). Unlike the opener, whose leading indentation is a preceding whitespace token, the leading indentation of each body line and of the closing marker is part of that token's owncontent.From the opener, resolve the reference described above and apply both rules against it:
$indentproperty (default4).Indenting heredoc/nowdoc bodies and closing markers is the "flexible heredoc/nowdoc syntax" introduced in PHP 7.3. Auto-fixing an un-indented heredoc would rewrite it into 7.3+ only syntax and introduce a parse error in code that must still run on PHP < 7.3. This also needs further investigation, but initially I believe that it is safer for this sniff to not support auto-fixing for the indentation errors.
Suggested error codes:
OpenerNotOnSameLine: the opener does not begin on the same line as its context. Whether this can be auto-fixed needs to be investigated.BodyIndent: a body line is not indented once past the scope indentation.CloserIndent: the closing marker is not indented once past the scope indentation.Describe the solution you'd like
A new sniff as outlined above.
The sniff should be able to flag the following:
Also see the examples outlined in the PER documents (rules + migration guide).
Additional context (optional)
This ticket 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.