File indexing completed on 2024-03-24 16:05:55
0001 /* 0002 SPDX-FileCopyrightText: 2016 Sven Brauch <mail@svenbrauch.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "pythonstylechecking.h" 0008 0009 #include <QTimer> 0010 #include <QStandardPaths> 0011 #include <QRegularExpression> 0012 0013 #include <language/duchain/problem.h> 0014 #include <language/editor/documentrange.h> 0015 #include <interfaces/icore.h> 0016 #include <shell/documentcontroller.h> 0017 0018 #include "pythondebug.h" 0019 #include "pythonparsejob.h" 0020 #include "helpers.h" 0021 0022 namespace Python { 0023 0024 StyleChecking::StyleChecking(QObject* parent) 0025 : QObject(parent) 0026 { 0027 qRegisterMetaType<KDevelop::ReferencedTopDUContext>("KDevelop::ReferencedTopDUContext"); 0028 connect(&m_checkerProcess, &QProcess::readyReadStandardOutput, 0029 this, &StyleChecking::processOutputStarted); 0030 connect(&m_checkerProcess, &QProcess::readyReadStandardError, 0031 [this]() { 0032 qWarning() << "python code checker error:" << m_checkerProcess.readAllStandardError(); 0033 }); 0034 auto config = KSharedConfig::openConfig("kdevpythonsupportrc"); 0035 m_pep8Group = config->group("pep8"); 0036 } 0037 0038 StyleChecking::~StyleChecking() 0039 { 0040 if ( m_checkerProcess.state() == QProcess::Running ) { 0041 m_checkerProcess.terminate(); 0042 m_checkerProcess.waitForFinished(100); 0043 } 0044 } 0045 0046 void StyleChecking::startChecker(const QString& text, const QString& select, 0047 const QString& ignore, const int maxLineLength) 0048 { 0049 // start up the server 0050 if ( m_checkerProcess.state() == QProcess::NotRunning ) { 0051 auto python = Helper::getPythonExecutablePath(nullptr); 0052 auto serverPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/codestyle.py"); 0053 if ( serverPath.isEmpty() ) { 0054 qWarning() << "setup problem: codestyle.py not found"; 0055 m_mutex.unlock(); 0056 return; 0057 } 0058 m_checkerProcess.start(python, {serverPath}); 0059 m_checkerProcess.waitForStarted(30); 0060 if ( m_checkerProcess.state() != QProcess::Running ) { 0061 qWarning() << "failed to start code checker process"; 0062 m_mutex.unlock(); 0063 return; 0064 } 0065 } 0066 0067 // send input 0068 QByteArray data = text.toUtf8(); 0069 QByteArray header; 0070 header.append(select.toUtf8()); 0071 header.append("\n"); 0072 header.append(ignore.toUtf8()); 0073 header.append("\n"); 0074 header.append(QByteArray::number(maxLineLength)); 0075 header.append("\n"); 0076 // size, always 10 bytes 0077 header.insert(0, QByteArray::number(header.size() + data.size()).leftJustified(10)); 0078 m_checkerProcess.write(header); 0079 m_checkerProcess.write(data); 0080 } 0081 0082 void StyleChecking::addErrorsToContext(const QVector<QString>& errors) 0083 { 0084 static QRegularExpression errorFormat("(.*):(\\d*):(\\d*): (.*)", QRegularExpression::CaseInsensitiveOption); 0085 DUChainWriteLocker lock; 0086 auto document = m_currentlyChecking->url(); 0087 for ( const auto& error : errors ) { 0088 QRegularExpressionMatch match; 0089 if ( (match = errorFormat.match(error)).hasMatch() ) { 0090 bool lineno_ok = false; 0091 bool colno_ok = false; 0092 int lineno = match.captured(2).toInt(&lineno_ok); 0093 int colno = match.captured(3).toInt(&colno_ok); 0094 if ( ! lineno_ok || ! colno_ok ) { 0095 qCDebug(KDEV_PYTHON) << "invalid line / col number"; 0096 continue; 0097 } 0098 QString error = match.captured(4); 0099 KDevelop::Problem* p = new KDevelop::Problem(); 0100 p->setFinalLocation(DocumentRange(document, KTextEditor::Range(lineno - 1, qMax(colno - 1, 0), 0101 lineno - 1, colno))); 0102 p->setSource(KDevelop::IProblem::Preprocessor); 0103 p->setSeverity(error.startsWith('W') ? KDevelop::IProblem::Hint : KDevelop::IProblem::Warning); 0104 p->setDescription(i18n("PEP8 checker error: %1", error)); 0105 ProblemPointer ptr(p); 0106 m_currentlyChecking->addProblem(ptr); 0107 } 0108 else { 0109 qCDebug(KDEV_PYTHON) << "invalid pep8 error line:" << error; 0110 } 0111 } 0112 0113 m_currentlyChecking->setFeatures((TopDUContext::Features) ( m_currentlyChecking->features() | ParseJob::PEP8Checking )); 0114 } 0115 0116 void StyleChecking::processOutputStarted() 0117 { 0118 if (m_mutex.try_lock()) { 0119 // The m_mutex (which is *not* recursive) was not locked!! 0120 // This probably means there is some problem with the 0121 // checker server, which is sending us more data than 0122 // we expected (or consumed). The only way we can deal 0123 // with this situation is to kill the server and start it 0124 // again, when needed 0125 if ( m_checkerProcess.state() == QProcess::Running ) { 0126 m_checkerProcess.terminate(); 0127 m_checkerProcess.waitForFinished(100); 0128 } 0129 m_mutex.unlock(); 0130 return; 0131 } 0132 0133 // read output size 0134 QByteArray size_d; 0135 size_d = m_checkerProcess.read(10); 0136 bool ok; 0137 auto size = size_d.toInt(&ok); 0138 if ( !ok || size < 0 ) { 0139 addSetupErrorToContext("Got invalid size: " + size_d); 0140 m_mutex.unlock(); 0141 return; 0142 } 0143 0144 // read actual output 0145 QByteArray buf; 0146 QTimer t; 0147 t.setSingleShot(true); 0148 t.start(100); 0149 while ( size > 0 && t.remainingTime() > 0 ) { 0150 auto d = m_checkerProcess.read(qMin(4096, size)); 0151 buf.append(d); 0152 size -= d.size(); 0153 qDebug() << "remaining:" << size << d.size(); 0154 } 0155 0156 // process it 0157 QVector<QString> errors; 0158 auto ofs = -1; 0159 auto prev = ofs; 0160 while ( prev = ofs, (ofs = buf.indexOf('\n', ofs+1)) != -1 ) { 0161 errors.append(buf.mid(prev+1, ofs-prev)); 0162 } 0163 if ( !t.isActive() ) { 0164 addSetupErrorToContext("Output took longer than 100 ms."); 0165 } 0166 addErrorsToContext(errors); 0167 0168 // done, unlock mutex 0169 m_currentlyChecking = nullptr; 0170 m_mutex.unlock(); 0171 } 0172 0173 void StyleChecking::updateStyleChecking(const KDevelop::ReferencedTopDUContext& top) 0174 { 0175 if ( !top ) { 0176 return; 0177 } 0178 auto url = top->url(); 0179 IDocument* idoc = ICore::self()->documentController()->documentForUrl(url.toUrl()); 0180 if ( !idoc || !idoc->textDocument() || top->features() & ParseJob::PEP8Checking ) { 0181 return; 0182 } 0183 auto text = idoc->textDocument()->text(); 0184 0185 if ( !m_mutex.tryLock(1000) ) { 0186 qWarning() << "timed out waiting for the style checker mutex"; 0187 return; 0188 } 0189 m_currentlyChecking = top; 0190 0191 // default empty is ok, it will never be used, because the config has to be written at least once 0192 // to even enable this feature. 0193 auto select = m_pep8Group.readEntry<QString>("enableErrors", ""); 0194 auto ignore = m_pep8Group.readEntry<QString>("disableErrors", ""); 0195 auto maxLineLength = m_pep8Group.readEntry<int>("maxLineLength", 80); 0196 startChecker(text, select, ignore, maxLineLength); 0197 } 0198 0199 void StyleChecking::addSetupErrorToContext(const QString& error) 0200 { 0201 DUChainWriteLocker lock; 0202 KDevelop::Problem *p = new KDevelop::Problem(); 0203 p->setFinalLocation(DocumentRange(m_currentlyChecking->url(), KTextEditor::Range(0, 0, 0, 0))); 0204 p->setSource(KDevelop::IProblem::Preprocessor); 0205 p->setSeverity(KDevelop::IProblem::Warning); 0206 p->setDescription(i18n("The PEP8 syntax checker does not seem to work correctly.") + "\n" + error); 0207 ProblemPointer ptr(p); 0208 m_currentlyChecking->addProblem(ptr); 0209 } 0210 0211 }; 0212 0213 #include "moc_pythonstylechecking.cpp"