Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1430594
NestedInlineTernarySniff.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
NestedInlineTernarySniff.php
View Options
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace
MediaWiki\Sniffs\Usage
;
use
PHP_CodeSniffer\Files\File
;
use
PHP_CodeSniffer\Sniffs\Sniff
;
use
PHP_CodeSniffer\Util\Tokens
;
class
NestedInlineTernarySniff
implements
Sniff
{
/**
* Tokens that can end an inline ternary statement.
*
* @var array
*/
private
array
$endTokens
=
[];
/**
* @inheritDoc
*/
public
function
register
():
array
{
$this
->
endTokens
=
Tokens
::
$assignmentTokens
+
Tokens
::
$includeTokens
+
[
// Operators having a lower precedence than the ternary operator,
// or left associative operators having the same precedence, can
// end inline ternary statements. This includes all assignment and
// include statements.
//
// In the PHP source code, the order of precedence can be found
// in the file Zend/zend_language_parser.y. To find the ternary
// operator in the list, search for "%left '?' ':'".
T_INLINE_THEN
=>
T_INLINE_THEN
,
T_INLINE_ELSE
=>
T_INLINE_ELSE
,
T_YIELD_FROM
=>
T_YIELD_FROM
,
T_YIELD
=>
T_YIELD
,
T_PRINT
=>
T_PRINT
,
T_LOGICAL_AND
=>
T_LOGICAL_AND
,
T_LOGICAL_XOR
=>
T_LOGICAL_XOR
,
T_LOGICAL_OR
=>
T_LOGICAL_OR
,
// Obviously, right brackets, right parentheses, commas, colons,
// and semicolons can also end inline ternary statements. There is
// a list of corresponding tokens in File::findEndOfStatement(),
// which we duplicate here.
T_COLON
=>
T_COLON
,
T_COMMA
=>
T_COMMA
,
T_SEMICOLON
=>
T_SEMICOLON
,
T_CLOSE_PARENTHESIS
=>
T_CLOSE_PARENTHESIS
,
T_CLOSE_SQUARE_BRACKET
=>
T_CLOSE_SQUARE_BRACKET
,
T_CLOSE_CURLY_BRACKET
=>
T_CLOSE_CURLY_BRACKET
,
T_CLOSE_SHORT_ARRAY
=>
T_CLOSE_SHORT_ARRAY
,
// Less obviously, a foreach loop's array_expression can be
// an inline ternary statement, and would be followed by "as".
T_AS
=>
T_AS
,
];
return
[
T_INLINE_THEN
];
}
/**
* @param File $phpcsFile File
* @param int $stackPtr Location
* @return void
*/
public
function
process
(
File
$phpcsFile
,
$stackPtr
)
{
$tokens
=
$phpcsFile
->
getTokens
();
$elsePtr
=
null
;
$thenNestingLevel
=
1
;
$thenNestedPtr
=
null
;
$elseNestedPtr
=
null
;
for
(
$i
=
$stackPtr
+
1
;
$i
<
$phpcsFile
->
numTokens
;
++
$i
)
{
// Skip bracketed and parenthesized subexpressions.
$inBrackets
=
isset
(
$tokens
[
$i
][
'bracket_closer'
]
);
if
(
$inBrackets
&&
$tokens
[
$i
][
'bracket_opener'
]
===
$i
)
{
$i
=
$tokens
[
$i
][
'bracket_closer'
];
continue
;
}
$inParentheses
=
isset
(
$tokens
[
$i
][
'parenthesis_closer'
]
);
if
(
$inParentheses
&&
$tokens
[
$i
][
'parenthesis_opener'
]
===
$i
)
{
$i
=
$tokens
[
$i
][
'parenthesis_closer'
];
continue
;
}
if
(
$elsePtr
===
null
)
{
// In the "then" part of the inline ternary statement:
if
(
$tokens
[
$i
][
'code'
]
===
T_INLINE_THEN
)
{
// Let $thenNestedPtr point to the T_INLINE_THEN token
// of the outermost inline ternary statement forming the
// "then" part of the current inline ternary statement.
// Example: $a ? $b ? $c ? $d : $e : $f : $g
// - ^ stackPtr
// - ^ thenNestedPtr
if
(
++
$thenNestingLevel
===
2
)
{
$thenNestedPtr
=
$i
;
}
}
elseif
(
$tokens
[
$i
][
'code'
]
===
T_INLINE_ELSE
)
{
// Let $elsePtr point to the T_INLINE_ELSE token of the
// current inline ternary statement. See below example.
if
(
--
$thenNestingLevel
===
0
)
{
$elsePtr
=
$i
;
}
}
// Strictly speaking, checking if the entire "then" part
// is an inline ternary statement would involve checking the
// token, whenever $thenNestingLevel is 1, against the
// list of operators of lower precedence.
//
// However, we omit this check in order to allow additional
// cases to be flagged as needing parentheses for clarity.
}
else
{
// In the "else" part of the inline ternary statement:
if
(
isset
(
$this
->
endTokens
[
$tokens
[
$i
][
'code'
]]
)
)
{
if
(
$tokens
[
$i
][
'code'
]
===
T_INLINE_THEN
)
{
// Let $elseNestedPtr point to the T_INLINE_THEN token
// of the inline ternary statement having the current
// inline ternary statement as its "if" part.
// Example: $a ? $b : $c ? $d : $e ? $f : $g
// - ^ stackPtr
// - ^ elsePtr
// - ^ elseNestedPtr
$elseNestedPtr
=
$i
;
}
break
;
}
}
}
// The "then" part of the current inline ternary statement should not
// be another inline ternary statement, unless that other inline
// ternary statement is parenthesized.
if
(
$thenNestedPtr
!==
null
&&
$elsePtr
!==
null
)
{
$fix
=
$phpcsFile
->
addFixableWarning
(
'Nested inline ternary statements can be difficult to read without parentheses'
,
$thenNestedPtr
,
'UnparenthesizedThen'
);
if
(
$fix
)
{
$phpcsFile
->
fixer
->
beginChangeset
();
$phpcsFile
->
fixer
->
addContent
(
$stackPtr
,
' ('
);
$phpcsFile
->
fixer
->
addContentBefore
(
$elsePtr
,
') '
);
$phpcsFile
->
fixer
->
endChangeset
();
}
}
// The current inline ternary statement must not be the "if" part of
// another inline ternary statement, unless the current inline
// ternary statement is parenthesized.
if
(
$elseNestedPtr
!==
null
&&
!(
// Exception: Stacking is permitted when only the short form of
// the ternary operator is used. In this case, the operator's
// left associativity is unlikely to matter.
$this
->
isShortTernary
(
$phpcsFile
,
$stackPtr
)
&&
$this
->
isShortTernary
(
$phpcsFile
,
$elseNestedPtr
)
)
)
{
// Report this violation as an error, because it looks like a bug.
// For the same reason, don't offer to fix it automatically.
$phpcsFile
->
addError
(
'Nested inline ternary statements, in PHP, may not behave as you intend '
.
'without parentheses'
,
$stackPtr
,
'UnparenthesizedTernary'
);
}
}
/**
* @param File $phpcsFile File
* @param int $i Location of T_INLINE_THEN
* @return bool
*/
private
function
isShortTernary
(
File
$phpcsFile
,
int
$i
):
bool
{
$tokens
=
$phpcsFile
->
getTokens
();
$i
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
$i
+
1
,
null
,
true
);
return
$i
!==
false
&&
$tokens
[
$i
][
'code'
]
===
T_INLINE_ELSE
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 19:18 (5 h, 45 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c5/d2/32cd709cffff01c4d9f1d20e8613
Default Alt Text
NestedInlineTernarySniff.php (6 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment