file_provider = new Provider\FakeFileProvider(); $this->project_checker = new \Psalm\Checker\ProjectChecker( new TestConfig(), $this->file_provider, new Provider\FakeParserCacheProvider() ); $this->project_checker->getCodebase()->collectReferences(); } /** * @dataProvider providerFileCheckerValidCodeParse * * @param string $code * @param array $error_levels * * @return void */ public function testValidCode($code, array $error_levels = []) { $test_name = $this->getName(); 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->getCodebase()->classlikes->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->getName(), '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->getCodebase()->classlikes->checkClassReferences(); } /** * @return array */ public function providerFileCheckerValidCodeParse() { return [ 'arrayOffset' => [ ' [ ' [ ' [ 'PossiblyUndefinedVariable', 'MixedArrayAccess', 'MixedOperand', 'MixedAssignment', ], ], 'ifInFunctionWithReference' => [ ' [ ' [ ' [ ' [ ' [ 'modify($name, $args[0]); } } private function modify(string $name, string $value): void { call_user_func(array($this, "modify_" . $name), $value); } public function modifyFoo(string $value): void { $this->value = $value; } public function getFoo() : string { return $this->value; } } $m = new A(); $m->foo("value"); $m->modifyFoo("value2"); echo $m->getFoo();', ], 'usedTraitMethod' => [ 'foo(); (new B)->foo();', ], 'usedInterfaceMethod' => [ 'foo();', ], 'dummyByRefVar' => [ ' [ ' [ ' 3); }', ], 'whileTypeChangedInIfAndContinueWithReference' => [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ ' [ '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) {} }', ], 'ifInReturnBlock' => [ ' [ 'passedByRef($b); }', ], 'constructorIsUsed' => [ 'foo(); } private function foo() : void {} } $a = new A(); echo (bool) $a;', ], ]; } /** * @return array */ public function providerFileCheckerInvalidCodeParse() { return [ 'function' => [ ' 'UnusedVariable', ], 'ifInFunctionWithoutReference' => [ ' 'UnusedVariable', ], 'varInNestedAssignmentWithoutReference' => [ ' 'UnusedVariable', ], 'varInSecondNestedAssignmentWithoutReference' => [ ' 'UnusedVariable', ], 'varReassignedInBothBranchesOfIf' => [ ' 'UnusedVariable', ], 'varReassignedInNestedBranchesOfIf' => [ ' 'UnusedVariable', ], 'ifVarReassignedInBranch' => [ ' 'UnusedVariable', ], 'elseVarReassignedInBranchAndNoReference' => [ ' 'UnusedVariable', ], 'switchVarReassignedInBranch' => [ ' 'UnusedVariable', ], 'switchVarReassignedInBranchWithDefault' => [ ' 'UnusedVariable', ], 'unusedListVar' => [ ' 'UnusedVariable', ], 'unusedPreForVar' => [ ' 'UnusedVariable', ], 'unusedIfInReturnBlock' => [ ' 'UnusedVariable', ], 'unusedIfVarInBranch' => [ ' 'UnusedVariable', ], 'throwWithMessageCallAndAssignmentAndNoReference' => [ 'getMessage(); } }', 'error_message' => 'UnusedVariable', ], 'whileTypeChangedInIfWithoutReference' => [ ' 'UnusedVariable', ], 'whileTypeChangedInIfAndContinueWithoutReference' => [ ' 'UnusedVariable', ], 'whileReassignedInIfAndContinueWithoutReferenceAfter' => [ ' 'UnusedVariable', ], 'whileReassignedInIfAndContinueWithoutReference' => [ ' 'UnusedVariable', ], 'unusedClass' => [ ' 'UnusedClass', ], 'publicUnusedMethod' => [ ' 'PossiblyUnusedMethod', ], 'possiblyUnusedParam' => [ 'foo(4);', 'error_message' => 'PossiblyUnusedParam', ], 'unusedParam' => [ ' 'UnusedParam', ], 'possiblyUnusedProperty' => [ ' 'PossiblyUnusedProperty', 'error_levels' => ['UnusedVariable'], ], 'unusedProperty' => [ ' 'UnusedProperty', 'error_levels' => ['UnusedVariable'], ], 'privateUnusedMethod' => [ ' 'UnusedMethod', ], 'unevaluatedCode' => [ ' 'UnevaluatedCode', ], ]; } }