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"