forked from qt-creator/qt-creator
		
	This will serve as the basic building block for several comment-related features. Task-number: QTCREATORBUG-6934 Task-number: QTCREATORBUG-12051 Task-number: QTCREATORBUG-13877 Change-Id: Ic68587c0d7985dc731da9f539884590fcec764de Reviewed-by: David Schulz <david.schulz@qt.io>
		
			
				
	
	
		
			226 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright (C) 2023 The Qt Company Ltd.
 | |
| // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 | |
| 
 | |
| #include "cplusplus/Overview.h"
 | |
| #include <cplusplus/declarationcomments.h>
 | |
| #include <cplusplus/CppDocument.h>
 | |
| #include <cplusplus/SymbolVisitor.h>
 | |
| 
 | |
| #include <QtTest>
 | |
| #include <QTextDocument>
 | |
| 
 | |
| using namespace CPlusPlus;
 | |
| using namespace Utils;
 | |
| 
 | |
| const char testSource[] = R"TheFile(
 | |
| //! Unrelated comment, even though it mentions MyEnum.
 | |
| 
 | |
| //! Related comment because of proximity.
 | |
| //! Related comment that mentions MyEnum.
 | |
| //! Related comment that mentions MyEnumA.
 | |
| 
 | |
| enum MyEnum { MyEnumA, MyEnumB };
 | |
| 
 | |
| // Unrelated comment because of different comment type.
 | |
| //! Related comment that mentions MyEnumClass::A.
 | |
| 
 | |
| enum class MyEnumClass { A, B };
 | |
| 
 | |
| // Related comment because of proximity.
 | |
| void myFunc();
 | |
| 
 | |
| /*
 | |
|  * Related comment because of proximity.
 | |
|  */
 | |
| template<typename T> class MyClassTemplate {};
 | |
| 
 | |
| /*
 | |
|  * Related comment, because it mentions MyOtherClass.
 | |
|  */
 | |
| 
 | |
| class MyOtherClass {
 | |
|     /// Related comment for function and parameter a2, but not parameter a.
 | |
|     /// @param a2
 | |
|     void memberFunc(int a, int a2);
 | |
| };
 | |
| 
 | |
| //! Comment for member function at implementation site.
 | |
| void MyOtherClass::memberFunc(int, int) {}
 | |
| 
 | |
| //! An unrelated comment
 | |
| 
 | |
| void funcWithoutComment();
 | |
| 
 | |
| )TheFile";
 | |
| 
 | |
| class SymbolFinder : public SymbolVisitor
 | |
| {
 | |
| public:
 | |
|     SymbolFinder(const QString &name, Document *doc, int expectedOccurrence)
 | |
|         : m_name(name), m_doc(doc), m_expectedOccurrence(expectedOccurrence) {}
 | |
|     const Symbol *find();
 | |
| 
 | |
| private:
 | |
|     bool preVisit(Symbol *) override { return !m_symbol; }
 | |
|     bool visit(Namespace *ns) override { return visitScope(ns); }
 | |
|     bool visit(Enum *e) override { return visitScope(e); }
 | |
|     bool visit(Class *c) override { return visitScope(c); }
 | |
|     bool visit(Function *f) override { return visitScope(f); }
 | |
|     bool visit(Argument *a) override;
 | |
|     bool visit(Declaration *d) override;
 | |
| 
 | |
|     bool visitScope(Scope *scope);
 | |
| 
 | |
|     const QString &m_name;
 | |
|     Document * const m_doc;
 | |
|     const Symbol *m_symbol = nullptr;
 | |
|     const int m_expectedOccurrence;
 | |
|     int m_tokenLocation = -1;
 | |
| };
 | |
| 
 | |
| class TestDeclarationComments : public QObject
 | |
| {
 | |
|     Q_OBJECT
 | |
| 
 | |
| private Q_SLOTS:
 | |
|     void initTestCase();
 | |
| 
 | |
|     void commentsForDecl_data();
 | |
|     void commentsForDecl();
 | |
| 
 | |
| private:
 | |
|     Snapshot m_snapshot;
 | |
|     Document::Ptr m_cppDoc;
 | |
|     QTextDocument m_textDoc;
 | |
| };
 | |
| 
 | |
| void TestDeclarationComments::initTestCase()
 | |
| {
 | |
|     m_cppDoc = m_snapshot.preprocessedDocument(testSource, "dummy.cpp");
 | |
|     m_cppDoc->check();
 | |
|     const bool hasErrors = !m_cppDoc->diagnosticMessages().isEmpty();
 | |
|     if (hasErrors) {
 | |
|         for (const Document::DiagnosticMessage &msg : m_cppDoc->diagnosticMessages())
 | |
|             qDebug() << '\t' << msg.text();
 | |
|     }
 | |
|     QVERIFY(!hasErrors);
 | |
|     m_textDoc.setPlainText(testSource);
 | |
| }
 | |
| 
 | |
| void TestDeclarationComments::commentsForDecl_data()
 | |
