Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1431042
MockBoilerplateSniff.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
MockBoilerplateSniff.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\PHPUnit
;
use
PHP_CodeSniffer\Files\File
;
use
PHP_CodeSniffer\Sniffs\Sniff
;
/**
* Simplify set up of mocks in PHPUnit test cases:
* ->will( $this->returnValue( ... ) ) becomes ->willReturn( ... )
* as well as other ->will() shortcuts, see PHPUnit docs table 8.1
* ->with( $this->equalTo( ... ) ) becomes ->with( ... ), for any number of parameters provided,
* since equalTo() is the default constraint checked if a value is provided (as long as the
* equalTo() call only had a single parameter)
* ->exactly( 1 ) becomes ->once()
* ->exactly( 0 ) becomes ->never()
*
* Potential future improvements include
* - replace unneeded $this->any() calls, i.e.
* ->expects( $this->any() )->method( ... ) becomes ->method( ... )
*
* - apply the with() replacements to withConsecutive() as well
*
* @author DannyS712
*/
class
MockBoilerplateSniff
implements
Sniff
{
use
PHPUnitTestTrait
;
/** @var array */
private
const
RELEVANT_METHODS
=
[
'exactly'
=>
'exactly'
,
'will'
=>
'will'
,
'with'
=>
'with'
,
];
/** @var array */
private
const
WILL_REPLACEMENTS
=
[
'returnValue'
=>
'willReturn'
,
'returnArgument'
=>
'willReturnArgument'
,
'returnCallback'
=>
'willReturnCallback'
,
'returnValueMap'
=>
'willReturnMap'
,
'onConsecutiveCalls'
=>
'willReturnOnConsecutiveCalls'
,
'returnSelf'
=>
'willReturnSelf'
,
'throwException'
=>
'willThrowException'
,
];
/**
* @inheritDoc
*/
public
function
register
():
array
{
return
[
T_STRING
];
}
/**
* @param File $phpcsFile
* @param int $stackPtr
*
* @return void|int
*/
public
function
process
(
File
$phpcsFile
,
$stackPtr
)
{
if
(
!
$this
->
isTestFile
(
$phpcsFile
,
$stackPtr
)
)
{
return
$phpcsFile
->
numTokens
;
}
$tokens
=
$phpcsFile
->
getTokens
();
if
(
$tokens
[
$stackPtr
][
'level'
]
<
2
)
{
// Needs to be in a method in a class
return
;
}
$methodName
=
$tokens
[
$stackPtr
][
'content'
];
if
(
!
isset
(
self
::
RELEVANT_METHODS
[
$methodName
]
)
)
{
// Not a method we care about
return
;
}
$methodOpener
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$stackPtr
+
1
,
null
,
true
);
if
(
!
isset
(
$tokens
[
$methodOpener
][
'parenthesis_closer'
]
)
)
{
// Needs to be a method call
return
$methodOpener
+
1
;
}
switch
(
$methodName
)
{
case
'exactly'
:
return
$this
->
handleExactly
(
$phpcsFile
,
$stackPtr
,
$methodOpener
);
case
'will'
:
return
$this
->
handleWill
(
$phpcsFile
,
$stackPtr
,
$methodOpener
);
case
'with'
:
return
$this
->
handleWith
(
$phpcsFile
,
$methodOpener
);
}
}
/**
* @param File $phpcsFile
* @param int $stackPtr
* @param int $exactlyOpener
*
* @return void|int
*/
public
function
handleExactly
(
File
$phpcsFile
,
$stackPtr
,
$exactlyOpener
)
{
$tokens
=
$phpcsFile
->
getTokens
();
$exactlyCloser
=
$tokens
[
$exactlyOpener
][
'parenthesis_closer'
];
$exactlyNumPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$exactlyOpener
+
1
,
$exactlyCloser
,
true
);
if
(
!
$exactlyNumPtr
||
$tokens
[
$exactlyNumPtr
][
'code'
]
!==
T_LNUMBER
)
{
// Not going to be ->exactly( 0 ) or ->exactly( 1 )
return
;
}
// Figure out if it is indeed 0 or 1
if
(
$tokens
[
$exactlyNumPtr
][
'content'
]
===
'0'
)
{
$exactlyShortcut
=
'never'
;
}
elseif
(
$tokens
[
$exactlyNumPtr
][
'content'
]
===
'1'
)
{
$exactlyShortcut
=
'once'
;
}
else
{
// no shortcut
return
;
}
// Make sure it is only the 0 or 1, not something like ->exactly( 1 + $num )
$afterNum
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$exactlyNumPtr
+
1
,
$exactlyCloser
,
true
);
if
(
$afterNum
)
{
return
;
}
// For reference, here are the different pointers we have stored
//
// $exactlyNumPtr
// $stackPtr | $exactlyCloser
// \ | /
// ->exactly( 0 )
// |
// $exactlyOpener
//
// ($exactlyNumPtr could point to a 1 instead of a 0)
// and we want to replace from $stackPtr until $exactlyCloser with the shortcut
// plus a ()
$warningName
=
(
$exactlyShortcut
===
'never'
?
'ExactlyNever'
:
'ExactlyOnce'
);
$fix
=
$phpcsFile
->
addFixableWarning
(
'Matcher ->exactly( %s ) should be replaced with shortcut ->%s()'
,
$stackPtr
,
$warningName
,
[
$tokens
[
$exactlyNumPtr
][
'content'
],
$exactlyShortcut
]
);
if
(
!
$fix
)
{
// There is no way the next issue can be closer than this
return
$exactlyCloser
;
}
$phpcsFile
->
fixer
->
beginChangeset
();
// Remove from after $stackPtr up to and including $exactlyCloser, so that if
// they are split over multiple lines we don't leave an ugly mess
for
(
$i
=
$stackPtr
+
1
;
$i
<=
$exactlyCloser
;
$i
++
)
{
$phpcsFile
->
fixer
->
replaceToken
(
$i
,
''
);
}
// Replace $stackPtr's exactly with the shortcut
$phpcsFile
->
fixer
->
replaceToken
(
$stackPtr
,
$exactlyShortcut
.
'()'
);
$phpcsFile
->
fixer
->
endChangeset
();
// There is no way the next issue can be closer that this
return
$exactlyCloser
;
}
/**
* @param File $phpcsFile
* @param int $stackPtr
* @param int $willOpener
*
* @return void|int
*/
public
function
handleWill
(
File
$phpcsFile
,
$stackPtr
,
$willOpener
)
{
$tokens
=
$phpcsFile
->
getTokens
();
$willCloser
=
$tokens
[
$willOpener
][
'parenthesis_closer'
];
$thisPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$willOpener
+
1
,
$willCloser
,
true
);
if
(
!
$thisPtr
||
$tokens
[
$thisPtr
][
'code'
]
!==
T_VARIABLE
||
$tokens
[
$thisPtr
][
'content'
]
!==
'$this'
)
{
// Not going to be $this->
return
;
}
$objectOperatorPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$thisPtr
+
1
,
$willCloser
,
true
);
if
(
!
$objectOperatorPtr
||
$tokens
[
$objectOperatorPtr
][
'code'
]
!==
T_OBJECT_OPERATOR
)
{
// Not $this->
return
;
}
$methodStubPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$objectOperatorPtr
+
1
,
$willCloser
,
true
);
if
(
!
$methodStubPtr
||
$tokens
[
$methodStubPtr
][
'code'
]
!==
T_STRING
||
!
isset
(
self
::
WILL_REPLACEMENTS
[
$tokens
[
$methodStubPtr
][
'content'
]
]
)
)
{
// Not $this-> followed by a method name we care about
return
;
}
$stubOpener
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$methodStubPtr
+
1
,
$willCloser
,
true
);
if
(
!
$stubOpener
||
!
isset
(
$tokens
[
$stubOpener
][
'parenthesis_closer'
]
)
)
{
// String is not a method name
return
;
}
$stubCloser
=
$tokens
[
$stubOpener
][
'parenthesis_closer'
];
// Okay, so we found something that might be worth replacing, in the form
// ->will( $this->returnValue( ... ) )
// or similar. Make sure there is nothing between the end of the stub and the parenthesis
// closer for the ->will() call
$afterStub
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$stubCloser
+
1
,
$willCloser
,
true
);
if
(
$afterStub
)
{
return
;
}
// For reference, here are the different pointers we have stored
//
// $willOpener $methodStubPtr
// \ | $willCloser
// $stackPtr | $thisPtr | $stubOpener /
// | | | | | |
// ->will ( $this -> returnValue( ... ) )
// | |
// $objectOperatorPtr $stubCloser
//
// What we want to do is to remove the inner stub, i.e. replace
// $this->returnValue( ... )
// with just the
// ...
// and then update the outer ->will( ... ) to use the shortcut
// ->willReturnValue( ... )
$stubMethod
=
$tokens
[
$methodStubPtr
][
'content'
];
$willReplacement
=
self
::
WILL_REPLACEMENTS
[
$stubMethod
];
$fix
=
$phpcsFile
->
addFixableWarning
(
'Use the shortcut %s() rather that manually stubbing a method with %s()'
,
$stackPtr
,
$stubMethod
,
[
$willReplacement
,
$stubMethod
]
);
if
(
!
$fix
)
{
// There is no way the next issue can be closer than this
return
$willCloser
;
}
$phpcsFile
->
fixer
->
beginChangeset
();
// To be consistent with whitespace around the parenthesis, we will keep
// the original parenthesis from $stubOpener and $stubCloser and the whitespace
// within them.
// Step 1: remove everything from after $stubCloser up to and including $willCloser
for
(
$i
=
$stubCloser
+
1
;
$i
<=
$willCloser
;
$i
++
)
{
$phpcsFile
->
fixer
->
replaceToken
(
$i
,
''
);
}
// Step 2: remove everything from $willOpener up to, but not including, $stubOpener
for
(
$i
=
$willOpener
;
$i
<
$stubOpener
;
$i
++
)
{
$phpcsFile
->
fixer
->
replaceToken
(
$i
,
''
);
}
// Step 3: replace 'will' with the correct shortcut method
$phpcsFile
->
fixer
->
replaceToken
(
$stackPtr
,
$willReplacement
);
$phpcsFile
->
fixer
->
endChangeset
();
// There is no way the next issue can be closer that this
return
$willCloser
;
}
/**
* @param File $phpcsFile
* @param int $withOpener
*
* @return int
*/
public
function
handleWith
(
File
$phpcsFile
,
$withOpener
)
{
$tokens
=
$phpcsFile
->
getTokens
();
$withCloser
=
$tokens
[
$withOpener
][
'parenthesis_closer'
];
// For every use of `$this->equalTo( ... )` between $withOpener and $withCloser,
// add a warning, and if fixing, replace with just the inner contents
// Use a for loop so that we can call findNext() after each continue
// phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer.Found
for
(
$thisPtr
=
$phpcsFile
->
findNext
(
T_VARIABLE
,
$withOpener
+
1
,
$withCloser
);
$thisPtr
;
$thisPtr
=
$phpcsFile
->
findNext
(
T_VARIABLE
,
$thisPtr
+
1
,
$withCloser
)
)
{
// Needs to be $this
if
(
$tokens
[
$thisPtr
][
'content'
]
!==
'$this'
)
{
continue
;
}
// Needs to be $this->
$objectOperatorPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$thisPtr
+
1
,
$withCloser
,
true
);
if
(
!
$objectOperatorPtr
||
$tokens
[
$objectOperatorPtr
][
'code'
]
!==
T_OBJECT_OPERATOR
)
{
continue
;
}
// Needs to be $this->equalTo
$methodPtr
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$objectOperatorPtr
+
1
,
$withCloser
,
true
);
// if its $this->logicalNot() or similar we want to skip past the closing
// parenthesis, just make sure its a function call here
if
(
!
$methodPtr
||
$tokens
[
$methodPtr
][
'code'
]
!==
T_STRING
)
{
continue
;
}
// Needs to be $this->equalTo( ... )
$methodOpener
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$methodPtr
+
1
,
$withCloser
,
true
);
if
(
!
$methodOpener
||
!
isset
(
$tokens
[
$methodOpener
][
'parenthesis_closer'
]
)
)
{
// String is not a method name
continue
;
}
$methodCloser
=
$tokens
[
$methodOpener
][
'parenthesis_closer'
];
if
(
$tokens
[
$methodPtr
][
'content'
]
!==
'equalTo'
)
{
$thisPtr
=
$methodCloser
;
continue
;
}
// Check for equalTo() with a second parameter, which we cannot fix
$shouldSkip
=
false
;
$searchFor
=
[
T_COMMA
,
T_OPEN_PARENTHESIS
];
for
(
$checkIndex
=
$phpcsFile
->
findNext
(
$searchFor
,
$methodOpener
+
1
,
$methodCloser
);
$checkIndex
;
$checkIndex
=
$phpcsFile
->
findNext
(
$searchFor
,
$checkIndex
+
1
,
$methodCloser
)
)
{
if
(
$tokens
[
$checkIndex
][
'code'
]
===
T_OPEN_PARENTHESIS
&&
isset
(
$tokens
[
$checkIndex
][
'parenthesis_closer'
]
)
)
{
// Jump past any parentheses in a function call within
// the equalTo(), eg $this->equalTo( add( 2, 3 ) )
$checkIndex
=
$tokens
[
$checkIndex
][
'parenthesis_closer'
];
}
elseif
(
$tokens
[
$checkIndex
][
'code'
]
===
T_COMMA
)
{
// equalTo() with multiple parameters, should not be removed
$shouldSkip
=
true
;
break
;
}
}
if
(
$shouldSkip
)
{
// Next $this->equalTo() cannot be until after the current one
$thisPtr
=
$methodCloser
;
continue
;
}
// Add a warning and maybe fix
$fix
=
$phpcsFile
->
addFixableWarning
(
'Default constraint equalTo() is unneeded and should be removed'
,
$methodPtr
,
'ConstraintEqualTo'
);
if
(
!
$fix
)
{
// Next $this->equalTo() cannot be until after the current one
$thisPtr
=
$methodCloser
;
continue
;
}
// For reference, here are the different pointers we have stored
//
// $objectOperatorPtr
// \
// $thisPtr | $methodOpener $methodCloser
// \ | | |
// $this -> equalTo ( ... )
// /
// $methodPtr
// Find the first and last non-whitespace parts of the ... and only keep
// those
$equalContentStart
=
$phpcsFile
->
findNext
(
T_WHITESPACE
,
$methodOpener
+
1
,
$methodCloser
,
true
);
$equalContentEnd
=
$phpcsFile
->
findPrevious
(
T_WHITESPACE
,
$methodCloser
-
1
,
$methodOpener
,
true
);
$phpcsFile
->
fixer
->
beginChangeset
();
// Step 1: remove from after $equalContentEnd up to and including $methodCloser
for
(
$i
=
$equalContentEnd
+
1
;
$i
<=
$methodCloser
;
$i
++
)
{
$phpcsFile
->
fixer
->
replaceToken
(
$i
,
''
);
}
// Step 2: remove from $thisPtr up to, but not including, $equalContentStart
for
(
$i
=
$thisPtr
;
$i
<
$equalContentStart
;
$i
++
)
{
$phpcsFile
->
fixer
->
replaceToken
(
$i
,
''
);
}
$phpcsFile
->
fixer
->
endChangeset
();
// There is no way the next issue can be closer that this
$thisPtr
=
$methodCloser
;
}
return
$withCloser
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 20:00 (3 h, 16 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ef/31/0c9c9a88f5043f790d560fb99993
Default Alt Text
MockBoilerplateSniff.php (13 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment