plugins/codeclimate/engine

Summary

Maintainability
Test Coverage
#!/usr/bin/env php
<?php declare(strict_types = 1);

// @phan-file-suppress PhanPluginRemoveDebugAny
require_once(__DIR__ . '/../../src/requirements.php');

require_once(__DIR__ . '/../../src/Phan/Bootstrap.php');

use Phan\CLI;
use Phan\Config;
use Phan\Issue;
use Phan\Output\Collector\BufferingCollector;
use Phan\Output\Filter\CategoryIssueFilter;
use Phan\Output\Filter\ChainedIssueFilter;
use Phan\Output\Filter\FileIssueFilter;
use Phan\Output\Filter\MinimumSeverityFilter;
use Phan\Output\PrinterFactory;
use Symfony\Component\Console\Output\ConsoleOutput;
use Phan\Phan;

call_user_func(static function () : void {
    // Create our CLI interface and load arguments
    chdir('/code');
    $cli = CLI::fromArgv();

    // Obtain the config
    $config_path = '/config.json';
    if (!is_file($config_path)) {
        fwrite(STDERR, "Could not find '$config_path'" . PHP_EOL);
        exit(1);
    }
    // @phan-suppress-next-line PhanPossiblyFalseTypeArgumentInternal
    $codeclimate_config = json_decode(file_get_contents($config_path), true);

    $inner_config = $codeclimate_config['config'] ?? [];

    // Parse the config
    if (isset($inner_config['minimum-severity'])) {
        $minimum_severity = (int) $inner_config['minimum-severity'];
    } else {
        $minimum_severity = 0;
    }

    $mask = -1;

    if ($inner_config['ignore-undeclared'] ?? false) {
        $mask &= ~Issue::CATEGORY_UNDEFINED;
    }

    if (isset($inner_config['quick'])) {
        Config::setValue('quick_mode', (bool)$inner_config['quick']);
    }

    if (isset($inner_config['backward-compatibility-checks'])) {
        Config::setValue('backward_compatibility_checks', (bool)$inner_config['backward-compatibility-checks']);
    }

    if (isset($inner_config['dead-code-detection'])) {
        Config::setValue('dead_code_detection', (bool)$inner_config['dead-code-detection']);
    }

    if (isset($inner_config['file_extensions'])) {
        $file_extensions = explode(",", $inner_config['file_extensions']);
    } else {
        $file_extensions = ['php'];
    }

    $include_paths = [];

    /**
     * @param string $file_path
     */
    $queue_file = static function (string $file_path) use (&$include_paths, $file_extensions) : void {
        $file_info = new SplFileInfo($file_path);

        if (in_array($file_info->getExtension(), $file_extensions, true)) {
            $include_paths[] = realpath($file_path);
        }
    };

    // Todo: wrap with `-l` console logic instead of custom function
    $queue_with_include_paths = static function (string $dir_path) use (&$queue_with_include_paths, &$queue_file) : void {
        foreach (scandir($dir_path) as $f) {
            if ($f !== '.' and $f !== '..') {
                if (is_dir("$dir_path/$f")) {
                    $queue_with_include_paths("$dir_path/$f");
                } else {
                    $queue_file("$dir_path/$f");
                }
            }
        }
    };

    foreach ($codeclimate_config['include_paths'] ?? [] as $path) {
        if (is_dir('/code/' . $path)) {
            $queue_with_include_paths('/code/' . $path);
        } else {
            $queue_file("/code/$path");
        }
    }

    // skip run if there are no paths
    if (empty($include_paths)) {
        exit();
    }

    $output = new ConsoleOutput();
    $factory = new PrinterFactory();
    Config::setValue('markdown_issue_messages', true);
    $printer = $factory->getPrinter('codeclimate', $output);
    Phan::setPrinter($printer);

    $filter = new ChainedIssueFilter([
        new FileIssueFilter(new Phan()),
        new MinimumSeverityFilter($minimum_severity),
        new CategoryIssueFilter($mask)
    ]);
    $collector = new BufferingCollector($filter);
    Phan::setIssueCollector($collector);

    // If .phan/config.php specified a list of files to parse and analyze, prefer those over codeclimate.
    $file_list_from_cli = $cli->getFileList();
    if (count($file_list_from_cli) > 0) {
        $force_codeclimate_filelist = $inner_config['force-codeclimate-filelist'] ?? false;
        if (!$force_codeclimate_filelist) {
            $include_paths = $file_list_from_cli;
        }
    }
    // Generate codebase info after parsing configs (e.g. included_extension_subset)
    $code_base = require(__DIR__ . '/../../src/codebase.php');

    // Analyze the file list provided via the CLI
    // @phan-suppress-next-line PhanThrowTypeAbsentForCall if this throws, it's not recoverable.
    Phan::analyzeFileList($code_base, /** @return string[] */ static function () use($include_paths) : array {
        return $include_paths;
    });
});