5959
6060#include < algorithm>
6161#include < cstddef>
62+ #include < fstream>
6263#include < iterator>
6364#include < list>
6465#include < memory>
9697#include < QtNetwork/QNetworkRequest>
9798#include < QPushButton>
9899#include < QRegularExpression>
100+ #include < QSet>
99101#include < QSettings>
100102#include < QSize>
101103#include < QTimer>
@@ -118,6 +120,18 @@ static QString fromNativePath(const QString& p) {
118120#endif
119121}
120122
123+ // The files selected in the results view are not necessarily spelled the same way as the
124+ // project or files.txt spells them, compare them as absolute paths
125+ static QString comparablePath (const QString& path) {
126+ return QDir::cleanPath (QFileInfo (path).absoluteFilePath ());
127+ }
128+
129+ static QSet<QString> comparablePaths (const QStringList& paths) {
130+ QSet<QString> ret;
131+ std::transform (paths.cbegin (), paths.cend (), std::inserter (ret, ret.end ()), comparablePath);
132+ return ret;
133+ }
134+
121135template <typename T = std::vector<std::string>>
122136static T toStdStringList (const QStringList& stringList) {
123137 T ret;
@@ -171,9 +185,7 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) :
171185 connect (mUI ->mActionAnalyzeFiles , &QAction::triggered, this , &MainWindow::analyzeFiles);
172186 connect (mUI ->mActionAnalyzeDirectory , &QAction::triggered, this , &MainWindow::analyzeDirectory);
173187 connect (mUI ->mActionSettings , &QAction::triggered, this , &MainWindow::programSettings);
174- connect (mUI ->mActionClearResults , &QAction::triggered, this , [this ]() {
175- clearResults ();
176- });
188+ connect (mUI ->mActionClearResults , &QAction::triggered, this , &MainWindow::clearResults);
177189 connect (mUI ->mActionOpenXML , &QAction::triggered, this , &MainWindow::openResults);
178190
179191 connect (mUI ->mActionShowStyle , &QAction::toggled, this , &MainWindow::showStyle);
@@ -570,21 +582,31 @@ void MainWindow::saveSettings() const
570582 mUI ->mResults ->saveSettings (mSettings );
571583}
572584
573- void MainWindow::doAnalyzeProject (ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& recheckFiles )
585+ void MainWindow::doAnalyzeProject (ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck )
574586{
575587 Settings checkSettings;
576588 auto supprs = std::make_shared<Suppressions>();
577589 if (!getCppcheckSettings (checkSettings, *supprs))
578590 return ;
579591
580- // filter requested files
581- if (!recheckFiles.isEmpty ()) {
592+ // Filter the selected files
593+ if (recheck == Recheck::Selected) {
594+ const QSet<QString> selected = comparablePaths (selectedFiles);
582595 p.fileSettings .remove_if ([&](const FileSettings& fs) {
583- return !recheckFiles .contains (QString::fromStdString (fs.filename ()));
596+ return !selected .contains (comparablePath ( QString::fromStdString (fs.filename () )));
584597 });
598+ if (p.fileSettings .empty ()) {
599+ QMessageBox msg (QMessageBox::Information,
600+ " Cppcheck" ,
601+ tr (" None of the selected files is part of the project, nothing to analyze." ),
602+ QMessageBox::Ok,
603+ this );
604+ msg.exec ();
605+ return ;
606+ }
585607 }
586608
587- clearResults (recheckFiles );
609+ clearResultsForAnalysis (checkSettings. buildDir , selectedFiles, recheck );
588610
589611 mIsLogfileLoaded = false ;
590612 if (mProjectFile ) {
@@ -630,7 +652,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo
630652 if (!checkSettings.buildDir .empty ()) {
631653 checkSettings.loadSummaries ();
632654 std::list<std::string> sourcefiles;
633- AnalyzerInformation::writeFilesTxt (checkSettings.buildDir , sourcefiles, p.fileSettings , !recheckFiles. isEmpty () );
655+ AnalyzerInformation::writeFilesTxt (checkSettings.buildDir , sourcefiles, p.fileSettings );
634656 }
635657
636658 // mThread->SetanalyzeProject(true);
@@ -649,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bo
649671 mUI ->mResults ->setCheckSettings (checkSettings);
650672}
651673
652- void MainWindow::doAnalyzeFiles (const QStringList &files, const bool checkLib, const bool checkConfig, const bool partialRecheck )
674+ void MainWindow::doAnalyzeFiles (const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck )
653675{
654676 if (files.isEmpty ())
655677 return ;
@@ -659,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
659681 if (!getCppcheckSettings (checkSettings, *supprs))
660682 return ;
661683
662- clearResults (partialRecheck ? files : QStringList () );
684+ clearResultsForAnalysis (checkSettings. buildDir , selectedFiles, recheck );
663685
664686 mIsLogfileLoaded = false ;
665687 FileList pathList;
@@ -708,7 +730,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, c
708730 if (!checkSettings.buildDir .empty ()) {
709731 checkSettings.loadSummaries ();
710732 const auto & sourcefiles = toStdStringList<std::list<std::string>>(fileNames);
711- AnalyzerInformation::writeFilesTxt (checkSettings.buildDir , sourcefiles, {}, partialRecheck );
733+ AnalyzerInformation::writeFilesTxt (checkSettings.buildDir , sourcefiles, {});
712734 }
713735
714736 mThread ->setCheckFiles (true );
@@ -1380,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
13801402 return ;
13811403
13821404 if (mProjectFile ) {
1383- // Clear details, statistics and progress
1384- mUI ->mResults ->clear (false );
1385- for (int i = 0 ; i < files.size (); ++i)
1386- mUI ->mResults ->clearRecheckFile (files[i]);
1387-
13881405 analyzeProject (mProjectFile , files);
13891406 return ;
13901407 }
@@ -1394,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files)
13941411 if (!getCppcheckSettings (checkSettings, *supprs))
13951412 return ;
13961413
1397- // Clear details, statistics and progress
1398- mUI ->mResults ->clear (false );
1399- for (int i = 0 ; i < files.size (); ++i)
1400- mUI ->mResults ->clearRecheckFile (files[i]);
1414+ clearResultsForAnalysis (checkSettings.buildDir , files, Recheck::Selected);
14011415
14021416 mCurrentDirectory = mUI ->mResults ->getCheckDirectory ();
14031417 FileList pathList;
@@ -1449,16 +1463,10 @@ void MainWindow::reAnalyze(bool all)
14491463 mUI ->mResults ->setCheckSettings (checkSettings);
14501464}
14511465
1452- void MainWindow::clearResults (const QStringList& selectedFiles )
1466+ void MainWindow::clearResults ()
14531467{
1454- if (!selectedFiles.isEmpty ()) {
1455- mUI ->mResults ->clear (false );
1456- for (const QString& f : selectedFiles)
1457- mUI ->mResults ->clearRecheckFile (f);
1458- return ;
1459- }
14601468 if (mProjectFile && !mProjectFile ->getBuildDir ().isEmpty ()) {
1461- QDir dir (QFileInfo (mProjectFile ->getFilename ()).absolutePath () + ' / ' + mProjectFile ->getBuildDir ());
1469+ QDir dir (QDir ( QFileInfo (mProjectFile ->getFilename ()).absolutePath ()). filePath ( mProjectFile ->getBuildDir () ));
14621470 for (const QString& f: dir.entryList (QDir::Files)) {
14631471 if (!f.endsWith (" files.txt" )) {
14641472 static const QRegularExpression rx (" ^.*.s[0-9]+$" );
@@ -1472,6 +1480,42 @@ void MainWindow::clearResults(const QStringList& selectedFiles)
14721480 enableResultsButtons ();
14731481}
14741482
1483+ void MainWindow::clearResultsForAnalysis (const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck)
1484+ {
1485+ switch (recheck) {
1486+ case Recheck::None:
1487+ clearResults ();
1488+ break ;
1489+ case Recheck::Selected:
1490+ // Only the selected files are analyzed, the results of the other files are kept
1491+ mUI ->mResults ->clear (false );
1492+ for (const QString& f : selectedFiles)
1493+ mUI ->mResults ->clearRecheckFile (f);
1494+ enableResultsButtons ();
1495+ break ;
1496+ case Recheck::Cached: {
1497+ // Every file reports its results again, either analyzed or replayed from the analyzer
1498+ // information. Remove the analyzer information of the selected files so that they really
1499+ // are analyzed again, also by the clang tools that keep their results next to it.
1500+ const QSet<QString> selected = comparablePaths (selectedFiles);
1501+ QDir dir (QString::fromStdString (buildDir));
1502+ std::ifstream filesTxt (buildDir + " /files.txt" );
1503+ std::string line;
1504+ while (std::getline (filesTxt, line)) {
1505+ AnalyzerInformation::Info info;
1506+ if (!info.parse (line) || !selected.contains (comparablePath (QString::fromStdString (info.sourceFile ))))
1507+ continue ;
1508+ const QString afile = QString::fromStdString (info.afile );
1509+ for (const QString& f : dir.entryList ({afile, afile + " .*" }, QDir::Files))
1510+ dir.remove (f);
1511+ }
1512+ mUI ->mResults ->clear (true );
1513+ enableResultsButtons ();
1514+ break ;
1515+ }
1516+ }
1517+ }
1518+
14751519void MainWindow::openResults ()
14761520{
14771521 if (mUI ->mResults ->hasResults ()) {
@@ -1870,10 +1914,19 @@ bool MainWindow::loadLastResults()
18701914 return true ;
18711915}
18721916
1873- void MainWindow::analyzeProject (const ProjectFile *projectFile, const QStringList& recheckFiles , const bool checkLib, const bool checkConfig)
1917+ void MainWindow::analyzeProject (const ProjectFile *projectFile, const QStringList& selectedFiles , const bool checkLib, const bool checkConfig)
18741918{
18751919 Settings::terminate (false );
18761920
1921+ // A build directory means there is an analyzer information cache. Analyzing the whole
1922+ // project is then the better way to recheck a few files: the cache skips the files that
1923+ // did not change and replays their findings, and files.txt stays complete so whole
1924+ // program analysis (unusedFunction, the MISRA cross-TU rules, ...) still sees every file.
1925+ // Only without a cache is it worth narrowing the analysis down to the selected files.
1926+ Recheck recheck = Recheck::None;
1927+ if (!selectedFiles.isEmpty ())
1928+ recheck = projectFile->getBuildDir ().isEmpty () ? Recheck::Selected : Recheck::Cached;
1929+
18771930 QFileInfo inf (projectFile->getFilename ());
18781931 const QString& rootpath = projectFile->getRootPath ();
18791932
@@ -1975,11 +2028,11 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
19752028 msg.exec ();
19762029 return ;
19772030 }
1978- doAnalyzeProject (p, checkLib, checkConfig, recheckFiles ); // TODO: avoid copy
2031+ doAnalyzeProject (p, checkLib, checkConfig, selectedFiles, recheck ); // TODO: avoid copy
19792032 return ;
19802033 }
19812034
1982- QStringList paths = recheckFiles. isEmpty () ? projectFile->getCheckPaths () : recheckFiles ;
2035+ QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths ();
19832036
19842037 // If paths not given then check the root path (which may be the project
19852038 // file's location, see above). This is to keep the compatibility with
@@ -1988,7 +2041,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis
19882041 if (paths.isEmpty ()) {
19892042 paths << mCurrentDirectory ;
19902043 }
1991- doAnalyzeFiles (paths, checkLib, checkConfig, !recheckFiles. isEmpty () );
2044+ doAnalyzeFiles (paths, checkLib, checkConfig, selectedFiles, recheck );
19922045}
19932046
19942047void MainWindow::newProjectFile ()
0 commit comments