$files_to_check * @param array $files * @param bool $hoist_constants * @param array $error_levels * */ public function testValidInclude( array $files, array $files_to_check, $hoist_constants = false, array $error_levels = [] ): void { $codebase = $this->project_analyzer->getCodebase(); foreach ($files as $file_path => $contents) { $this->addFile($file_path, $contents); $codebase->scanner->addFilesToShallowScan([$file_path => $file_path]); } foreach ($files_to_check as $file_path) { $codebase->addFilesToAnalyze([$file_path => $file_path]); } $config = $codebase->config; $config->skip_checks_on_unresolvable_includes = true; foreach ($error_levels as $error_level) { $config->setCustomErrorLevel($error_level, \Psalm\Config::REPORT_SUPPRESS); } $codebase->scanFiles(); $config->hoist_constants = $hoist_constants; foreach ($files_to_check as $file_path) { $file_analyzer = new FileAnalyzer($this->project_analyzer, $file_path, $config->shortenFileName($file_path)); $file_analyzer->analyze(); } } /** * @dataProvider providerTestInvalidIncludes * * @param array $files_to_check * @param array $files * @param string $error_message */ public function testInvalidInclude( array $files, array $files_to_check, $error_message ): void { if (strpos($this->getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); } $codebase = $this->project_analyzer->getCodebase(); foreach ($files as $file_path => $contents) { $this->addFile($file_path, $contents); $codebase->scanner->addFilesToShallowScan([$file_path => $file_path]); } foreach ($files_to_check as $file_path) { $codebase->addFilesToAnalyze([$file_path => $file_path]); } $config = $codebase->config; $config->skip_checks_on_unresolvable_includes = false; $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); $codebase->scanFiles(); foreach ($files_to_check as $file_path) { $file_analyzer = new FileAnalyzer($this->project_analyzer, $file_path, $config->shortenFileName($file_path)); $file_analyzer->analyze(); } } /** * @return array,files_to_check:array}> */ public function providerTestValidIncludes(): array { return [ 'basicRequire' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => 'fooFoo(); } }', getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireSingleStringType' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => 'fooFoo(); } }', getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'nestedRequire' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' 'fooFoo(); } }', ], 'files_to_check' => [ getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'requireNamespace' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireFunction' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'namespacedRequireFunction' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireConstant' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireNamespacedWithUse' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'noInfiniteRequireLoop' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => 'fooFoo(); } } class C {} new D();', getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'analyzeAllClasses' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => 'fooFoo(); } } class C { public function barBar(): void { } }', getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => 'barBar(); } }', ], 'files_to_check' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'loopWithInterdependencies' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'variadicArgs' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'globalIncludedVar' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'returnNamespacedFunctionCallType' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' 'doThing(); } }', ], 'files_to_check' => [ getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'functionUsedElsewhere' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'closureInIncludedFile' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'hoistConstants' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => true, ], 'duplicateClasses' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => 'aa(); } }', getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => 'dd(); } }', ], 'files_to_check' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => false, 'error_levels' => ['DuplicateClass'], ], 'duplicateClassesProperty' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => false, 'error_levels' => ['DuplicateClass', 'MissingPropertyType'], ], 'functionsDefined' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'index.php' => ' ' ' 'x = 5; } }', ], 'files_to_check' => [ getcwd() . DIRECTORY_SEPARATOR . 'index.php', ], ], 'suppressMissingFile' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'nestedParentFile' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php', ], ], 'undefinedMethodAfterInvalidRequire' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'returnValue' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'noCrash' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'classes.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'user.php', ], ], ]; } /** * @return array,files_to_check:array,error_message:string}> */ public function providerTestInvalidIncludes(): array { return [ 'undefinedMethodInRequire' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => 'fooFo(); } }', getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedMethod', ], 'requireFunctionWithStrictTypes' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidArgument', ], 'requireFunctionWithStrictTypesInClass' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidArgument', ], 'requireFunctionWithWeakTypes' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidScalarArgument', ], 'requireFunctionWithStrictTypesButDocblockType' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidScalarArgument', ], 'namespacedRequireFunction' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedFunction', ], 'globalIncludedIncorrectVar' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], 'error_message' => 'UndefinedVariable', ], 'invalidTraitFunctionReturnInUncheckedFile' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidReturnType', ], 'invalidDoubleNestedTraitFunctionReturnInUncheckedFile' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file3.php' => ' ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], 'error_message' => 'InvalidReturnType', ], 'invalidTraitFunctionMissingNestedUse' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'A.php', getcwd() . DIRECTORY_SEPARATOR . 'B.php', ], 'error_message' => 'UndefinedTrait - A.php:3:33', ], 'SKIPPED-noHoistConstants' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], 'error_message' => 'UndefinedConstant', ], 'undefinedMethodAfterInvalidRequire' => [ 'files' => [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedFunction', ], ]; } }