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(¤tFileTypeResults); 0269 analysisResultsParser.parse(m_process->readAllStandardOutput()); 0270 0271 m_analysisResults->addAnalysisResults(¤tFileTypeResults); 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 }