Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4102369
PossiblyStaticMethodPlugin.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
PossiblyStaticMethodPlugin.php
View Options
<?php
declare
(
strict_types
=
1
);
use
ast\Node
;
use
Phan\AST\ContextNode
;
use
Phan\CodeBase
;
use
Phan\Config
;
use
Phan\Language\Element\AddressableElement
;
use
Phan\Language\Element\Func
;
use
Phan\Language\Element\FunctionInterface
;
use
Phan\Language\Element\Method
;
use
Phan\Language\ElementContext
;
use
Phan\PluginV3
;
use
Phan\PluginV3\AnalyzeFunctionCapability
;
use
Phan\PluginV3\AnalyzeMethodCapability
;
use
Phan\PluginV3\FinalizeProcessCapability
;
/**
* This file checks if a method can be made static without causing any errors.
*
* It hooks into these events:
*
* - analyzeMethod
* Once all classes are parsed, this method will be called
* on every method in the code base
*
* - analyzeFunction
* Once all classes and functions are parsed, this method will be called
* on every function in the code base
*
* - finalizeProcess
* Once the analysis phase is complete, this method will be called
*
* A plugin file must
*
* - Contain a class that inherits from \Phan\PluginV3
*
* - End by returning an instance of that class.
*
* It is assumed without being checked that plugins aren't
* mangling state within the passed code base or context.
*
* Note: When adding new plugins,
* add them to the corresponding section of README.md
*/
final
class
PossiblyStaticMethodPlugin
extends
PluginV3
implements
AnalyzeFunctionCapability
,
AnalyzeMethodCapability
,
FinalizeProcessCapability
{
/**
* @var array<string,FunctionInterface> a list of functions and methods where checks were postponed
*/
private
$methods_for_postponed_analysis
=
[];
/**
* @param CodeBase $code_base
* The code base in which the method exists
*
* @param FunctionInterface $method
* A function or method being analyzed
*/
private
static
function
analyzePostponedMethod
(
CodeBase
$code_base
,
FunctionInterface
$method
):
void
{
if
(
$method
instanceof
Method
)
{
if
(
$method
->
isOverride
())
{
// This method can't be static unless its parent is also static.
return
;
}
if
(
$method
->
isOverriddenByAnother
())
{
// Changing this method causes a fatal error.
return
;
}
}
$stmts_list
=
self
::
getStatementListToAnalyze
(
$method
);
if
(
$stmts_list
===
null
)
{
// check for abstract methods, etc.
return
;
}
if
(
self
::
nodeCanBeStatic
(
$code_base
,
$method
,
$stmts_list
))
{
if
(
$method
instanceof
Method
)
{
$visibility_upper
=
ucfirst
(
$method
->
getVisibilityName
());
self
::
emitIssue
(
$code_base
,
$method
->
getContext
(),
"PhanPluginPossiblyStatic{$visibility_upper}Method"
,
"$visibility_upper method {METHOD} can be static"
,
[
$method
->
getRepresentationForIssue
()]
);
}
else
{
self
::
emitIssue
(
$code_base
,
$method
->
getContext
(),
"PhanPluginPossiblyStaticClosure"
,
"{FUNCTION} can be static"
,
[
$method
->
getRepresentationForIssue
()]
);
}
}
}
/**
* @param FunctionInterface $method
* @return ?Node - returns null if there's no statement list to analyze
*/
private
static
function
getStatementListToAnalyze
(
FunctionInterface
$method
):
?
Node
{
$node
=
$method
->
getNode
();
if
(!
$node
)
{
return
null
;
}
return
$node
->
children
[
'stmts'
];
}
/**
* @param CodeBase $code_base
* The code base in which the method exists
*
* @param Node|int|string|float|null $node
* @return bool - returns true if the node allows its method to be static
*/
private
static
function
nodeCanBeStatic
(
CodeBase
$code_base
,
FunctionInterface
$method
,
$node
):
bool
{
if
(!(
$node
instanceof
Node
))
{
if
(
is_array
(
$node
))
{
foreach
(
$node
as
$child_node
)
{
if
(!
self
::
nodeCanBeStatic
(
$code_base
,
$method
,
$child_node
))
{
return
false
;
}
}
}
return
true
;
}
switch
(
$node
->
kind
)
{
case
ast\AST_VAR
:
if
(
$node
->
children
[
'name'
]
===
'this'
)
{
return
false
;
}
// Handle edge cases such as `${$this->varName}`
break
;
case
ast\AST_CLASS
:
case
ast\AST_FUNC_DECL
:
return
true
;
case
ast\AST_STATIC_CALL
:
if
(
self
::
isSelfOrParentCallUsingObject
(
$code_base
,
$method
,
$node
))
{
return
false
;
}
// Check code such as `static::someMethod($this->prop)`
break
;
case
ast\AST_CLOSURE
:
case
ast\AST_ARROW_FUNC
:
if
(
$node
->
flags
&
\ast\flags\MODIFIER_STATIC
)
{
return
true
;
}
break
;
}
foreach
(
$node
->
children
as
$child_node
)
{
if
(!
self
::
nodeCanBeStatic
(
$code_base
,
$method
,
$child_node
))
{
return
false
;
}
}
return
true
;
}
/**
* @param CodeBase $code_base
* The code base in which the calling instance method exists
*
* @param Node $node a node of kind ast\AST_STATIC_CALL
* (e.g. SELF::someMethod(), parent::someMethod(), SomeClass::staticMethod())
*
* @return bool true if the AST_STATIC_CALL node is really calling an instance method
*/
private
static
function
isSelfOrParentCallUsingObject
(
CodeBase
$code_base
,
FunctionInterface
$method
,
Node
$node
):
bool
{
$class_node
=
$node
->
children
[
'class'
];
if
(!(
$class_node
instanceof
Node
&&
$class_node
->
kind
===
ast\AST_NAME
))
{
return
false
;
}
$class_name
=
$class_node
->
children
[
'name'
];
if
(!
is_string
(
$class_name
))
{
return
false
;
}
if
(!
in_array
(
strtolower
(
$class_name
),
[
'self'
,
'parent'
],
true
))
{
return
false
;
}
$method_name
=
$node
->
children
[
'method'
];
if
(!
is_string
(
$method_name
))
{
// This is uninferable
return
true
;
}
if
(!
$method
instanceof
AddressableElement
)
{
// should be impossible
return
true
;
}
try
{
$method
=
(
new
ContextNode
(
$code_base
,
new
ElementContext
(
$method
),
$node
))->
getMethod
(
$method_name
,
true
,
false
);
}
catch
(
Exception
$_
)
{
// This might be an instance method if we don't know what it is
return
true
;
}
return
!
$method
->
isStatic
();
}
/**
* @param CodeBase $code_base @unused-param
* The code base in which the method exists
*
* @param Method $method
* A method being analyzed
*
* @override
*/
public
function
analyzeMethod
(
CodeBase
$code_base
,
Method
$method
):
void
{
// 1. Perform any checks that can be done immediately to rule out being able
// to convert this to a static method
if
(
$method
->
isStatic
())
{
// This is what we want.
return
;
}
if
(
$method
->
isMagic
())
{
// Magic methods can't be static.
return
;
}
if
(
$method
->
getFQSEN
()
!==
$method
->
getRealDefiningFQSEN
())
{
// Only warn once for the original definition of this method.
// Don't warn about subclasses inheriting this method.
return
;
}
$method_filter
=
Config
::
getValue
(
'plugin_config'
)[
'possibly_static_method_ignore_regex'
]
??
null
;
if
(
is_string
(
$method_filter
))
{
$fqsen_string
=
ltrim
((
string
)
$method
->
getFQSEN
(),
'
\\
'
);
if
(
preg_match
(
$method_filter
,
$fqsen_string
)
>
0
)
{
return
;
}
}
if
(!
$method
->
hasNode
())
{
// There's no body to check - This is abstract or can't be checked
return
;
}
$fqsen
=
$method
->
getFQSEN
();
// 2. Defer remaining checks until we have all the necessary information
// (is this method overridden/an override, is parent::foo() referring to a static or an instance method, etc.)
$this
->
methods_for_postponed_analysis
[(
string
)
$fqsen
]
=
$method
;
}
/**
* @param CodeBase $code_base @unused-param
* The code base in which the function exists
*
* @param Func $function
* A function being analyzed
* @override
*/
public
function
analyzeFunction
(
CodeBase
$code_base
,
Func
$function
):
void
{
if
(!
$function
->
isClosure
())
{
return
;
}
if
(
$function
->
isStatic
())
{
return
;
}
if
(!
$function
->
hasNode
())
{
// There's no body to check - This is abstract or can't be checked
return
;
}
// NOTE: The possibly_static_method_ignore_regex isn't used because there's no way to apply it to closures
$fqsen
=
$function
->
getFQSEN
();
// 2. Defer remaining checks until we have all the necessary information
// (is this method overridden/an override, is parent::foo() referring to a static or an instance method, etc.)
$this
->
methods_for_postponed_analysis
[(
string
)
$fqsen
]
=
$function
;
}
/**
* @param CodeBase $code_base
* The code base being analyzed
*
* @override
*/
public
function
finalizeProcess
(
CodeBase
$code_base
):
void
{
foreach
(
$this
->
methods_for_postponed_analysis
as
$method
)
{
self
::
analyzePostponedMethod
(
$code_base
,
$method
);
}
}
}
// Every plugin needs to return an instance of itself at the
// end of the file in which it's defined.
return
new
PossiblyStaticMethodPlugin
();
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 17:24 (3 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
60/37/33ecbfb94944fbd7ea1c80931b43
Default Alt Text
PossiblyStaticMethodPlugin.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment