File indexing completed on 2024-05-12 04:39:13
0001 /* 0002 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "todoextractor.h" 0008 0009 #include "../util/clangtypes.h" 0010 0011 #include <language/duchain/problem.h> 0012 #include <language/duchain/stringhelpers.h> 0013 #include <language/editor/documentrange.h> 0014 #include <interfaces/icore.h> 0015 #include <interfaces/ilanguagecontroller.h> 0016 #include <interfaces/icompletionsettings.h> 0017 0018 #include <QStringList> 0019 #include <QDir> 0020 0021 #include <algorithm> 0022 #include <limits> 0023 0024 using namespace KDevelop; 0025 0026 namespace { 0027 0028 inline int findEndOfLineOrEnd(const QString& str, int from = 0) 0029 { 0030 const int index = str.indexOf(QLatin1Char('\n'), from); 0031 return (index == -1 ? str.length() : index); 0032 } 0033 0034 inline int findBeginningOfLineOrStart(const QString& str, int from = 0) 0035 { 0036 const int index = str.lastIndexOf(QLatin1Char('\n'), from); 0037 return (index == -1 ? 0 : index+1); 0038 } 0039 0040 inline int findEndOfCommentOrEnd(const QString& str, int from = 0) 0041 { 0042 const int index = str.indexOf(QLatin1String("*/"), from); 0043 return (index == -1 ? str.length() : index); 0044 } 0045 0046 /** 0047 * @brief Class for parsing to-do items out of a given comment string 0048 * 0049 * Make sure this class is very performant as it will be used for every comment string 0050 * throughout the code base. 0051 * 0052 * So: No unnecessary deep-copies of strings if possible! 0053 */ 0054 class CommentTodoParser 0055 { 0056 public: 0057 struct Result { 0058 /// Description of the problem 0059 QString description; 0060 /// Range within the comment 0061 KTextEditor::Range localRange; 0062 }; 0063 0064 CommentTodoParser(const QString& str, const QStringList& markerWords) 0065 : m_str(str) 0066 , m_todoMarkerWords(markerWords) 0067 { 0068 skipUntilMarkerWord(); 0069 } 0070 0071 QVector<Result> results() const 0072 { 0073 return m_results; 0074 } 0075 0076 private: 0077 void skipUntilMarkerWord() 0078 { 0079 // in the most-cases, we won't find a to-do item 0080 // make sure this case is sufficiently fast 0081 0082 // for each to-do marker, scan the comment text 0083 for (const QString& todoMarker : m_todoMarkerWords) { 0084 int offset = m_str.indexOf(todoMarker, m_offset); 0085 if (offset != -1) { 0086 m_offset = offset; 0087 parseTodoMarker(); 0088 } 0089 } 0090 } 0091 0092 void skipUntilNewline() 0093 { 0094 m_offset = findEndOfLineOrEnd(m_str, m_offset); 0095 } 0096 0097 void parseTodoMarker() 0098 { 0099 // okay, we've found something 0100 // m_offset points to the start of the to-do item 0101 const int lineStart = findBeginningOfLineOrStart(m_str, m_offset); 0102 const int lineEnd = findEndOfLineOrEnd(m_str, m_offset); 0103 Q_ASSERT(lineStart <= m_offset); 0104 Q_ASSERT(lineEnd > m_offset); 0105 0106 QString text = m_str.mid(m_offset, lineEnd - m_offset); 0107 Q_ASSERT(!text.contains(QLatin1Char('\n'))); 0108 0109 // there's nothing to be stripped on the left side, hence ignore that 0110 text.chop(text.length() - findEndOfCommentOrEnd(text)); 0111 text = text.trimmed(); // remove additional whitespace from the end 0112 0113 // check at what line within the comment we are by just counting the newlines until now 0114 const int line = std::count(m_str.constBegin(), m_str.constBegin() + m_offset, QLatin1Char('\n')); 0115 KTextEditor::Cursor start = {line, m_offset - lineStart}; 0116 KTextEditor::Cursor end = {line, start.column() + text.length()}; 0117 m_results << Result{text, {start, end}}; 0118 0119 skipUntilNewline(); 0120 skipUntilMarkerWord(); 0121 } 0122 0123 inline bool atEnd() const 0124 { 0125 return m_offset < m_str.length(); 0126 } 0127 0128 private: 0129 const QString m_str; 0130 const QStringList m_todoMarkerWords; 0131 0132 int m_offset = 0; 0133 0134 QVector<Result> m_results; 0135 }; 0136 0137 } 0138 0139 Q_DECLARE_TYPEINFO(CommentTodoParser::Result, Q_MOVABLE_TYPE); 0140 0141 TodoExtractor::TodoExtractor(CXTranslationUnit unit, CXFile file) 0142 : m_unit(unit) 0143 , m_file(file) 0144 , m_todoMarkerWords(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords()) 0145 { 0146 extractTodos(); 0147 } 0148 0149 void TodoExtractor::extractTodos() 0150 { 0151 using uintLimits = std::numeric_limits<uint>; 0152 0153 auto start = clang_getLocation(m_unit, m_file, 1, 1); 0154 auto end = clang_getLocation(m_unit, m_file, uintLimits::max(), uintLimits::max()); 0155 0156 auto range = clang_getRange(start, end); 0157 0158 IndexedString path(QDir(ClangString(clang_getFileName(m_file)).toString()).canonicalPath()); 0159 0160 if(clang_Range_isNull(range)){ 0161 return; 0162 } 0163 0164 const ClangTokens tokens(m_unit, range); 0165 for (CXToken token : tokens) { 0166 CXTokenKind tokenKind = clang_getTokenKind(token); 0167 if (tokenKind != CXToken_Comment) { 0168 continue; 0169 } 0170 0171 CXString tokenSpelling = clang_getTokenSpelling(m_unit, token); 0172 auto tokenRange = ClangRange(clang_getTokenExtent(m_unit, token)).toRange(); 0173 const QString text = ClangString(tokenSpelling).toString(); 0174 0175 CommentTodoParser parser(text, m_todoMarkerWords); 0176 const auto parserResults = parser.results(); 0177 m_problems.reserve(m_problems.size() + parserResults.size()); 0178 for (const CommentTodoParser::Result& result : parserResults) { 0179 ProblemPointer problem(new Problem); 0180 problem->setDescription(result.description); 0181 problem->setSeverity(IProblem::Hint); 0182 problem->setSource(IProblem::ToDo); 0183 0184 // move the local range to the correct location 0185 // note: localRange is the range *within* the comment only 0186 auto localRange = result.localRange; 0187 KTextEditor::Range todoRange{ 0188 tokenRange.start().line() + localRange.start().line(), 0189 localRange.start().column(), 0190 tokenRange.start().line() + localRange.end().line(), 0191 localRange.end().column()}; 0192 problem->setFinalLocation({path, todoRange}); 0193 m_problems << problem; 0194 } 0195 } 0196 } 0197 0198 QList< ProblemPointer > TodoExtractor::problems() const 0199 { 0200 return m_problems; 0201 }