mirror of
https://github.com/Dolibarr/dolibarr.git
synced 2025-02-20 13:46:52 +01:00
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:
parent
ed19e0603f
commit
1c452bc86e
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user