2) { die('Too many arguments provided for psalm --init' . PHP_EOL); } if (isset($args[1])) { if (!preg_match('/^[1-8]$/', $args[1])) { die('Config strictness must be a number between 1 and 8 inclusive' . PHP_EOL); } $level = (int)$args[1]; } $source_dir = $args[0]; } try { $template_contents = Psalm\Config\Creator::getContents($current_dir, $source_dir, $level); } catch (Psalm\Exception\ConfigCreationException $e) { die($e->getMessage() . PHP_EOL); } if (!file_put_contents($current_dir . 'psalm.xml', $template_contents)) { die('Could not write to psalm.xml' . PHP_EOL); } exit('Config file created successfully. Please re-run psalm.' . PHP_EOL); } if (array_key_exists('v', $options)) { echo 'Psalm ' . PSALM_VERSION . PHP_EOL; exit; } $config = initialiseConfig($path_to_config, $current_dir, $output_format, $first_autoloader); if ($config->resolve_from_config_file) { $current_dir = $config->base_dir; chdir($current_dir); } if (isset($options['r']) && is_string($options['r'])) { $root_path = realpath($options['r']); if (!$root_path) { fwrite( STDERR, 'Could not locate root directory ' . $current_dir . DIRECTORY_SEPARATOR . $options['r'] . PHP_EOL ); exit(1); } $current_dir = $root_path . DIRECTORY_SEPARATOR; } $threads = isset($options['threads']) ? (int)$options['threads'] : 1; if ($threads === 1 && ini_get('pcre.jit') === '1' && PHP_OS === 'Darwin' && version_compare(PHP_VERSION, '7.3.0') >= 0 ) { echo( 'If you want to run Psalm as a language server, or run Psalm with' . PHP_EOL . 'multiple processes (--threads=4), beware:' . PHP_EOL . \Psalm\Internal\Fork\Pool::MAC_PCRE_MESSAGE . PHP_EOL . PHP_EOL ); } $ini_handler = new \Psalm\Internal\Fork\PsalmRestarter('PSALM'); if (isset($options['disable-extension'])) { if (is_array($options['disable-extension'])) { /** @psalm-suppress MixedAssignment */ foreach ($options['disable-extension'] as $extension) { if (is_string($extension)) { $ini_handler->disableExtension($extension); } } } elseif (is_string($options['disable-extension'])) { $ini_handler->disableExtension($options['disable-extension']); } } if ($threads > 1) { $ini_handler->disableExtension('grpc'); } $ini_handler->disableExtension('uopz'); $type_map_location = null; if (isset($options['generate-json-map']) && is_string($options['generate-json-map'])) { $type_map_location = $options['generate-json-map']; } // If XDebug is enabled, restart without it $ini_handler->check(); setlocale(LC_CTYPE, 'C'); if (isset($options['set-baseline'])) { if (is_array($options['set-baseline'])) { die('Only one baseline file can be created at a time' . PHP_EOL); } } $output_format = isset($options['output-format']) && is_string($options['output-format']) ? $options['output-format'] : \Psalm\Report::TYPE_CONSOLE; $paths_to_check = getPathsToCheck(isset($options['f']) ? $options['f'] : null); $plugins = []; if (isset($options['plugin'])) { $plugins = $options['plugin']; if (!is_array($plugins)) { $plugins = [$plugins]; } } $show_info = isset($options['show-info']) ? $options['show-info'] !== 'false' && $options['show-info'] !== '0' : true; $is_diff = isset($options['diff']); /** @var false|'always'|'auto' $find_unused_code */ $find_unused_code = false; if (isset($options['find-dead-code'])) { $options['find-unused-code'] = $options['find-dead-code']; } if (isset($options['find-unused-code'])) { if ($options['find-unused-code'] === 'always') { $find_unused_code = 'always'; } else { $find_unused_code = 'auto'; } } $find_references_to = isset($options['find-references-to']) && is_string($options['find-references-to']) ? $options['find-references-to'] : null; if (isset($options['shepherd'])) { if (is_string($options['shepherd'])) { $config->shepherd_host = $options['shepherd']; } $shepherd_plugin = __DIR__ . '/Psalm/Plugin/Shepherd.php'; if (!file_exists($shepherd_plugin)) { die('Could not find Shepherd plugin location ' . $shepherd_plugin . PHP_EOL); } $plugins[] = $shepherd_plugin; } if (isset($options['clear-cache'])) { $cache_directory = $config->getCacheDirectory(); Config::removeCacheDirectory($cache_directory); echo 'Cache directory deleted' . PHP_EOL; exit; } if (isset($options['clear-global-cache'])) { $cache_directory = $config->getGlobalCacheDirectory(); if ($cache_directory) { Config::removeCacheDirectory($cache_directory); echo 'Global cache directory deleted' . PHP_EOL; } exit; } // disable progressbar on CI if (isset($_SERVER['TRAVIS']) || isset($_SERVER['CIRCLECI']) || isset($_SERVER['APPVEYOR']) || isset($_SERVER['JENKINS_URL']) || isset($_SERVER['SCRUTINIZER']) || isset($_SERVER['GITLAB_CI']) ) { $options['no-progress'] = true; } $debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); $progress = $debug ? new DebugProgress() : (isset($options['no-progress']) ? new VoidProgress() : new DefaultProgress(!$config->error_baseline, $show_info)); if (isset($options['no-cache'])) { $providers = new Provider\Providers( new Provider\FileProvider ); } else { $no_reflection_cache = isset($options['no-reflection-cache']); $file_storage_cache_provider = $no_reflection_cache ? null : new Provider\FileStorageCacheProvider($config); $classlike_storage_cache_provider = $no_reflection_cache ? null : new Provider\ClassLikeStorageCacheProvider($config); $providers = new Provider\Providers( new Provider\FileProvider, new Provider\ParserCacheProvider($config), $file_storage_cache_provider, $classlike_storage_cache_provider, new Provider\FileReferenceCacheProvider($config) ); } $stdout_report_options = new \Psalm\Report\ReportOptions(); $stdout_report_options->use_color = !array_key_exists('m', $options); $stdout_report_options->show_info = $show_info; /** * @psalm-suppress PropertyTypeCoercion */ $stdout_report_options->format = $output_format; $stdout_report_options->show_snippet = !isset($options['show-snippet']) || $options['show-snippet'] !== "false"; $project_analyzer = new ProjectAnalyzer( $config, $providers, $stdout_report_options, ProjectAnalyzer::getFileReportOptions( isset($options['report']) && is_string($options['report']) ? [$options['report']] : [], isset($options['report-show-info']) ? $options['report-show-info'] !== 'false' && $options['report-show-info'] !== '0' : true ), $threads, $progress ); if (isset($options['php-version'])) { if (!is_string($options['php-version'])) { die('Expecting a version number in the format x.y' . PHP_EOL); } $project_analyzer->setPhpVersion($options['php-version']); } $project_analyzer->getCodebase()->diff_methods = isset($options['diff-methods']); if ($type_map_location) { $project_analyzer->getCodebase()->store_node_types = true; } $start_time = microtime(true); $config->visitComposerAutoloadFiles($project_analyzer, $progress); $now_time = microtime(true); $progress->debug('Visiting autoload files took ' . number_format($now_time - $start_time, 3) . 's' . "\n"); if (array_key_exists('debug-by-line', $options)) { $project_analyzer->debug_lines = true; } if ($config->find_unused_code) { $find_unused_code = 'auto'; } if ($find_references_to !== null) { $project_analyzer->getCodebase()->collectLocations(); $project_analyzer->show_issues = false; } if ($find_unused_code) { $project_analyzer->getCodebase()->reportUnusedCode($find_unused_code); } if ($config->find_unused_variables) { $project_analyzer->getCodebase()->reportUnusedVariables(); } if (isset($options['track-tainted-input'])) { $project_analyzer->trackTaintedInputs(); } /** @var string $plugin_path */ foreach ($plugins as $plugin_path) { $config->addPluginPath($plugin_path); } if ($paths_to_check === null) { $project_analyzer->check($current_dir, $is_diff); } elseif ($paths_to_check) { $project_analyzer->checkPaths($paths_to_check); } if ($find_references_to) { $project_analyzer->findReferencesTo($find_references_to); } if (isset($options['set-baseline']) && is_string($options['set-baseline'])) { if ($is_diff) { fwrite(STDERR, 'Cannot set baseline in --diff mode' . PHP_EOL); } else { fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL); ErrorBaseline::create( new \Psalm\Internal\Provider\FileProvider, $options['set-baseline'], IssueBuffer::getIssuesData(), $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) ); fwrite(STDERR, "Baseline saved to {$options['set-baseline']}."); /** @var string $configFile */ $configFile = Config::locateConfigFile($path_to_config ?? $current_dir); $configFileContents = $amendedConfigFileContents = file_get_contents($configFile); if ($config->error_baseline) { $amendedConfigFileContents = preg_replace( '/errorBaseline=".*?"/', "errorBaseline=\"{$options['set-baseline']}\"", $configFileContents ); } else { $endPsalmOpenTag = strpos($configFileContents, '>', (int)strpos($configFileContents, '", $endPsalmOpenTag, 1 ); } else { $amendedConfigFileContents = substr_replace( $configFileContents, " errorBaseline=\"{$options['set-baseline']}\">", $endPsalmOpenTag, 1 ); } } file_put_contents($configFile, $amendedConfigFileContents); fwrite(STDERR, PHP_EOL); } } $issue_baseline = []; if (isset($options['update-baseline'])) { if ($is_diff) { fwrite(STDERR, 'Cannot update baseline in --diff mode' . PHP_EOL); } else { $baselineFile = Config::getInstance()->error_baseline; if (empty($baselineFile)) { die('Cannot update baseline, because no baseline file is configured.' . PHP_EOL); } try { $issue_current_baseline = ErrorBaseline::read( new \Psalm\Internal\Provider\FileProvider, $baselineFile ); $total_issues_current_baseline = ErrorBaseline::countTotalIssues($issue_current_baseline); $issue_baseline = ErrorBaseline::update( new \Psalm\Internal\Provider\FileProvider, $baselineFile, IssueBuffer::getIssuesData(), $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) ); $total_issues_updated_baseline = ErrorBaseline::countTotalIssues($issue_baseline); $total_fixed_issues = $total_issues_current_baseline - $total_issues_updated_baseline; if ($total_fixed_issues > 0) { echo str_repeat('-', 30) . "\n"; echo $total_fixed_issues . ' errors fixed' . "\n"; } } catch (\Psalm\Exception\ConfigException $exception) { fwrite(STDERR, 'Could not update baseline file: ' . $exception->getMessage() . PHP_EOL); exit(1); } } } if (!empty(Config::getInstance()->error_baseline) && !isset($options['ignore-baseline'])) { try { $issue_baseline = ErrorBaseline::read( new \Psalm\Internal\Provider\FileProvider, (string)Config::getInstance()->error_baseline ); } catch (\Psalm\Exception\ConfigException $exception) { fwrite(STDERR, 'Error while reading baseline: ' . $exception->getMessage() . PHP_EOL); exit(1); } } if ($type_map_location) { $file_map = $providers->file_reference_provider->getFileMaps(); $name_file_map = []; $expected_references = []; foreach ($file_map as $file_path => $map) { $file_name = $config->shortenFileName($file_path); foreach ($map[0] as $map_parts) { $expected_references[$map_parts[1]] = true; } $map[2] = []; $name_file_map[$file_name] = $map; } $reference_dictionary = []; foreach ($providers->classlike_storage_provider->getAll() as $storage) { if (!$storage->location) { continue; } $fq_classlike_name = $storage->name; if (isset($expected_references[$fq_classlike_name])) { $reference_dictionary[$fq_classlike_name] = $storage->location->file_name . ':' . $storage->location->getLineNumber() . ':' . $storage->location->getColumn(); } foreach ($storage->methods as $method_name => $method_storage) { if (!$method_storage->location) { continue; } if (isset($expected_references[$fq_classlike_name . '::' . $method_name . '()'])) { $reference_dictionary[$fq_classlike_name . '::' . $method_name . '()'] = $method_storage->location->file_name . ':' . $method_storage->location->getLineNumber() . ':' . $method_storage->location->getColumn(); } } foreach ($storage->properties as $property_name => $property_storage) { if (!$property_storage->location) { continue; } if (isset($expected_references[$fq_classlike_name . '::$' . $property_name])) { $reference_dictionary[$fq_classlike_name . '::$' . $property_name] = $property_storage->location->file_name . ':' . $property_storage->location->getLineNumber() . ':' . $property_storage->location->getColumn(); } } } $type_map_string = json_encode(['files' => $name_file_map, 'references' => $reference_dictionary]); $providers->file_provider->setContents( $type_map_location, $type_map_string ); } IssueBuffer::finish( $project_analyzer, !$paths_to_check, $start_time, isset($options['stats']), $issue_baseline );