| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "commentastvisitor.h" |
| |
| CommentAstVisitor::CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode) : m_engine(engine) |
| { |
| rootNode->accept(this); |
| |
| // Look for complete orphans that have not been attached to *any* node |
| QVector<Comment> completeOrphans; |
| |
| for (const auto &comment : m_engine->comments()) { |
| if (isCommentAttached(comment)) |
| continue; |
| |
| bool found_orphan = false; |
| for (const auto &orphanList : orphanComments().values()) { |
| for (const auto &orphan : orphanList) { |
| if (orphan.contains(comment)) { |
| found_orphan = true; |
| break; |
| } |
| } |
| |
| if (found_orphan) |
| break; |
| } |
| |
| if (found_orphan) |
| continue; |
| |
| completeOrphans.append(Comment(m_engine, Comment::Location::Front, {comment})); |
| } |
| |
| m_orphanComments[nullptr] = completeOrphans; |
| } |
| |
| QList<SourceLocation> CommentAstVisitor::findCommentsInLine(quint32 line, bool includePrevious) const |
| { |
| QList<SourceLocation> results; |
| if (line == 0) |
| return results; |
| |
| for (const auto &location : m_engine->comments()) { |
| if (location.startLine != line) |
| continue; |
| |
| if (isCommentAttached(location)) |
| continue; |
| |
| results.append(location); |
| |
| if (includePrevious) { |
| // See if we can find any more comments above this one |
| auto previous = findCommentsInLine(line - 1, true); |
| |
| // Iterate it in reverse to restore the correct order |
| for (auto it = previous.rbegin(); it != previous.rend(); it++) { |
| results.prepend(*it); |
| } |
| } |
| |
| break; |
| } |
| |
| return results; |
| } |
| |
| bool CommentAstVisitor::isCommentAttached(const SourceLocation &location) const |
| { |
| for (const auto &value : m_attachedComments.values()) { |
| if (value.contains(location)) |
| return true; |
| } |
| |
| for (const auto &value : m_listItemComments.values()) { |
| if (value.contains(location)) |
| return true; |
| } |
| |
| // If a comment is already marked as an orphan of a Node that counts as attached too. |
| for (const auto &orphanList : m_orphanComments.values()) { |
| for (const auto &value : orphanList) { |
| if (value.contains(location)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| Comment CommentAstVisitor::findComment(SourceLocation first, SourceLocation last, |
| int locations) const |
| { |
| if (locations & Comment::Location::Front) { |
| quint32 searchAt = first.startLine - 1; |
| |
| const auto comments = findCommentsInLine(searchAt, true); |
| if (!comments.isEmpty()) |
| return Comment(m_engine, Comment::Location::Front, comments); |
| } |
| |
| if (locations & Comment::Location::Front_Inline) { |
| quint32 searchAt = first.startLine; |
| |
| const auto comments = findCommentsInLine(searchAt); |
| if (!comments.isEmpty()) |
| return Comment(m_engine, Comment::Location::Front_Inline, comments); |
| } |
| |
| if (locations & Comment::Location::Back_Inline) { |
| quint32 searchAt = last.startLine; |
| |
| const auto comments = findCommentsInLine(searchAt); |
| if (!comments.isEmpty()) |
| return Comment(m_engine, Comment::Location::Back_Inline, comments); |
| } |
| |
| if (locations & Comment::Location::Back) { |
| quint32 searchAt = last.startLine + 1; |
| |
| const auto comments = findCommentsInLine(searchAt); |
| if (!comments.isEmpty()) |
| return Comment(m_engine, Comment::Location::Back, comments); |
| } |
| |
| return Comment(); |
| |
| } |
| |
| Comment CommentAstVisitor::findComment(Node *node, int locations) const |
| { |
| return findComment(node->firstSourceLocation(), node->lastSourceLocation(), locations); |
| } |
| |
| QVector<Comment> CommentAstVisitor::findOrphanComments(Node *node) const |
| { |
| QVector<Comment> comments; |
| |
| for (auto &comment : m_engine->comments()) { |
| if (isCommentAttached(comment)) |
| continue; |
| |
| if (comment.begin() <= node->firstSourceLocation().begin() |
| || comment.end() > node->lastSourceLocation().end()) { |
| continue; |
| } |
| |
| comments.append(Comment(m_engine, Comment::Location::Front, {comment})); |
| } |
| |
| return comments; |
| } |
| |
| void CommentAstVisitor::attachComment(Node *node, int locations) |
| { |
| auto comment = findComment(node, locations); |
| |
| if (comment.isValid()) |
| m_attachedComments[node] = comment; |
| } |
| |
| bool CommentAstVisitor::visit(UiScriptBinding *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(StatementList *node) |
| { |
| for (auto *item = node; item != nullptr; item = item->next) |
| attachComment(item->statement, Comment::Front | Comment::Back_Inline); |
| return true; |
| } |
| |
| void CommentAstVisitor::endVisit(StatementList *node) |
| { |
| m_orphanComments[node] = findOrphanComments(node); |
| } |
| |
| bool CommentAstVisitor::visit(UiObjectBinding *node) |
| { |
| attachComment(node, Comment::Front | Comment::Front_Inline | Comment::Back); |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(UiObjectDefinition *node) |
| { |
| attachComment(node, Comment::Front | Comment::Front_Inline | Comment::Back); |
| return true; |
| } |
| |
| void CommentAstVisitor::endVisit(UiObjectDefinition *node) |
| { |
| m_orphanComments[node] = findOrphanComments(node); |
| } |
| |
| bool CommentAstVisitor::visit(UiArrayBinding *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| void CommentAstVisitor::endVisit(UiArrayBinding *node) |
| { |
| m_orphanComments[node] = findOrphanComments(node); |
| } |
| |
| bool CommentAstVisitor::visit(UiEnumDeclaration *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| void CommentAstVisitor::endVisit(UiEnumDeclaration *node) |
| { |
| m_orphanComments[node] = findOrphanComments(node); |
| } |
| |
| bool CommentAstVisitor::visit(UiEnumMemberList *node) |
| { |
| for (auto *item = node; item != nullptr; item = item->next) { |
| auto comment = findComment(item->memberToken, |
| item->valueToken.isValid() ? item->valueToken : item->memberToken, |
| Comment::Front | Comment::Back_Inline); |
| |
| if (comment.isValid()) |
| m_listItemComments[item->memberToken.begin()] = comment; |
| } |
| |
| m_orphanComments[node] = findOrphanComments(node); |
| |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(UiPublicMember *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(FunctionDeclaration *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(UiImport *node) |
| { |
| attachComment(node); |
| return true; |
| } |
| |
| bool CommentAstVisitor::visit(UiPragma *node) |
| { |
| attachComment(node); |
| return true; |
| } |