file_provider = new Provider\FakeFileProvider(); $this->project_checker = new \Psalm\Checker\ProjectChecker( new TestConfig(), new \Psalm\Provider\Providers( $this->file_provider, new Provider\FakeParserCacheProvider() ) ); $this->project_checker->getCodebase()->reportUnusedCode(); } /** * @dataProvider providerFileCheckerValidCodeParse * * @param string $code * @param array $error_levels * * @return void */ public function testValidCode($code, array $error_levels = []) { $test_name = $this->getTestName(); if (strpos($test_name, 'PHP7-') !== false) { if (version_compare(PHP_VERSION, '7.0.0dev', '<')) { $this->markTestSkipped('Test case requires PHP 7.'); return; } } elseif (strpos($test_name, 'SKIPPED-') !== false) { $this->markTestSkipped('Skipped due to a bug.'); } $file_path = self::$src_dir_path . 'somefile.php'; $this->addFile( $file_path, $code ); foreach ($error_levels as $error_level) { $this->project_checker->config->setCustomErrorLevel($error_level, Config::REPORT_SUPPRESS); } $context = new Context(); $context->collect_references = true; $this->analyzeFile($file_path, $context); $this->project_checker->checkClassReferences(); } /** * @dataProvider providerFileCheckerInvalidCodeParse * * @param string $code * @param string $error_message * @param array $error_levels * * @return void */ public function testInvalidCode($code, $error_message, $error_levels = []) { if (strpos($this->getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); } $this->expectException('\Psalm\Exception\CodeException'); $this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/'); $file_path = self::$src_dir_path . 'somefile.php'; foreach ($error_levels as $error_level) { $this->project_checker->config->setCustomErrorLevel($error_level, Config::REPORT_SUPPRESS); } $this->addFile( $file_path, $code ); $context = new Context(); $context->collect_references = true; $this->analyzeFile($file_path, $context); $this->project_checker->checkClassReferences(); } /** * @return array */ public function providerFileCheckerValidCodeParse() { return [ 'arrayOffset' => [ ' [ ' [ ' [ 'PossiblyUndefinedVariable', 'MixedArrayAccess', 'MixedOperand', 'MixedAssignment', ], ], 'varDefinedInIfWithReference' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' 3);', ], 'loopTypeChangedInIfAndContinueWithReference' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ 'getMessage(); } }', ], 'throwWithMessageCallAndAssignmentAndReference' => [ 'getMessage(); } if ($s) {} }', ], 'throwWithMessageCallAndAssignmentInCatchAndReference' => [ 'getMessage(); $s = "hello"; } if ($s) {} }', ], 'throwWithMessageCallAndAssignmentInTryAndCatchAndReference' => [ 'getMessage(); $s = "hello"; } if ($s) {} }', ], 'throwWithMessageCallAndNestedAssignmentInTryAndCatchAndReference' => [ 'getMessage(); $t = "hello"; } if ($t) { $s = $t; } } if ($s) {} }', ], 'throwWithReturnInOneCatch' => [ 'getMessage(); $s = false; } catch (Exception $e) { return; } if ($s) {} }', ], 'loopWithIfRedefinition' => [ ' [ 'passedByRef($b); }', ], 'usedMethodCallVariable' => [ '$methodName()] = true; } return $ret; }', 'error_levels' => [ 'MixedAssignment', 'MixedMethodCall', 'MixedArrayOffset', ], ], 'globalVariableUsage' => [ ' [ ' [ ' 0) { return "value"; } throw new Exception("fail"); } function main() : void { try { $s = example_string(); if (!$s) { echo "Failed to get string\n"; } } catch (Exception $e) { $s = "fallback"; } printf("s is %s\n", $s); }', ], 'loopTypeChangedInIfAndBreakWithReference' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' $options */ public function __construct(array $options) { $this->setOptions($options); } /** * @param array $options */ protected function setOptions(array $options): void { foreach ($options as $key => $value) { $normalized = ucfirst($key); $method = "set" . $normalized; if (method_exists($this, $method)) { $this->$method($value); } } } } new A(["bar" => "bat"]);', ], 'instanceofVarUse' => [ ' [ ' 0); echo $i;', ], 'callableReferencesItself' => [ ' [ ' 'UnusedVariable', ], 'unusedVarWithAdditionOp' => [ ' 'UnusedVariable', ], 'unusedVarWithConditionalAdditionOp' => [ ' 'UnusedVariable', ], 'unusedVarWithConditionalAddition' => [ ' 'UnusedVariable', ], 'unusedVarWithIncrement' => [ ' 'UnusedVariable', ], 'unusedVarWithConditionalIncrement' => [ ' 'UnusedVariable', ], 'ifInBothBranchesWithoutReference' => [ ' 'UnusedVariable', ], 'varInNestedAssignmentWithoutReference' => [ ' 'UnusedVariable', ], 'varInSecondNestedAssignmentWithoutReference' => [ ' 'UnusedVariable', ], 'varReassignedInBothBranchesOfIf' => [ ' 'UnusedVariable', ], 'varReassignedInNestedBranchesOfIf' => [ ' 'UnusedVariable', ], 'ifVarReassignedInBranchWithNoUse' => [ ' 'UnusedVariable', ], 'elseVarReassignedInBranchAndNoReference' => [ ' 'UnusedVariable', ], 'switchVarReassignedInBranch' => [ ' 'UnusedVariable', ], 'switchVarReassignedInBranchWithDefault' => [ ' 'UnusedVariable', ], 'switchVarReassignedInAllBranches' => [ ' 'UnusedVariable', ], 'unusedListVar' => [ ' 'UnusedVariable', ], 'unusedPreForVar' => [ ' 'UnusedVariable', ], 'unusedIfInReturnBlock' => [ ' 'UnusedVariable', ], 'unusedIfVarInBranch' => [ ' 'UnusedVariable', ], 'throwWithMessageCallAndAssignmentAndNoReference' => [ 'getMessage(); } }', 'error_message' => 'UnusedVariable', ], 'throwWithMessageCallAndAssignmentInCatchAndNoReference' => [ 'getMessage(); $s = "hello"; } }', 'error_message' => 'UnusedVariable', ], 'throwWithMessageCallAndNestedAssignmentInTryAndCatchAndNoReference' => [ 'getMessage(); $t = "hello"; } if ($t) { $s = $t; } } }', 'error_message' => 'UnusedVariable', ], 'throwWithReturnInOneCatchAndNoReference' => [ 'getMessage(); $s = false; } catch (Exception $e) { return; } }', 'error_message' => 'UnusedVariable', ], 'loopTypeChangedInIfWithoutReference' => [ ' 'UnusedVariable', ], 'loopTypeChangedInIfAndContinueWithoutReference' => [ ' 'UnusedVariable', ], 'loopReassignedInIfAndContinueWithoutReferenceAfter' => [ ' 'UnusedVariable', ], 'loopReassignedInIfAndContinueWithoutReference' => [ ' 'UnusedVariable', ], 'unusedConditionalCode' => [ ' 'UnusedVariable', ], 'varDefinedInIfWithoutReference' => [ ' 'UnusedVariable', ], 'SKIPPED-byrefInForeachLoopWithoutReference' => [ ' 'UnusedVariable', ], 'loopSetIfNullWithBreakWithoutReference' => [ ' 'UnusedVariable', ], 'loopSetIfNullWithBreakWithoutReference' => [ ' 'UnusedVariable', ], 'loopSetIfNullWithContinueWithoutReference' => [ ' 'UnusedVariable', ], 'loopSetIfNullWithContinueWithoutReference' => [ ' 'UnusedVariable', ], 'loopAssignmentAfterReferenceWithBreak' => [ ' 'UnusedVariable', ], 'loopAssignmentAfterReferenceWithBreakInIf' => [ ' 'UnusedVariable', ], 'switchVarConditionalAssignmentWithoutReference' => [ ' 'UnusedVariable', ], 'switchInIf' => [ ' 'UnusedVariable', ], 'reusedKeyVar' => [ ' "foo.foo"]; foreach ($arr as $key => $v) { list($key) = explode(".", $v); echo $key; }', 'error_message' => 'UnusedVariable', ], ]; } }