File indexing completed on 2024-05-12 04:39:22

0001 /*
0002     SPDX-FileCopyrightText: 2016 Carlos Nihelton <carlosnsoliveira@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "replacementparser.h"
0008 
0009 // plugin
0010 #include <debug.h>
0011 // Std
0012 // See <https://github.com/CarlosNihelton/kdev-clang-tidy/issues/1>
0013 #include <algorithm>
0014 #include <fstream>
0015 #include <iterator>
0016 #include <tuple>
0017 
0018 #ifdef BOOST_NO_EXCEPTIONS
0019 // Because we are using boost we need to provide an implementation of this
0020 // function, because KDE disables exceptions on
0021 // boost libraries.
0022 namespace boost
0023 {
0024 void throw_exception(std::exception const& e)
0025 {
0026     qCDebug(KDEV_CLANGTIDY) << e.what();
0027 }
0028 } // namespace boost
0029 #endif
0030 
0031 namespace
0032 {
0033 
0034 /**
0035 * @function
0036 * @brief For a given string_ref representing the source code, it counts the number of rows and the column where the
0037 * string_ref ends.
0038 * @param substring : a piece of the source code.
0039 * @return std::pair<size_t,size_t>: <b>first</b>: count of lines and <b>second</b>: column at the end.
0040 */
0041 inline std::pair<size_t, size_t> countOfRowAndColumnToTheEndOfSubstr(boost::string_ref substring)
0042 {
0043     size_t line = 0, col = 0;
0044     for (const auto elem : substring) {
0045         if (elem == char('\n')) {
0046             ++line;
0047             col = 0;
0048         } else {
0049             ++col;
0050         }
0051     }
0052 
0053     return std::make_pair(line, col);
0054 }
0055 
0056 } // namespace
0057 
0058 namespace ClangTidy
0059 {
0060 
0061 const QRegularExpression ReplacementParser::check{ QStringLiteral(
0062     "---\\s+MainSourceFile:\\s+.+\\s+Replacements:(\\s+.+)+\\s\\.\\.\\.") };
0063 
0064 const QRegularExpression ReplacementParser::regex{ (
0065     QStringLiteral("\\s+\\s+-\\s+FilePath:\\s+(.+\\.cpp)\\s+Offset:\\s+(\\d+)\\s+Length:\\s+"
0066                    "(\\d+)\\s+ ReplacementText:\\s(.+)")) };
0067 
0068 ReplacementParser::ReplacementParser(const QString& yaml_file, const QString& source_file)
0069     : currentLine{ 0 }
0070     , currentColumn{ 0 }
0071     , currentOffset{ 0 }
0072     , cReplacements{ 0 }
0073     , m_yamlname{ yaml_file }
0074     , m_sourceFile{ source_file }
0075 {
0076     if (m_yamlname.endsWith(QLatin1String(".yaml"))) {
0077         QFile yaml(m_yamlname);
0078         yaml.open(QIODevice::ReadOnly);
0079         m_yamlContent = QString::fromLocal8Bit(yaml.readAll());
0080 
0081         auto checkMatch = check.match(m_yamlContent);
0082         if (!checkMatch.hasMatch()) {
0083             m_yamlname.clear();
0084             m_yamlContent.clear();
0085         }
0086     }
0087 
0088     // TODO (CarlosNihelton): Discover a way to get that from KDevelop.
0089     if (m_sourceFile.endsWith(QLatin1String(".cpp"))) {
0090         i_source = IndexedString(m_sourceFile);
0091         // See <https://github.com/CarlosNihelton/kdev-clang-tidy/issues/1>
0092         std::ifstream cpp;
0093         cpp.open(m_sourceFile.toUtf8().constData());
0094         std::copy(std::istreambuf_iterator<char>(cpp), std::istreambuf_iterator<char>(),
0095                   std::back_insert_iterator<std::string>(m_sourceCode));
0096         m_sourceView = boost::string_ref(m_sourceCode);
0097     }
0098 }
0099 
0100 void ReplacementParser::parse()
0101 {
0102     if (m_yamlContent.isEmpty()) {
0103         return; // Nothing to parse.
0104     }
0105 
0106     for (auto iMatch = regex.globalMatch(m_yamlContent); iMatch.hasNext(); ++cReplacements) {
0107         auto smatch = iMatch.next();
0108         auto rep = nextNode(smatch);
0109         all_replacements.push_back(rep);
0110     }
0111 }
0112 
0113 Replacement ReplacementParser::nextNode(const QRegularExpressionMatch& smatch)
0114 {
0115     Replacement repl;
0116 
0117     if (smatch.capturedRef(1) != m_sourceFile) {
0118         return repl; // Parsing output from only one file.
0119     }
0120     const int off = smatch.capturedRef(2).toInt();
0121     const int len = smatch.capturedRef(3).toInt();
0122     repl.range = composeNextNodeRange(off, len);
0123     if (repl.range.isValid()) {
0124         repl.offset = off;
0125         repl.length = len;
0126         repl.replacementText = smatch.captured(4);
0127         if (repl.replacementText.startsWith(QLatin1Char('\'')) && repl.replacementText.endsWith(QLatin1Char('\''))) {
0128             repl.replacementText.remove(0, 1);
0129             repl.replacementText.remove(repl.replacementText.length() - 1, 1);
0130         }
0131     } else {
0132         repl.offset = 0;
0133         repl.length = 0;
0134         repl.replacementText.clear();
0135     }
0136 
0137     return repl;
0138 }
0139 
0140 KDevelop::DocumentRange ReplacementParser::composeNextNodeRange(size_t offset, size_t length)
0141 {
0142     qCDebug(KDEV_CLANGTIDY) << "count: " << cReplacements << "\toffset: " << offset << "\tlength: " << length << '\n';
0143     KDevelop::DocumentRange range{ KDevelop::IndexedString(), KTextEditor::Range::invalid() };
0144     /// See https://github.com/CarlosNihelton/kdev-clang-tidy/issues/2.
0145     if (offset < 1 || offset + length >= m_sourceView.length()) {
0146         return range;
0147     }
0148 
0149     range.document = i_source;
0150     auto sourceView = m_sourceView.substr(currentOffset, offset - currentOffset);
0151 
0152     auto pos = ::countOfRowAndColumnToTheEndOfSubstr(sourceView);
0153 
0154     if (pos.first == 0) {
0155         currentColumn += pos.second;
0156     } else {
0157         currentColumn = pos.second;
0158     }
0159     currentLine += pos.first;
0160     currentOffset = offset;
0161 
0162     if (length == 0) {
0163         range.setBothColumns(currentColumn);
0164         range.setBothLines(currentLine);
0165         return range;
0166     }
0167 
0168     sourceView = m_sourceView.substr(offset, length);
0169     pos = ::countOfRowAndColumnToTheEndOfSubstr(sourceView);
0170 
0171     KTextEditor::Cursor start(currentLine, currentColumn);
0172 
0173     size_t endCol;
0174     if (pos.first == 0) {
0175         endCol = currentColumn + pos.second;
0176     } else {
0177         endCol = pos.second;
0178     }
0179 
0180     KTextEditor::Cursor end(currentLine + pos.first, endCol);
0181 
0182     range.setRange(start, end);
0183 
0184     return range;
0185 }
0186 } // namespace ClangTidy