mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Add support for @psalm-external-mutation-free
This commit is contained in:
parent
e8500e5e51
commit
bbde2d6239
@ -619,7 +619,7 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
|
||||
);
|
||||
}
|
||||
|
||||
if ($context->mutation_free || $codebase->find_unused_variables) {
|
||||
if ($context->mutation_free || $context->external_mutation_free || $codebase->find_unused_variables) {
|
||||
$callmap_function_pure = $function_id && $in_call_map
|
||||
? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args)
|
||||
: null;
|
||||
@ -628,7 +628,7 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
|
||||
&& !$function_storage->pure)
|
||||
|| ($callmap_function_pure === false)
|
||||
) {
|
||||
if ($context->mutation_free) {
|
||||
if ($context->mutation_free || $context->external_mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImpureFunctionCall(
|
||||
'Cannot call an impure function from a mutation-free context',
|
||||
|
@ -1226,7 +1226,23 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} elseif ($method_storage->mutation_free
|
||||
} elseif ($context->external_mutation_free
|
||||
&& !$method_storage->mutation_free
|
||||
&& $fq_class_name !== $context->self
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImpureMethodCall(
|
||||
'Cannot call an possibly-mutating method '
|
||||
. $method_id . ' from a mutation-free context',
|
||||
new CodeLocation($source, $stmt->name)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} elseif (($method_storage->mutation_free
|
||||
|| ($method_storage->external_mutation_free
|
||||
&& isset($stmt->var->external_mutation_free)))
|
||||
&& $codebase->find_unused_variables
|
||||
&& !$context->inside_conditional
|
||||
&& !$context->inside_unset
|
||||
|
@ -306,6 +306,11 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
||||
) {
|
||||
$storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
||||
|
||||
if ($storage->external_mutation_free) {
|
||||
/** @psalm-suppress UndefinedPropertyAssignment */
|
||||
$stmt->external_mutation_free = true;
|
||||
}
|
||||
|
||||
// if we're not calling this constructor via new static()
|
||||
if ($storage->abstract && !$can_extend) {
|
||||
if (IssueBuffer::accepts(
|
||||
|
@ -38,7 +38,15 @@ class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract implements P
|
||||
|| $node instanceof PhpParser\Node\Expr\YieldFrom
|
||||
|| $node instanceof PhpParser\Node\Expr\New_
|
||||
) {
|
||||
if ($node instanceof PhpParser\Node\Expr\FuncCall && isset($node->pure)) {
|
||||
if (($node instanceof PhpParser\Node\Expr\FuncCall
|
||||
|| $node instanceof PhpParser\Node\Expr\MethodCall
|
||||
|| $node instanceof PhpParser\Node\Expr\StaticCall)
|
||||
&& isset($node->pure)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($node instanceof PhpParser\Node\Expr\New_ && isset($node->external_mutation_free)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -510,6 +510,49 @@ class UnusedVariableManipulationTest extends FileManipulationTest
|
||||
['UnusedVariable'],
|
||||
true,
|
||||
],
|
||||
|
||||
'removeLongUnusedAssignment' => [
|
||||
'<?php
|
||||
/**
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
class A {
|
||||
private string $foo;
|
||||
|
||||
public function __construct(string $foo) {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoo(string $foo) : void {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
function foo() : void {
|
||||
$a = (new A("hello"))->setFoo("goodbye");
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
class A {
|
||||
private string $foo;
|
||||
|
||||
public function __construct(string $foo) {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoo(string $foo) : void {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
function foo() : void {
|
||||
}',
|
||||
'7.1',
|
||||
['UnusedVariable'],
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -552,6 +552,30 @@ class UnusedCodeTest extends TestCase
|
||||
|
||||
(new Foo)->bar();'
|
||||
],
|
||||
'usedMethodCallForExternalMutationFreeClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
class A {
|
||||
private string $foo;
|
||||
|
||||
public function __construct(string $foo) {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoo(string $foo) : void {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function getFoo() : string {
|
||||
return $this->foo;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A("hello");
|
||||
$a->setFoo($a->getFoo() . "cool");',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -747,6 +771,28 @@ class UnusedCodeTest extends TestCase
|
||||
(new A())->handle();',
|
||||
'error_message' => 'UnusedProperty',
|
||||
],
|
||||
'unusedMethodCallForExternalMutationFreeClass' => [
|
||||
'<?php
|
||||
/**
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
class A {
|
||||
private string $foo;
|
||||
|
||||
public function __construct(string $foo) {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
|
||||
public function setFoo(string $foo) : void {
|
||||
$this->foo = $foo;
|
||||
}
|
||||
}
|
||||
|
||||
function foo() : void {
|
||||
(new A("hello"))->setFoo("goodbye");
|
||||
}',
|
||||
'error_message' => 'UnusedMethodCall',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user