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 }