Qual: Fix apstats & add phan (#28127)

# Qual: Fix apstats & add phan

I fixed several issues that I ran into when running apstats.php
locally to test the phan integration.

So this commit only proposes the updates to apstats.php.

I added a test that disables phan when its configurations is not
found so it should be fine to integration in develop.
This commit is contained in:
MDW 2024-02-12 02:01:51 +01:00 committed by GitHub
parent ed19e0603f
commit 1c452bc86e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,6 +2,7 @@
<?php
/*
* Copyright (C) 2023 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -41,15 +42,20 @@ define('VERSION', "1.0");
$phpstanlevel = 3;
// Include Dolibarr environment
require_once $path.'../../htdocs/master.inc.php';
if (!is_readable("{$path}../../htdocs/config/config.php")) {
define("DOL_DOCUMENT_ROOT", __DIR__."/../../htdocs");
} else {
require_once $path.'../../htdocs/master.inc.php';
}
require_once $path.'../../htdocs/core/lib/files.lib.php';
require_once $path.'../../htdocs/core/lib/functions.lib.php';
require_once $path.'../../htdocs/core/lib/geturl.lib.php';
print '***** '.constant('PRODUCT').' - '.constant('VERSION').' *****'."\n";
if (empty($argv[1])) {
print 'You must run this tool being into the root of the project.'."\n";
print 'Usage: '.constant('PRODUCT').'.php pathto/outputfile.html [--dir-scc=pathtoscc|disabled] [--dir-phpstan=pathtophpstan|disabled]'."\n";
print 'Example: '.constant('PRODUCT').'.php documents/apstats/index.html --dir-scc=/snap/bin --dir-phpstan=~/git/phpstan/htdocs/includes/bin';
print 'You must run this tool at the root of the project.'."\n";
print 'Usage: '.constant('PRODUCT').'.php pathto/outputfile.html [--dir-scc=pathtoscc|disabled] [--dir-phpstan=pathtophpstan|disabled] [--dir-phan=path/to/phan|disabled]'."\n";
print 'Example: '.constant('PRODUCT').'.php documents/apstats/index.html --dir-scc=/snap/bin --dir-phpstan=~/git/phpstan/htdocs/includes/bin --dir-phan=~/vendor/bin/phan';
exit(0);
}
@ -64,19 +70,27 @@ if (!is_dir($outputdir)) {
$dirscc = '';
$dirphpstan = '';
$dir_phan = '';
$datatable_script = '';
$i = 0;
while ($i < $argc) {
$reg = array();
if (preg_match('/--dir-scc=(.*)$/', $argv[$i], $reg)) {
if (preg_match('/^--dir-scc=(.*)$/', $argv[$i], $reg)) {
$dirscc = $reg[1];
}
if (preg_match('/--dir-phpstan=(.*)$/', $argv[$i], $reg)) {
} elseif (preg_match('/^--dir-phpstan=(.*)$/', $argv[$i], $reg)) {
$dirphpstan = $reg[1];
} elseif (preg_match('/^--dir-phan=(.*)$/', $argv[$i], $reg)) {
$dir_phan = $reg[1];
}
$i++;
}
if (!is_readable("{$path}phan/config.php")) {
print "Skipping phan - configuration not found";
// Disable phan while not integrated yet
$dir_phan = 'disabled';
}
// Start getting data
@ -86,10 +100,11 @@ $timestart = time();
$urlgit = 'https://github.com/Dolibarr/dolibarr/blob/develop/';
// Count lines of code of application
$output_arrproj = array();
$output_arrdep = array();
if ($dirscc != 'disabled') {
$commandcheck = ($dirscc ? $dirscc.'/' : '').'scc . --exclude-dir=htdocs/includes,htdocs/custom,htdocs/theme/common/fontawesome-5,htdocs/theme/common/octicons';
print 'Execute SCC to count lines of code in project: '.$commandcheck."\n";
$output_arrproj = array();
$resexecproj = 0;
exec($commandcheck, $output_arrproj, $resexecproj);
@ -97,20 +112,36 @@ if ($dirscc != 'disabled') {
// Count lines of code of dependencies
$commandcheck = ($dirscc ? $dirscc.'/' : '').'scc htdocs/includes htdocs/theme/common/fontawesome-5 htdocs/theme/common/octicons';
print 'Execute SCC to count lines of code in dependencies: '.$commandcheck."\n";
$output_arrdep = array();
$resexecdep = 0;
exec($commandcheck, $output_arrdep, $resexecdep);
}
// Get technical debt
$output_arrtd = array();
if ($dirphpstan != 'disabled') {
$commandcheck = ($dirphpstan ? $dirphpstan.'/' : '').'phpstan --level='.$phpstanlevel.' -v analyze -a build/phpstan/bootstrap.php --memory-limit 5G --error-format=github';
print 'Execute PHPStan to get the technical debt: '.$commandcheck."\n";
$output_arrtd = array();
$resexectd = 0;
exec($commandcheck, $output_arrtd, $resexectd);
}
$output_phan_json = array();
$res_exec_phan = 0;
if ($dir_phan != 'disabled') {
// Get technical debt (phan)
$PHAN_CONFIG = "dev/tools/phan/config_extended.php";
$PHAN_BASELINE = "dev/tools/phan/baseline.txt";
$PHAN_MIN_PHP = "7.0";
$PHAN_MEMORY_OPT = "--memory-limit 5G";
$commandcheck
= ($dir_phan ? $dir_phan.DIRECTORY_SEPARATOR : '')
."phan --output-mode json $PHAN_MEMORY_OPT -k $PHAN_CONFIG -B $PHAN_BASELINE --analyze-twice --minimum-target-php-version $PHAN_MIN_PHP";
print 'Execute Phan to get the technical debt: '.$commandcheck."\n";
exec($commandcheck, $output_phan_json, $res_exec_phan);
}
$arrayoflineofcode = array();
$arraycocomo = array();
$arrayofmetrics = array(
@ -173,7 +204,7 @@ foreach (array('proj', 'dep') as $source) {
}
// Search the max
$arrayofmax = array('Lines'=>0);
$arrayofmax = array('Lines' => 0);
foreach (array('proj', 'dep') as $source) {
if (!empty($arrayoflineofcode[$source])) {
foreach ($arrayoflineofcode[$source] as $val) {
@ -183,8 +214,11 @@ foreach (array('proj', 'dep') as $source) {
}
$nbofmonth = 2;
$delay = (3600 * 24 * 30 * $nbofmonth);
// Get stats on nb of commits
$commandcheck = "git log --shortstat --no-renames --no-merges --use-mailmap --pretty='format:%cI;%H;%aN;%aE;%ce;%s' --since='".dol_print_date(dol_now() - $delay, '%Y-%m-%d');"'"; // --since= --until=...
$commandcheck = "git log --shortstat --no-renames --no-merges --use-mailmap --pretty=".escapeshellarg('format:%cI;%H;%aN;%aE;%ce;%s')." --since=".dol_print_date(dol_now() - $delay, '%Y-%m-%d'); // --since= --until=...
print 'Execute git log to get list of commits: '.$commandcheck."\n";
$output_arrglpu = array();
$resexecglpu = 0;
@ -196,14 +230,14 @@ $nbofmonth = 2;
$delay = (3600 * 24 * 30 * $nbofmonth);
$arrayofalerts = array();
$commandcheck = "git log --shortstat --no-renames --no-merges --use-mailmap --pretty='format:%cI;%H;%aN;%aE;%ce;%s' --since='".dol_print_date(dol_now() - $delay, '%Y-%m-%d')."' | grep -E 'yogosha|CVE|Sec:'";
$commandcheck = "git log --shortstat --no-renames --no-merges --use-mailmap --pretty=".escapeshellarg('format:%cI;%H;%aN;%aE;%ce;%s')." --since=".escapeshellarg(dol_print_date(dol_now() - $delay, '%Y-%m-%d'))." | grep -E ".escapeshellarg("(yogosha|CVE|Sec:)");
print 'Execute git log to get commits related to security: '.$commandcheck."\n";
$output_arrglpu = array();
$resexecglpu = 0;
exec($commandcheck, $output_arrglpu, $resexecglpu);
foreach ($output_arrglpu as $val) {
$tmpval = cleanVal2($val);
if (preg_match('/yogosha|CVE|Sec:/i', $tmpval['title'])) {
if (preg_match('/(yogosha|CVE|Sec:)/i', $tmpval['title'])) {
$alreadyfound = '';
$alreadyfoundcommitid = '';
foreach ($arrayofalerts as $val) {
@ -303,11 +337,13 @@ $html = '<html>'."\n";
$html .= '<meta charset="utf-8">'."\n";
$html .= '<meta http-equiv="refresh" content="300">'."\n";
$html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">'."\n";
$html .= '<meta name="keywords" content="erp, crm, dolibarr, statistic, projet, security alerts" />'."\n";
$html .= '<meta name="keywords" content="erp, crm, dolibarr, statistics, project, security alerts" />'."\n";
$html .= '<meta name="title" content="Dolibarr project statistics" />'."\n";
$html .= '<meta name="description" content="Statistics about the Dolibarr ERP CRM Open Source project (lines of code, contributions, security alerts, technical debt..." />'."\n";
$html .= '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />'."\n";
$html .= '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'."\n";
$html .= '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.7/css/jquery.dataTables.min.css">';
$html .= '<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>';
$html .= '
<style>
body {
@ -588,11 +624,13 @@ $html .= '<div class="box inline-box back1">';
$html .= 'COCOMO value<br><span class="small opacitymedium">(Basic organic model)</span><br>';
$html .= '<b>$'.formatNumber((empty($arraycocomo['proj']['currency']) ? 0 : $arraycocomo['proj']['currency']) + (empty($arraycocomo['dep']['currency']) ? 0 : $arraycocomo['dep']['currency']), 2).'</b>';
$html .= '</div>';
$html .= '<div class="box inline-box back2">';
$html .= 'COCOMO effort<br><span class="small opacitymedium">(Basic organic model)</span><br>';
$html .= '<b>'.formatNumber($arraycocomo['proj']['people'] * $arraycocomo['proj']['effort'] + $arraycocomo['dep']['people'] * $arraycocomo['dep']['effort']);
$html .= ' months people</b>';
$html .= '</div>';
if (array_key_exists('proj', $arraycocomo)) {
$html .= '<div class="box inline-box back2">';
$html .= 'COCOMO effort<br><span class="small opacitymedium">(Basic organic model)</span><br>';
$html .= '<b>'.formatNumber($arraycocomo['proj']['people'] * $arraycocomo['proj']['effort'] + $arraycocomo['dep']['people'] * $arraycocomo['dep']['effort']);
$html .= ' months people</b>';
$html .= '</div>';
}
$html .= '</div>';
$html .= '</section>'."\n";
@ -622,10 +660,48 @@ if (!empty($output_arrtd)) {
}
$phan_nblines = 0;
if (count($output_phan_json) != 0) {
$phan_notices = json_decode($output_phan_json[count($output_phan_json) - 1], true);
// Info: result code is $res_exec_phan
'@phan-var-force array<array{type:string,type_id:int,check_name:string,description:string,severity:int,location:array{path:string,lines:array{begin:int,end:int}}}> $phan_notices';
$phan_items = [];
foreach ($phan_notices as $notice) {
if (!empty($notice['location'])) {
$path = $notice['location']['path'];
$line_start = $notice['location']['lines']['begin'];
$line_end = $notice['location']['lines']['end'];
if ($line_start == $line_end) {
$line_range = "#L{$line_start}";
$line_range_txt = $line_start;
} else {
$line_range = "#L{$line_start}-L{$line_end}";
$line_range_txt = "{$line_start}-{$line_end}";
}
$code_url = $urlgit.$path.$line_range;
$description = dolPrintLabel($notice['description']);
if ($phan_nblines < 20) {
$tmp = '<tr class="nohidden">';
} else {
$tmp = '<tr class="hidden sourcephan">';
}
$tmp .= "<td>$path</td>";
$tmp .= '<td class="">';
$tmp .= "<a href=\"{$code_url}\" target=\"_blank\">{$line_range_txt}</a>";
$tmp .= '</td>';
$tmp .= "<td>$description</td>";
$tmp .= '</tr>';
$phan_items[] = $tmp;
$phan_nblines++;
}
}
}
// Last security errors
$html .= '<section class="chapter" id="linesofcode">'."\n";
$html .= '<h2><span class="fas fa-code pictofixedwidth"></span>Last security issues <span class="opacitymedium">(last '.$nbofmonth.' month)</span></h2>'."\n";
$html .= '<h2><span class="fas fa-code pictofixedwidth"></span>Last security issues <span class="opacitymedium">(last '.($nbofmonths!=1?$nbofmonths.' months':'month').')</span></h2>'."\n";
$html .= '<div class="div-table-responsive">'."\n";
$html .= '<div class="boxallwidth">'."\n";
@ -675,22 +751,46 @@ $html .= '</div>';
$html .= '</section>';
// Technical debt
// Technical debt PHPstan
if ($nblines != 0) {
$html .= '<section class="chapter" id="technicaldebt">'."\n";
$html .= '<h2><span class="fas fa-book-dead pictofixedwidth"></span>Technical debt <span class="opacitymedium">(PHPStan level '.$phpstanlevel.' -> '.$nblines.' warnings)</span></h2>'."\n";
$html .= '<section class="chapter" id="technicaldebt">'."\n";
$html .= '<h2><span class="fas fa-book-dead pictofixedwidth"></span>Technical debt <span class="opacitymedium">(PHPStan level '.$phpstanlevel.' -> '.$nblines.' warnings)</span></h2>'."\n";
$html .= '<div class="div-table-responsive">'."\n";
$html .= '<div class="boxallwidth">'."\n";
$html .= '<table class="list_technical_debt centpercent">'."\n";
$html .= '<tr class="trgroup"><td>File</td><td>Line</td><td>Type</td></tr>'."\n";
$html .= $tmp;
$html .= '<tr class="sourcephpstan"><td colspan="3"><span class="seedetail" data-source="phpstan" id="sourcephpstan">Show all...</span></td></tr>';
$html .= '</table>';
$html .= '</div>';
$html .= '</div>';
$html .= '<div class="div-table-responsive">'."\n";
$html .= '<div class="boxallwidth">'."\n";
$html .= '<table class="list_technical_debt centpercent">'."\n";
$html .= '<tr class="trgroup"><td>File</td><td>Line</td><td>Type</td></tr>'."\n";
$html .= $tmp;
$html .= '<tr class="sourcephpstan"><td colspan="3"><span class="seedetail" data-source="phpstan" id="sourcephpstan">Show all...</span></td></tr>';
$html .= '</table>';
$html .= '</div>';
$html .= '</div>';
$html .= '</section>'."\n";
}
$html .= '</section>'."\n";
// Technical debt Phan
if ($phan_nblines != 0) {
$datatable_script .= '
if (typeof(DataTable)==="function") {jQuery(".sourcephan").toggle(true);}
let phantable = new DataTable("#technicaldebtphan table");
';
$html .= '<section class="chapter" id="technicaldebtphan">'."\n";
$html .= '<h2><span class="fas fa-book-dead pictofixedwidth"></span>Technical debt <span class="opacitymedium">(PHAN '.$phan_nblines.' warnings)</span></h2>'."\n";
$html .= '<div class="div-table-responsive">'."\n";
$html .= '<div class="boxallwidth">'."\n";
$html .= '<table class="list_technical_debt centpercent">'."\n";
$html .= '<thead><tr class="trgroup"><td>File</td><td>Line</td><td>Detail</td></tr></thead><tbody>'."\n";
$html .= implode("\n", $phan_items);
$html .= '</tbody></table>';
$html .= '<div><span class="seedetail" data-source="phan" id="sourcephan">Show all...</span></div>';
$html .= '</div></div>';
$html .= '</section>'."\n";
}
// JS code to allow to expand/collapse
@ -703,6 +803,7 @@ $(".seedetail").on("click", function() {
console.log("Click on "+source+" so we show class .source"+source);
jQuery(".source"+source).toggle();
} );
'.$datatable_script.'
});
</script>
';
@ -768,8 +869,8 @@ function cleanVal2($val)
$tmpval['issueid'] = '';
$tmpval['issueidyogosha'] = '';
$tmpval['issueidcve'] = '';
$tmpval['title'] = $tmp[5];
$tmpval['created_at'] = $tmp[0];
$tmpval['title'] = array_key_exists(5, $tmp) ? $tmp[5] : '';
$tmpval['created_at'] = array_key_exists(0, $tmp) ? $tmp[0] : '';
$tmpval['updated_at'] = '';
$reg = array();