| {
 | |
|     QTest::addColumn<QString>("symbolName");
 | |
|     QTest::addColumn<QString>("expectedCommentPrefix");
 | |
|     QTest::addColumn<int>("occurrence");
 | |
| 
 | |
|     QTest::newRow("enum type") << "MyEnum" << "//! Related comment because of proximity" << 1;
 | |
|     QTest::newRow("enum value (positive)") << "MyEnumA"
 | |
|                                            << "//! Related comment because of proximity" << 1;
 | |
|     QTest::newRow("enum value (negative") << "MyEnumB" << QString() << 1;
 | |
| 
 | |
|     QTest::newRow("enum class type") << "MyEnumClass"
 | |
|                                      << "//! Related comment that mentions MyEnumClass::A" << 1;
 | |
|     QTest::newRow("enum class value (positive)")
 | |
|         << "A" << "//! Related comment that mentions MyEnumClass::A" << 1;
 | |
|     QTest::newRow("enum class value (negative") << "B" << QString() << 1;
 | |
| 
 | |
|     QTest::newRow("function declaration with comment")
 | |
|         << "myFunc" << "// Related comment because of proximity" << 1;
 | |
| 
 | |
|     QTest::newRow("class template")
 | |
|         << "MyClassTemplate" << "/*\n * Related comment because of proximity." << 1;
 | |
| 
 | |
|     QTest::newRow("class")
 | |
|         << "MyOtherClass" << "/*\n * Related comment, because it mentions MyOtherClass." << 1;
 | |
|     QTest::newRow("member function declaration")
 | |
|         << "memberFunc" << "/// Related comment for function and parameter a2, but not parameter a."
 | |
|         << 1;
 | |
|     QTest::newRow("function parameter (negative)") << "a" << QString() << 1;
 | |
|     QTest::newRow("function parameter (positive)") << "a2" << "/// Related comment for function "
 | |
|                                                       "and parameter a2, but not parameter a." << 1;
 | |
| 
 | |
|     QTest::newRow("member function definition") << "memberFunc" <<
 | |
|         "//! Comment for member function at implementation site." << 2;
 | |
| 
 | |
|     QTest::newRow("function declaration without comment") << "funcWithoutComment" << QString() << 1;
 | |
| }
 | |
| 
 | |
| void TestDeclarationComments::commentsForDecl()
 | |
| {
 | |
|     QFETCH(QString, symbolName);
 | |
|     QFETCH(QString, expectedCommentPrefix);
 | |
|     QFETCH(int, occurrence);
 | |
| 
 | |
|     SymbolFinder finder(symbolName, m_cppDoc.get(), occurrence);
 | |
|     const Symbol * const symbol = finder.find();
 | |
|     QVERIFY(symbol);
 | |
| 
 | |
|     const QList<Token> commentTokens = commentsForDeclaration(symbol, m_snapshot, m_textDoc);
 | |
|     if (expectedCommentPrefix.isEmpty()) {
 | |
|         QVERIFY(commentTokens.isEmpty());
 | |
|         return;
 | |
|     }
 | |
|     QVERIFY(!commentTokens.isEmpty());
 | |
| 
 | |
|     const int firstCommentPos = m_cppDoc->translationUnit()->getTokenPositionInDocument(
 | |
|         commentTokens.first(), &m_textDoc);
 | |
|     const QString actualCommentPrefix = m_textDoc.toPlainText().mid(firstCommentPos,
 | |
|                                                                     expectedCommentPrefix.length());
 | |
|     QCOMPARE(actualCommentPrefix, expectedCommentPrefix);
 | |
| }
 | |
| 
 | |
| 
 | |
| const Symbol *SymbolFinder::find()
 | |
| {
 | |
|     for (int i = 0, occurrences = 0; i < m_doc->translationUnit()->tokenCount(); ++i) {
 | |
|         const Token &tok = m_doc->translationUnit()->tokenAt(i);
 | |
|         if (tok.kind() != T_IDENTIFIER)
 | |
|             continue;
 | |
|         if (tok.spell() != m_name)
 | |
|             continue;
 | |
|         if (++occurrences == m_expectedOccurrence) {
 | |
|             m_tokenLocation = i;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     if (m_tokenLocation == -1)
 | |
|         return nullptr;
 | |
| 
 | |
|     visit(m_doc->globalNamespace());
 | |
|     return m_symbol;
 | |
| }
 | |
| 
 | |
| bool SymbolFinder::visit(Argument *a)
 | |
| {
 | |
|     if (a->sourceLocation() == m_tokenLocation)
 | |
|         m_symbol = a;
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool SymbolFinder::visit(Declaration *d)
 | |
| {
 | |
|     if (d->sourceLocation() == m_tokenLocation) {
 | |
|         m_symbol = d;
 | |
|         return false;
 | |
|     }
 | |
|     if (const auto func = d->type().type()->asFunctionType())
 | |
|         return visit(func);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool SymbolFinder::visitScope(Scope *scope)
 | |
| {
 | |
|     for (int i = 0; i < scope->memberCount(); ++i) {
 | |
|         Symbol * const s = scope->memberAt(i);
 | |
|         if (s->sourceLocation() == m_tokenLocation) {
 | |
|             m_symbol = s;
 | |
|             return false;
 | |
|         }
 | |
|         accept(s);
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| QTEST_APPLESS_MAIN(TestDeclarationComments)
 | |
| #include "tst_declarationcomments.moc"
 |