Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1432220
ExtendClassUsageSniff.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
ExtendClassUsageSniff.php
View Options
<?php
/**
* Report warnings when a global variable or function is used where there's a better
* context-aware alternative.
*
* Should use $this->msg() rather than wfMessage() on ContextSource extend.
* Should use $this->getUser() rather than $wgUser on ContextSource extend.
* Should use $this->getRequest() rather than $wgRequest on ContextSource extend.
*/
namespace
MediaWiki\Sniffs\Usage
;
use
PHP_CodeSniffer\Files\File
;
use
PHP_CodeSniffer\Sniffs\Sniff
;
class
ExtendClassUsageSniff
implements
Sniff
{
private
const
MSG_MAP
=
[
T_FUNCTION
=>
'function'
,
T_VARIABLE
=>
'variable'
];
/**
* List of globals which cannot be used together with getConfig() because most
* of these are objects. They are excluded from reporting of this sniff.
*/
private
const
NON_CONFIG_GLOBALS_MEDIAWIKI_CORE
=
[
'$wgAuth'
,
'$wgConf'
,
'$wgContLang'
,
'$wgLang'
,
'$wgMemc'
,
'$wgOut'
,
'$wgParser'
,
'$wgRequest'
,
'$wgTitle'
,
'$wgUser'
,
'$wgVersion'
,
'$wgInitialSessionId'
,
// special global from WebStart.php
'$IP'
,
// special global from entry points (index.php, load.php, api.php, etc.)
'$mediaWiki'
,
];
/**
* Allow extensions add to the above list of non-config globals via .phpcs.xml
* @var string[]
*/
public
array
$nonConfigGlobals
=
[];
private
const
CHECK_CONFIG
=
[
// All extended class name. Map of extended class name to the checklist that
// should be used.
// Note that the SpecialPage class does NOT actually extend ContextSource,
// but all of the checks for ContextSource here also apply equally to SpecialPage
'extendsCls'
=>
[
'ContextSource'
=>
'ContextSource'
,
'SpecialPage'
=>
'ContextSource'
,
// Subclasses of ContextSource
'ApiBase'
=>
'ContextSource'
,
'ApiQueryBase'
=>
'ContextSource'
,
'ApiQueryGeneratorBase'
=>
'ContextSource'
,
'ApiQueryRevisionsBase'
=>
'ContextSource'
,
'DifferenceEngine'
=>
'ContextSource'
,
'HTMLForm'
=>
'ContextSource'
,
'IndexPager'
=>
'ContextSource'
,
'Skin'
=>
'ContextSource'
,
// Subclasses of SpecialPage
'AuthManagerSpecialPage'
=>
'ContextSource'
,
'FormSpecialPage'
=>
'ContextSource'
,
'ImageQueryPage'
=>
'ContextSource'
,
'IncludableSpecialPage'
=>
'ContextSource'
,
'PageQueryPage'
=>
'ContextSource'
,
'QueryPage'
=>
'ContextSource'
,
'UnlistedSpecialPage'
=>
'ContextSource'
,
'WantedQueryPage'
=>
'ContextSource'
,
// Subclasses of IndexPager
'AlphabeticPager'
=>
'ContextSource'
,
'RangeChronologicalPager'
=>
'ContextSource'
,
'ReverseChronologicalPager'
=>
'ContextSource'
,
'TablePager'
=>
'ContextSource'
,
],
// All details of usage need to be check.
'checkList'
=>
[
// Checklist name, usually the extended class
'ContextSource'
=>
[
[
// The check content.
'content'
=>
'wfMessage'
,
// The content shows on report message.
'msg_content'
=>
'wfMessage()'
,
// The check content code.
'code'
=>
T_FUNCTION
,
// The expected content.
'expect_content'
=>
'$this->msg()'
,
// The expected content code.
'expect_code'
=>
T_FUNCTION
],
[
'content'
=>
'$wgUser'
,
'msg_content'
=>
'$wgUser'
,
'code'
=>
T_VARIABLE
,
'expect_content'
=>
'$this->getUser()'
,
'expect_code'
=>
T_FUNCTION
],
[
'content'
=>
'$wgRequest'
,
'msg_content'
=>
'$wgRequest'
,
'code'
=>
T_VARIABLE
,
'expect_content'
=>
'$this->getRequest()'
,
'expect_code'
=>
T_FUNCTION
],
[
'content'
=>
'$wgOut'
,
'msg_content'
=>
'$wgOut'
,
'code'
=>
T_VARIABLE
,
'expect_content'
=>
'$this->getOutput()'
,
'expect_code'
=>
T_FUNCTION
],
[
'content'
=>
'$wgLang'
,
'msg_content'
=>
'$wgLang'
,
'code'
=>
T_VARIABLE
,
'expect_content'
=>
'$this->getLanguage()'
,
'expect_code'
=>
T_FUNCTION
],
]
]
];
/**
* @inheritDoc
*/
public
function
register
():
array
{
return
[
T_CLASS
];
}
/**
* @param File $phpcsFile
* @param int $stackPtr The current token index.
* @return void
*/
public
function
process
(
File
$phpcsFile
,
$stackPtr
)
{
$extClsContent
=
$phpcsFile
->
findExtendedClassName
(
$stackPtr
);
if
(
$extClsContent
===
false
)
{
// No extends token found
return
;
}
// Ignore namespace separator at the beginning
$extClsContent
=
ltrim
(
$extClsContent
,
'
\\
'
);
// This should be replaced with a mechanism that check if
// the base class is in the list of restricted classes
if
(
!
isset
(
self
::
CHECK_CONFIG
[
'extendsCls'
][
$extClsContent
]
)
)
{
return
;
}
$tokens
=
$phpcsFile
->
getTokens
();
$currToken
=
$tokens
[
$stackPtr
];
$nonConfigGlobals
=
array_flip
(
array_merge
(
self
::
NON_CONFIG_GLOBALS_MEDIAWIKI_CORE
,
$this
->
nonConfigGlobals
)
);
$checkListName
=
self
::
CHECK_CONFIG
[
'extendsCls'
][
$extClsContent
];
$extClsCheckList
=
self
::
CHECK_CONFIG
[
'checkList'
][
$checkListName
];
// Loop over all tokens of the class to check each function
$i
=
$currToken
[
'scope_opener'
];
$end
=
$currToken
[
'scope_closer'
];
$eligibleFunc
=
null
;
$endOfGlobal
=
null
;
while
(
$i
!==
false
&&
$i
<
$end
)
{
$iToken
=
$tokens
[
$i
];
if
(
$iToken
[
'code'
]
===
T_FUNCTION
)
{
$eligibleFunc
=
null
;
// If this is a function, make sure it's eligible
// (i.e. not static or abstract, and has a body).
$methodProps
=
$phpcsFile
->
getMethodProperties
(
$i
);
$isStaticOrAbstract
=
$methodProps
[
'is_static'
]
||
$methodProps
[
'is_abstract'
];
$hasBody
=
isset
(
$iToken
[
'scope_opener'
]
)
&&
isset
(
$iToken
[
'scope_closer'
]
);
if
(
!
$isStaticOrAbstract
&&
$hasBody
)
{
$funcNamePtr
=
$phpcsFile
->
findNext
(
T_STRING
,
$i
);
$eligibleFunc
=
[
'name'
=>
$tokens
[
$funcNamePtr
][
'content'
],
'scope_start'
=>
$iToken
[
'scope_opener'
],
'scope_end'
=>
$iToken
[
'scope_closer'
]
];
}
}
if
(
$eligibleFunc
!==
null
&&
$i
>
$eligibleFunc
[
'scope_start'
]
&&
$i
<
$eligibleFunc
[
'scope_end'
]
)
{
// Inside eligible function, check the
// current token against the checklist
foreach
(
$extClsCheckList
as
$value
)
{
$condition
=
false
;
if
(
$value
[
'code'
]
===
T_FUNCTION
&&
strcasecmp
(
$iToken
[
'content'
],
$value
[
'content'
]
)
===
0
)
{
$condition
=
true
;
}
if
(
$value
[
'code'
]
===
T_VARIABLE
&&
$iToken
[
'content'
]
===
$value
[
'content'
]
)
{
$condition
=
true
;
}
if
(
$condition
)
{
$phpcsFile
->
addWarning
(
'Should use %s %s rather than %s %s'
,
$i
,
'FunctionVarUsage'
,
[
self
::
MSG_MAP
[
$value
[
'expect_code'
]],
$value
[
'expect_content'
],
self
::
MSG_MAP
[
$value
[
'code'
]],
$value
[
'msg_content'
]
]
);
}
}
// Handle globals to check for use of getConfig()
if
(
$iToken
[
'code'
]
===
T_GLOBAL
)
{
$endOfGlobal
=
$phpcsFile
->
findEndOfStatement
(
$i
,
T_COMMA
);
}
elseif
(
$endOfGlobal
!==
null
&&
$i
>=
$endOfGlobal
)
{
$endOfGlobal
=
null
;
}
if
(
$endOfGlobal
!==
null
&&
$iToken
[
'code'
]
===
T_VARIABLE
&&
!
isset
(
$nonConfigGlobals
[
$iToken
[
'content'
]]
)
)
{
$phpcsFile
->
addWarning
(
'Should use function %s rather than global %s'
,
$i
,
'FunctionConfigUsage'
,
[
'$this->getConfig()->get()'
,
$iToken
[
'content'
]
]
);
}
}
// Jump to the next function
if
(
$eligibleFunc
===
null
||
$i
>=
$eligibleFunc
[
'scope_end'
]
)
{
$start
=
$eligibleFunc
===
null
?
$i
:
$eligibleFunc
[
'scope_end'
];
$i
=
$phpcsFile
->
findNext
(
T_FUNCTION
,
$start
+
1
,
$end
);
continue
;
}
// Find next token to work with
$i
=
$phpcsFile
->
findNext
(
[
T_STRING
,
T_VARIABLE
,
T_FUNCTION
,
T_GLOBAL
],
$i
+
1
,
$end
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 21:32 (1 d, 9 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
68/09/942a658c31acbbbef99e6af28321
Default Alt Text
ExtendClassUsageSniff.php (7 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment