File indexing completed on 2024-04-21 04:34:32

0001 /*
0002  * This file is part of KDevelop Krazy2 Plugin.
0003  *
0004  * Copyright 2012 Daniel Calviño Sánchez <danxuliu@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU General Public License
0008  * as published by the Free Software Foundation; either version 2
0009  * of the License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program. If not, see <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "analysisjob.h"
0021 
0022 #include <KConfigGroup>
0023 #include <KLocalizedString>
0024 #include <KSharedConfig>
0025 
0026 #include <interfaces/icore.h>
0027 #include <interfaces/iuicontroller.h>
0028 
0029 #include "analysisparameters.h"
0030 #include "analysisprogressparser.h"
0031 #include "analysisresults.h"
0032 #include "analysisresultsparser.h"
0033 #include "checker.h"
0034 #include "./common.h"
0035 
0036 #include <QUrl>
0037 
0038 //public:
0039 
0040 AnalysisJob::AnalysisJob(QObject* parent /*= 0*/): KJob(parent),
0041     m_currentAnalysisParameters(nullptr),
0042     m_analysisResults(nullptr),
0043     m_analysisProgressParser(new AnalysisProgressParser(this)),
0044     m_process(new QProcess(this)) {
0045 
0046     setObjectName(xi18nc("@action:inmenu", "<command>krazy2</command> analysis"));
0047 
0048     connect(m_process, SIGNAL(finished(int)),
0049             this, SLOT(handleProcessFinished(int)));
0050     connect(m_process, SIGNAL(error(QProcess::ProcessError)),
0051             this, SLOT(handleProcessError(QProcess::ProcessError)));
0052 
0053     setCapabilities(Killable);
0054 }
0055 
0056 void AnalysisJob::start() {
0057     Q_ASSERT(!m_analysisParametersList.isEmpty());
0058     Q_ASSERT(m_analysisResults);
0059 
0060     KDevelop::ICore::self()->uiController()->registerStatus(m_analysisProgressParser);
0061     connect(this, SIGNAL(finished(KJob*)), m_analysisProgressParser, SLOT(finish()));
0062 
0063     int totalNumberOfCheckers = 0;
0064     foreach (const AnalysisParameters* analysisParameters, m_analysisParametersList) {
0065         QStringList namesOfFilesToBeAnalyzed = analysisParameters->filesToBeAnalyzed();
0066         m_namesOfFilesToBeAnalyzed.append(namesOfFilesToBeAnalyzed);
0067 
0068         totalNumberOfCheckers += calculateNumberOfCheckersToBeExecuted(namesOfFilesToBeAnalyzed,
0069                                                                        analysisParameters->checkersToRun());
0070     }
0071 
0072     m_analysisProgressParser->setNumberOfCheckers(totalNumberOfCheckers);
0073     m_analysisProgressParser->start();
0074 
0075     prepareNextAnalysisParameters();
0076 
0077     startAnalysis(m_pendingFileTypes.takeFirst());
0078 }
0079 
0080 void AnalysisJob::addAnalysisParameters(const AnalysisParameters* analysisParameters) {
0081     Q_ASSERT(analysisParameters);
0082     Q_ASSERT(analysisParameters->wereCheckersInitialized());
0083     Q_ASSERT(!analysisParameters->checkersToRun().isEmpty());
0084 
0085     m_analysisParametersList.append(analysisParameters);
0086 }
0087 
0088 void AnalysisJob::setAnalysisResults(AnalysisResults* analysisResults) {
0089     m_analysisResults = analysisResults;
0090 }
0091 
0092 //protected:
0093 
0094 bool AnalysisJob::doKill() {
0095     m_analysisParametersList.clear();
0096     m_pendingFileTypes.clear();
0097 
0098     m_process->kill();
0099 
0100     return true;
0101 }
0102 
0103 //private:
0104 
0105 int AnalysisJob::calculateNumberOfCheckersToBeExecuted(const QStringList& namesOfFilesToBeAnalyzed,
0106                                                        const QList<const Checker*>& checkersToRun) const {
0107     int numberOfCheckersToBeExecuted = 0;
0108     foreach (const Checker* checker, checkersToRun) {
0109         if (isCheckerCompatibleWithAnyFile(checker, namesOfFilesToBeAnalyzed)) {
0110             numberOfCheckersToBeExecuted++;
0111         }
0112     }
0113 
0114     return numberOfCheckersToBeExecuted;
0115 }
0116 
0117 bool AnalysisJob::isCheckerCompatibleWithAnyFile(const Checker* checker,
0118                                                  const QStringList& fileNames) const {
0119     foreach (const QString& fileName, fileNames) {
0120         if (isCheckerCompatibleWithFile(checker, fileName)) {
0121             return true;
0122         }
0123     }
0124 
0125     return false;
0126 }
0127 
0128 bool AnalysisJob::isCheckerCompatibleWithFile(const Checker* checker,
0129                                               const QString& fileName) const {
0130     //Based on Krazy2 lib/Krazy/Utils.pm fileType function (commit d2301b9,
0131     //2012-05-26), licensed as GPLv2+.
0132     if (checker->fileType() == "c++" &&
0133         fileName.contains(QRegExp("\\.(cpp|cc|cxx|c|C|tcc|h|hxx|hpp|H\\+\\+)$"))) {
0134         return true;
0135     } else if (checker->fileType() == "desktop" &&
0136                fileName.contains(QRegExp("\\.desktop$"))) {
0137         return true;
0138     } else if (checker->fileType() == "designer" &&
0139                fileName.contains(QRegExp("\\.ui$"))) {
0140         return true;
0141     } else if (checker->fileType() == "kconfigxt" &&
0142                fileName.contains(QRegExp("\\.kcfg$"))) {
0143         return true;
0144     } else if (checker->fileType() == "po" &&
0145                fileName.contains(QRegExp("\\.po$"))) {
0146         return true;
0147     } else if (checker->fileType() == "messages" &&
0148                fileName.contains(QRegExp("Messages\\.sh$"))) {
0149         return true;
0150     } else if (checker->fileType() == "kpartgui" &&
0151                fileName.contains(QRegExp("\\.ui$"))) {
0152         return true;
0153     } else if (checker->fileType() == "tips" &&
0154                fileName.contains(QRegExp("\\.tips$"))) {
0155         return true;
0156     } else if (checker->fileType() == "qml" &&
0157                fileName.contains(QRegExp("\\.qml$"))) {
0158         return true;
0159     } else if (checker->fileType() == "qdoc" &&
0160                fileName.contains(QRegExp("\\.qdoc$"))) {
0161         return true;
0162     } else if (checker->fileType() == "cmake" &&
0163                fileName.contains(QRegExp("(CMakeLists\\.txt|\\.cmake)$"))) {
0164         return true;
0165     }
0166 
0167     return false;
0168 }
0169 
0170 void AnalysisJob::prepareNextAnalysisParameters() {
0171     Q_ASSERT(m_pendingFileTypes.isEmpty());
0172     Q_ASSERT(!m_analysisParametersList.isEmpty());
0173 
0174     m_analysisProgressParser->resetNumberOfFilesForEachFileType();
0175 
0176     m_currentAnalysisParameters = m_analysisParametersList.takeFirst();
0177     m_currentNamesOfFilesToBeAnalyzed = m_namesOfFilesToBeAnalyzed.takeFirst();
0178 
0179     foreach (const Checker* checker, m_currentAnalysisParameters->checkersToRun()) {
0180         if (!m_pendingFileTypes.contains(checker->fileType())) {
0181             m_pendingFileTypes.append(checker->fileType());
0182         }
0183     }
0184 }
0185 
0186 QStringList AnalysisJob::checkersToRunAsKrazy2Arguments(const QString& fileType) const {
0187     QStringList namesOfCheckersToRun;
0188     QStringList namesOfExtraCheckersToRun;
0189     foreach (const Checker* checker, m_currentAnalysisParameters->checkersToRun()) {
0190         if (checker->fileType() == fileType && !checker->isExtra()) {
0191             namesOfCheckersToRun.append(checker->name());
0192         } else if (checker->fileType() == fileType && checker->isExtra()) {
0193             namesOfExtraCheckersToRun.append(checker->name());
0194         }
0195     }
0196 
0197     if (namesOfExtraCheckersToRun.isEmpty()) {
0198         if (namesOfCheckersToRun.isEmpty()) {
0199             return QStringList();
0200         }
0201 
0202         return QStringList() << "--types" << fileType
0203                              << "--check" << namesOfCheckersToRun.join(",");
0204     }
0205 
0206     QStringList namesOfCheckersNotToRun;
0207     foreach (const Checker* checker, m_currentAnalysisParameters->availableCheckers()) {
0208         if (!checker->isExtra() && checker->fileType() == fileType &&
0209                 !namesOfCheckersToRun.contains(checker->name())) {
0210             namesOfCheckersNotToRun.append(checker->name());
0211         }
0212     }
0213 
0214     return QStringList() << "--types" << fileType
0215                          << "--exclude" << namesOfCheckersNotToRun.join(",")
0216                          << "--extra" << namesOfExtraCheckersToRun.join(",");
0217 }
0218 
0219 void AnalysisJob::startAnalysis(const QString& fileType) {
0220     KConfigGroup krazy2Configuration = KSharedConfig::openConfig()->group("Krazy2");
0221     QString krazy2Url = krazy2Configuration.readEntry("krazy2 Path");
0222     QString krazy2Path = urlToString(krazy2Url);
0223     if(krazy2Path.isEmpty())
0224         krazy2Path = defaultPath();
0225 
0226     QStringList arguments;
0227     arguments << "--export" << "xml";
0228     arguments << "--explain";
0229     arguments << checkersToRunAsKrazy2Arguments(fileType);
0230     arguments << "-";
0231 
0232     m_process->setProgram(krazy2Path);
0233     m_process->setArguments(arguments);
0234     m_process->setReadChannelMode(QProcess::SeparateChannels);
0235 
0236     connect(m_process, SIGNAL(readyReadStandardError()),
0237             this, SLOT(handleProcessReadyStandardError()));
0238 
0239     m_process->start();
0240 
0241     QString krazy2Input;
0242     foreach (const QString& fileName, m_currentNamesOfFilesToBeAnalyzed) {
0243         krazy2Input += fileName + '\n';
0244     }
0245 
0246     int totalWritten = 0;
0247     while (totalWritten < krazy2Input.length()) {
0248         totalWritten += m_process->write(krazy2Input.toUtf8());
0249     }
0250     m_process->closeWriteChannel();
0251 }
0252 
0253 //private slots:
0254 
0255 void AnalysisJob::handleProcessReadyStandardError() {
0256     m_analysisProgressParser->parse(m_process->readAllStandardError());
0257 }
0258 
0259 void AnalysisJob::handleProcessFinished(int exitCode) {
0260     Q_UNUSED(exitCode);
0261 
0262     AnalysisResults currentFileTypeResults;
0263     foreach (const Checker* checker, m_currentAnalysisParameters->availableCheckers()) {
0264         currentFileTypeResults.addChecker(new Checker(*checker));
0265     }
0266 
0267     AnalysisResultsParser analysisResultsParser;
0268     analysisResultsParser.setAnalysisResults(&currentFileTypeResults);
0269     analysisResultsParser.parse(m_process->readAllStandardOutput());
0270 
0271     m_analysisResults->addAnalysisResults(&currentFileTypeResults);
0272 
0273     if (m_pendingFileTypes.isEmpty() && !m_analysisParametersList.isEmpty()) {
0274         prepareNextAnalysisParameters();
0275     }
0276 
0277     if (!m_pendingFileTypes.isEmpty()) {
0278         startAnalysis(m_pendingFileTypes.takeFirst());
0279         return;
0280     }
0281 
0282     emitResult();
0283 }
0284 
0285 void AnalysisJob::handleProcessError(QProcess::ProcessError processError) {
0286     setError(UserDefinedError);
0287 
0288     if (processError == QProcess::FailedToStart && m_process->program().isEmpty()) {
0289         setErrorText(xi18nc("@info", "<para>There is no path set in the Krazy2 configuration "
0290                                      "for the <command>krazy2</command> executable.</para>"));
0291     } else if (processError == QProcess::FailedToStart) {
0292         setErrorText(xi18nc("@info", "<para><command>krazy2</command> failed to start "
0293                                      "using the path "
0294                                      "(<filename>%1</filename>).</para>", m_process->program()));
0295     } else if (processError == QProcess::Crashed) {
0296         setErrorText(xi18nc("@info", "<para><command>krazy2</command> crashed.</para>"));
0297     } else {
0298         setErrorText(xi18nc("@info", "<para>An error occurred while executing "
0299                                      "<command>krazy2</command>.</para>"));
0300     }
0301 
0302     emitResult();
0303 }