File indexing completed on 2024-04-21 04:38:10
0001 /* 0002 SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "job.h" 0008 0009 #include "checksdb.h" 0010 #include "debug.h" 0011 #include "globalsettings.h" 0012 #include "plugin.h" 0013 #include "utils.h" 0014 0015 #include <language/editor/documentrange.h> 0016 #include <shell/problem.h> 0017 // KF 0018 #include <KLocalizedString> 0019 #include <KShell> 0020 // Qt 0021 #include <QMessageBox> 0022 #include <QElapsedTimer> 0023 #include <QFileInfo> 0024 #include <QRegularExpression> 0025 0026 namespace Clazy 0027 { 0028 0029 Job::Job() 0030 : KDevelop::CompileAnalyzeJob(nullptr) 0031 , m_db(nullptr) 0032 , m_timer(nullptr) 0033 { 0034 } 0035 0036 QString commandLineString(const JobParameters& params) 0037 { 0038 QStringList args; 0039 0040 args << params.executablePath; 0041 0042 if (!params.checks.isEmpty()) { 0043 args << QLatin1String("-checks=") + params.checks; 0044 } 0045 0046 if (params.onlyQt) { 0047 args << QStringLiteral("-only-qt"); 0048 } 0049 0050 if (params.qtDeveloper) { 0051 args << QStringLiteral("-qt-developer"); 0052 } 0053 0054 if (params.qt4Compat) { 0055 args << QStringLiteral("-qt4-compat"); 0056 } 0057 0058 if (params.visitImplicitCode) { 0059 args << QStringLiteral("-visit-implicit-code"); 0060 } 0061 0062 if (params.ignoreIncludedFiles) { 0063 args << QStringLiteral("-ignore-included-files"); 0064 } 0065 0066 if (!params.headerFilter.isEmpty()) { 0067 args << QLatin1String("-header-filter=") + params.headerFilter; 0068 } 0069 0070 if (params.enableAllFixits) { 0071 args << QStringLiteral("-enable-all-fixits"); 0072 } 0073 0074 if (params.noInplaceFixits) { 0075 args << QStringLiteral("-no-inplace-fixits"); 0076 } 0077 0078 if (!params.extraAppend.isEmpty()) { 0079 args << QLatin1String("-extra-arg=") + params.extraAppend; 0080 } 0081 0082 if (!params.extraPrepend.isEmpty()) { 0083 args << QLatin1String("-extra-arg-before=%1") + params.extraPrepend; 0084 } 0085 0086 if (!params.extraClazy.isEmpty()) { 0087 args << KShell::splitArgs(params.extraClazy); 0088 } 0089 0090 args << QLatin1String("-p=\"") + params.buildDir + QLatin1Char('\"'); 0091 0092 // for (auto it = args.begin(), end = args.end(); it != end; ++it) { 0093 // QString& commandPart = *it; 0094 // commandPart = spaceEscapedString(commandPart); 0095 // } 0096 0097 return args.join(QLatin1Char(' ')); 0098 } 0099 0100 Job::Job(const JobParameters& params, QSharedPointer<const ChecksDB> db) 0101 : KDevelop::CompileAnalyzeJob(nullptr) 0102 , m_db(db) 0103 , m_timer(new QElapsedTimer) 0104 { 0105 setJobName(i18n("Clazy Analysis (%1)", prettyPathName(params.url))); 0106 0107 setParallelJobCount(params.parallelJobCount); 0108 setBuildDirectoryRoot(params.buildDir); 0109 setCommand(commandLineString(params), params.verboseOutput); 0110 setToolDisplayName(QStringLiteral("Clazy")); 0111 setSources(params.filePaths); 0112 } 0113 0114 Job::~Job() 0115 { 0116 } 0117 0118 void Job::processStdoutLines(const QStringList& lines) 0119 { 0120 m_standardOutput << lines; 0121 } 0122 0123 void Job::processStderrLines(const QStringList& lines) 0124 { 0125 static const auto errorRegex = QRegularExpression( 0126 QStringLiteral("(.+):(\\d+):(\\d+):\\s+warning:\\s+(.+)\\s+\\[-Wclazy-(.+)\\]$")); 0127 0128 QVector<KDevelop::IProblem::Ptr> problems; 0129 0130 for (const QString & line : lines) { 0131 auto match = errorRegex.match(line); 0132 if (match.hasMatch()) { 0133 auto check = m_db ? m_db->checks().value(match.captured(5), nullptr) : nullptr; 0134 0135 const QString levelName = check ? check->level->displayName : i18n("Unknown Level"); 0136 KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(levelName)); 0137 0138 problem->setSeverity(KDevelop::IProblem::Warning); 0139 problem->setDescription(match.captured(4)); 0140 if (check) { 0141 problem->setExplanation(check->description); 0142 } 0143 0144 // Sometimes warning/error file path contains "." or ".." elements so we should fix 0145 // it and take "real" (canonical) path value. But QFileInfo::canonicalFilePath() 0146 // returns empty string when file does not exists. Unfortunately we can't pass some 0147 // real file path from unit tests, therefore we should skip canonicalFilePath() step. 0148 // To detect such testing cases we are check m_timer value, which is not-null only for 0149 // "real" jobs, created with public constructor. 0150 const auto document = m_timer.isNull() ? match.captured(1) : QFileInfo(match.captured(1)).canonicalFilePath(); 0151 0152 const int line = match.capturedRef(2).toInt() - 1; 0153 const int column = match.capturedRef(3).toInt() - 1; 0154 0155 // TODO add KDevelop::IProblem::FinalLocationMode::ToEnd type ? 0156 KTextEditor::Range range(line, column, line, 1000); 0157 KDevelop::DocumentRange documentRange(KDevelop::IndexedString(document), range); 0158 problem->setFinalLocation(documentRange); 0159 problem->setFinalLocationMode(KDevelop::IProblem::Range); 0160 0161 problems.append(problem); 0162 } 0163 } 0164 m_stderrOutput << lines; 0165 0166 if (problems.size()) { 0167 emit problemsDetected(problems); 0168 } 0169 } 0170 0171 void Job::postProcessStdout(const QStringList& lines) 0172 { 0173 processStdoutLines(lines); 0174 0175 KDevelop::CompileAnalyzeJob::postProcessStdout(lines); 0176 } 0177 0178 void Job::postProcessStderr(const QStringList& lines) 0179 { 0180 processStderrLines(lines); 0181 0182 KDevelop::CompileAnalyzeJob::postProcessStderr(lines); 0183 } 0184 0185 void Job::start() 0186 { 0187 m_standardOutput.clear(); 0188 m_stderrOutput.clear(); 0189 0190 m_timer->restart(); 0191 0192 KDevelop::CompileAnalyzeJob::start(); 0193 } 0194 0195 void Job::childProcessError(QProcess::ProcessError e) 0196 { 0197 QString message; 0198 0199 switch (e) { 0200 case QProcess::FailedToStart: 0201 message = i18n("Failed to start Clazy analysis process."); 0202 break; 0203 0204 case QProcess::Crashed: 0205 if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { 0206 message = i18n("Clazy analysis process crashed."); 0207 } 0208 break; 0209 0210 case QProcess::Timedout: 0211 message = i18n("Clazy analysis process timed out."); 0212 break; 0213 0214 case QProcess::WriteError: 0215 message = i18n("Write to Clazy analysis process failed."); 0216 break; 0217 0218 case QProcess::ReadError: 0219 message = i18n("Read from Clazy analysis process failed."); 0220 break; 0221 0222 case QProcess::UnknownError: 0223 // errors will be displayed in the output view ? 0224 // don't notify the user 0225 break; 0226 } 0227 0228 if (!message.isEmpty()) { 0229 QMessageBox::critical(nullptr, i18nc("@title:window", "Clazy Error"), message); 0230 } 0231 0232 KDevelop::CompileAnalyzeJob::childProcessError(e); 0233 } 0234 0235 void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) 0236 { 0237 qCDebug(KDEV_CLAZY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; 0238 0239 setPercent(100); 0240 postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)}); 0241 0242 if (exitCode != 0) { 0243 qCDebug(KDEV_CLAZY) << "clazy failed"; 0244 qCDebug(KDEV_CLAZY) << "stdout output: "; 0245 qCDebug(KDEV_CLAZY) << m_standardOutput.join(QLatin1Char('\n')); 0246 qCDebug(KDEV_CLAZY) << "stderr output: "; 0247 qCDebug(KDEV_CLAZY) << m_stderrOutput.join(QLatin1Char('\n')); 0248 } 0249 0250 KDevelop::CompileAnalyzeJob::childProcessExited(exitCode, exitStatus); 0251 } 0252 0253 } 0254 0255 #include "moc_job.cpp"