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