From a8179152c9d38bcb73778db533988c6281699e14 Mon Sep 17 00:00:00 2001
From: Erik Verbruggen <erik.verbruggen@nokia.com>
Date: Wed, 28 Jul 2010 12:09:50 +0200
Subject: [PATCH] Added tests for the InsertionPointLocator and fixed bugs.

---
 .gitignore                                    |   1 +
 src/libs/cplusplus/InsertionPointLocator.cpp  |  65 +++--
 src/libs/cplusplus/InsertionPointLocator.h    |  13 +-
 src/plugins/designer/qtcreatorintegration.cpp |  47 +--
 tests/auto/cplusplus/codegen/codegen.pro      |   7 +
 tests/auto/cplusplus/codegen/tst_codegen.cpp  | 267 ++++++++++++++++++
 tests/auto/cplusplus/cplusplus.pro            |  12 +-
 7 files changed, 347 insertions(+), 65 deletions(-)
 create mode 100644 tests/auto/cplusplus/codegen/codegen.pro
 create mode 100644 tests/auto/cplusplus/codegen/tst_codegen.cpp

diff --git a/.gitignore b/.gitignore
index 15bb1bc52ea..85fa07df957 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,6 +79,7 @@ share/doc/qtcreator/qtcreator.qch
 tests/manual/cplusplus/cplusplus0
 tests/manual/cplusplus-dump/cplusplus0
 tests/manual/qml-ast2dot/qml-ast2dot
+tests/auto/cplusplus/codegen/tst_codegen
 tests/auto/qml/qmldesigner/bauhaustests/tst_bauhaus
 tests/auto/qml/qmldesigner/coretests/tst_qmldesigner_core
 tests/auto/qml/qmldesigner/propertyeditortests/tst_propertyeditor
diff --git a/src/libs/cplusplus/InsertionPointLocator.cpp b/src/libs/cplusplus/InsertionPointLocator.cpp
index 9db5278e244..2b295090375 100644
--- a/src/libs/cplusplus/InsertionPointLocator.cpp
+++ b/src/libs/cplusplus/InsertionPointLocator.cpp
@@ -64,9 +64,9 @@ static QString generate(InsertionPointLocator::AccessSpec xsSpec)
     }
 }
 
-static int distance(InsertionPointLocator::AccessSpec one, InsertionPointLocator::AccessSpec two)
+static int ordering(InsertionPointLocator::AccessSpec xsSpec)
 {
-    static QList<InsertionPointLocator::AccessSpec> distances = QList<InsertionPointLocator::AccessSpec>()
+    static QList<InsertionPointLocator::AccessSpec> order = QList<InsertionPointLocator::AccessSpec>()
             << InsertionPointLocator::Public
             << InsertionPointLocator::PublicSlot
             << InsertionPointLocator::Signals
@@ -76,7 +76,7 @@ static int distance(InsertionPointLocator::AccessSpec one, InsertionPointLocator
             << InsertionPointLocator::Private
             ;
 
-    return distances.indexOf(one) - distances.indexOf(two);
+    return order.indexOf(xsSpec);
 }
 
 struct AccessRange
@@ -134,39 +134,62 @@ protected:
                     ast->lbrace_token,
                     ast->rbrace_token);
 
-        QPair<unsigned, bool> result = findMatch(ranges, _xsSpec);
+        unsigned beforeToken = 0;
+        bool needsPrefix = false;
+        bool needsSuffix = false;
+        findMatch(ranges, _xsSpec, beforeToken, needsPrefix, needsSuffix);
 
         unsigned line = 0, column = 0;
-        getTokenStartPosition(result.first, &line, &column);
+        getTokenStartPosition(beforeToken, &line, &column);
 
         QString prefix;
-        if (!result.second)
+        if (needsPrefix)
             prefix = generate(_xsSpec);
 
-        _result = InsertionLocation(prefix, line, column);
+        QString suffix;
+        if (needsSuffix)
+            suffix = QLatin1Char('\n');
+
+        _result = InsertionLocation(prefix, suffix, line, column);
         return false;
     }
 
-    static QPair<unsigned, bool> findMatch(const QList<AccessRange> &ranges,
-                                           InsertionPointLocator::AccessSpec xsSpec)
+    static void findMatch(const QList<AccessRange> &ranges,
+                          InsertionPointLocator::AccessSpec xsSpec,
+                          unsigned &beforeToken,
+                          bool &needsPrefix,
+                          bool &needsSuffix)
     {
+        Q_ASSERT(!ranges.isEmpty());
+        const int lastIndex = ranges.size() - 1;
+
         // try an exact match, and ignore the first (default) access spec:
-        for (int i = ranges.size() - 1; i > 0; --i) {
+        for (int i = lastIndex; i > 0; --i) {
             const AccessRange &range = ranges.at(i);
-            if (range.xsSpec == xsSpec)
-                return qMakePair(range.end, true);
+            if (range.xsSpec == xsSpec) {
+                beforeToken = range.end;
+                needsPrefix = false;
+                needsSuffix = (i != lastIndex);
+                return;
+            }
         }
 
-        // try to find a fitting access spec to insert in front of:
-        AccessRange best = ranges.last();
-        for (int i = ranges.size() - 2; i > 0; --i) {
-            const AccessRange &range = ranges.at(i);
-            if (distance(range.xsSpec, xsSpec) < distance(best.xsSpec, xsSpec))
-                best = range;
+        // try to find a fitting access spec to insert XXX:
+        for (int i = lastIndex; i > 0; --i) {
+            const AccessRange &current = ranges.at(i);
+
+            if (ordering(xsSpec) > ordering(current.xsSpec)) {
+                beforeToken = current.end;
+                needsPrefix = true;
+                needsSuffix = (i != lastIndex);
+                return;
+            }
         }
 
         // otherwise:
-        return qMakePair(ranges.first().end, false);
+        beforeToken = ranges.first().end;
+        needsPrefix = true;
+        needsSuffix = (ranges.size() != 1);
     }
 
     QList<AccessRange> collectAccessRanges(DeclarationListAST *decls,
@@ -237,8 +260,10 @@ InsertionLocation::InsertionLocation()
     , m_column(0)
 {}
 
-InsertionLocation::InsertionLocation(const QString &prefix, unsigned line, unsigned column)
+InsertionLocation::InsertionLocation(const QString &prefix, const QString &suffix,
+                                     unsigned line, unsigned column)
     : m_prefix(prefix)
+    , m_suffix(suffix)
     , m_line(line)
     , m_column(column)
 {}
diff --git a/src/libs/cplusplus/InsertionPointLocator.h b/src/libs/cplusplus/InsertionPointLocator.h
index 5d0d854d525..20d1cfe8302 100644
--- a/src/libs/cplusplus/InsertionPointLocator.h
+++ b/src/libs/cplusplus/InsertionPointLocator.h
@@ -33,7 +33,7 @@
 #include <CPlusPlusForwardDeclarations.h>
 #include <Symbols.h>
 
-#include <cplusplus/CppDocument.h>
+#include "CppDocument.h"
 
 namespace CPlusPlus {
 
@@ -41,18 +41,22 @@ class CPLUSPLUS_EXPORT InsertionLocation
 {
 public:
     InsertionLocation();
-    InsertionLocation(const QString &prefix, unsigned line, unsigned column);
+    InsertionLocation(const QString &prefix, const QString &suffix, unsigned line, unsigned column);
 
     /// \returns The prefix to insert before any other text.
     QString prefix() const
     { return m_prefix; }
 
+    /// \returns The suffix to insert after the other inserted text.
+    QString suffix() const
+    { return m_suffix; }
+
     /// \returns The line where to insert. The line number is 1-based.
-    int line() const
+    unsigned line() const
     { return m_line; }
 
     /// \returns The column where to insert. The column number is 1-based.
-    int column() const
+    unsigned column() const
     { return m_column; }
 
     bool isValid() const
@@ -60,6 +64,7 @@ public:
 
 private:
     QString m_prefix;
+    QString m_suffix;
     unsigned m_line;
     unsigned m_column;
 };
diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp
index ff40fd0c1a4..ea51e896a5c 100644
--- a/src/plugins/designer/qtcreatorintegration.cpp
+++ b/src/plugins/designer/qtcreatorintegration.cpp
@@ -275,44 +275,6 @@ static bool isEndingQuote(const QString &contents, int quoteIndex)
     return endingQuote;
 }
 
-// TODO: Wait for robust Roberto's code using AST or whatever for that. Current implementation is hackish.
-static int findClassEndPosition(const QString &headerContents, int classStartPosition)
-{
-    const QString contents = headerContents.mid(classStartPosition); // we start serching from the beginning of class declaration
-    // We need to find the position of class closing "}"
-    int openedBlocksCount = 0; // counter of nested {} blocks
-    int idx = 0; // index of current position in the contents
-    while (true) {
-        if (idx < 0 || idx >= contents.length()) // indexOf returned -1, that means we don't have closing comment mark
-            break;
-        if (contents.mid(idx, 2) == QLatin1String("//")) {
-            idx = contents.indexOf(QLatin1Char('\n'), idx + 2) + 1; // drop everything up to the end of line
-        } else if (contents.mid(idx, 2) == QLatin1String("/*")) {
-            idx = contents.indexOf(QLatin1String("*/"), idx + 2) + 2; // drop everything up to the nearest */
-        } else if (contents.mid(idx, 4) == QLatin1String("'\\\"'")) {
-            idx += 4; // drop it
-        } else if (contents.at(idx) == QLatin1Char('\"')) {
-            do {
-                idx = contents.indexOf(QLatin1Char('\"'), idx + 1); // drop everything up to the nearest "
-            } while (idx > 0 && !isEndingQuote(contents, idx)); // if the nearest " is preceded by \ (or by \\\ or by \\\\\, but not by \\ nor \\\\) we find next one
-            if (idx < 0)
-                break;
-            idx++;
-        } else {
-            if (contents.at(idx) == QLatin1Char('{')) {
-                openedBlocksCount++;
-            } else if (contents.at(idx) == QLatin1Char('}')) {
-                openedBlocksCount--;
-                if (openedBlocksCount == 0) {
-                    return classStartPosition + idx;
-                }
-            }
-            idx++;
-        }
-    }
-    return -1;
-}
-
 static inline ITextEditable *editableAt(const QString &fileName, int line, int column)
 {
     return qobject_cast<ITextEditable *>(TextEditor::BaseTextEditor::openEditorAt(fileName, line, column));
@@ -329,7 +291,7 @@ static void addDeclaration(Document::Ptr doc, const Class *cl, const QString &fu
     const InsertionLocation loc = find.methodDeclarationInClass(cl, InsertionPointLocator::PrivateSlot);
 
     //
-    // ### FIXME: change this to use the Refactoring changes!
+    // ### FIXME: change this to use the Refactoring changes.
     //
 
     if (ITextEditable *editable = editableAt(docFileName, loc.line(), loc.column() - 1)) {
@@ -338,7 +300,7 @@ static void addDeclaration(Document::Ptr doc, const Class *cl, const QString &fu
             QTextCursor tc = editor->textCursor();
             int pos = tc.position();
             tc.beginEditBlock();
-            tc.insertText(loc.prefix() + declaration);
+            tc.insertText(loc.prefix() + declaration + loc.suffix());
             tc.setPosition(pos, QTextCursor::KeepAnchor);
             editor->indentInsertedText(tc);
             tc.endEditBlock();
@@ -370,6 +332,11 @@ static Document::Ptr addDefinition(const CPlusPlus::Snapshot &docTable,
         // we take only those documents which have the same filename
         if (headerBaseName == sourceFI.baseName()) {
             if (ITextEditable *editable = editableAt(doc->fileName(), 0, 0)) {
+
+                //
+                // ### FIXME: use the InsertionPointLocator to insert at the correct place.
+                //
+
                 const QString contents = editable->contents();
                 int column;
                 editable->convertPosition(contents.length(), line, &column);
diff --git a/tests/auto/cplusplus/codegen/codegen.pro b/tests/auto/cplusplus/codegen/codegen.pro
new file mode 100644
index 00000000000..fa736489c72
--- /dev/null
+++ b/tests/auto/cplusplus/codegen/codegen.pro
@@ -0,0 +1,7 @@
+TEMPLATE = app
+CONFIG += qt warn_on console depend_includepath
+CONFIG += qtestlib testcase
+CONFIG -= app_bundle
+include(../shared/shared.pri)
+SOURCES += tst_codegen.cpp
+TARGET=tst_$$TARGET
diff --git a/tests/auto/cplusplus/codegen/tst_codegen.cpp b/tests/auto/cplusplus/codegen/tst_codegen.cpp
new file mode 100644
index 00000000000..60b66b6b97b
--- /dev/null
+++ b/tests/auto/cplusplus/codegen/tst_codegen.cpp
@@ -0,0 +1,267 @@
+#include <AST.h>
+#include <Control.h>
+#include <CppDocument.h>
+#include <DiagnosticClient.h>
+#include <InsertionPointLocator.h>
+#include <Scope.h>
+#include <TranslationUnit.h>
+#include <Literals.h>
+#include <Semantic.h>
+#include <Symbols.h>
+
+#include <QtTest>
+#include <QtDebug>
+#include <QTextDocument>
+
+using namespace CPlusPlus;
+
+class tst_Codegen: public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void public_in_empty_class();
+    void public_in_nonempty_class();
+    void public_before_protected();
+    void private_after_protected();
+    void protected_in_nonempty_class();
+    void protected_betwee_public_and_private();
+    void qtdesigner_integration();
+};
+
+void tst_Codegen::public_in_nonempty_class()
+{
+    const QByteArray src = "\n"
+            "class Foo\n" // line 1
+            "{\n"
+            "public:\n"   // line 3
+            "};\n"        // line 4
+            "\n";
+
+    Document::Ptr doc = Document::create("public_in_nonempty_class");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Public);
+    QVERIFY(loc.isValid());
+    QVERIFY(loc.prefix().isEmpty());
+    QVERIFY(loc.suffix().isEmpty());
+    QCOMPARE(loc.line(), 4U);
+    QCOMPARE(loc.column(), 1U);
+}
+
+void tst_Codegen::public_in_empty_class()
+{
+    const QByteArray src = "\n"
+            "class Foo\n" // line 1
+            "{\n"
+            "};\n"
+            "\n";
+
+    Document::Ptr doc = Document::create("public_in_empty_class");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Public);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("public:\n"));
+    QVERIFY(loc.suffix().isEmpty());
+    QCOMPARE(loc.line(), 3U);
+    QCOMPARE(loc.column(), 1U);
+}
+
+void tst_Codegen::public_before_protected()
+{
+    const QByteArray src = "\n"
+            "class Foo\n"  // line 1
+            "{\n"
+            "protected:\n" // line 3
+            "};\n"
+            "\n";
+
+    Document::Ptr doc = Document::create("public_before_protected");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Public);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("public:\n"));
+    QCOMPARE(loc.suffix(), QLatin1String("\n"));
+    QCOMPARE(loc.column(), 1U);
+    QCOMPARE(loc.line(), 3U);
+}
+
+void tst_Codegen::private_after_protected()
+{
+    const QByteArray src = "\n"
+            "class Foo\n"  // line 1
+            "{\n"
+            "protected:\n" // line 3
+            "};\n"
+            "\n";
+
+    Document::Ptr doc = Document::create("private_after_protected");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Private);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("private:\n"));
+    QVERIFY(loc.suffix().isEmpty());
+    QCOMPARE(loc.column(), 1U);
+    QCOMPARE(loc.line(), 4U);
+}
+
+void tst_Codegen::protected_in_nonempty_class()
+{
+    const QByteArray src = "\n"
+            "class Foo\n" // line 1
+            "{\n"
+            "public:\n"   // line 3
+            "};\n"        // line 4
+            "\n";
+
+    Document::Ptr doc = Document::create("protected_in_nonempty_class");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Protected);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("protected:\n"));
+    QVERIFY(loc.suffix().isEmpty());
+    QCOMPARE(loc.column(), 1U);
+    QCOMPARE(loc.line(), 4U);
+}
+
+void tst_Codegen::protected_betwee_public_and_private()
+{
+    const QByteArray src = "\n"
+            "class Foo\n" // line 1
+            "{\n"
+            "public:\n"   // line 3
+            "private:\n"  // line 4
+            "};\n"        // line 5
+            "\n";
+
+    Document::Ptr doc = Document::create("protected_betwee_public_and_private");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 1U);
+
+    Class *mainWindow = doc->globalSymbolAt(0)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 1U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::Protected);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("protected:\n"));
+    QCOMPARE(loc.suffix(), QLatin1String("\n"));
+    QCOMPARE(loc.column(), 1U);
+    QCOMPARE(loc.line(), 4U);
+}
+
+void tst_Codegen::qtdesigner_integration()
+{
+    const QByteArray src = "/**** Some long (C)opyright notice ****/\n"
+            "#ifndef MAINWINDOW_H\n"
+            "#define MAINWINDOW_H\n"
+            "\n"
+            "#include <QMainWindow>\n"
+            "\n"
+            "namespace Ui {\n"
+            "    class MainWindow;\n"
+            "}\n"
+            "\n"
+            "class MainWindow : public QMainWindow\n" // line 10
+            "{\n"
+            "    Q_OBJECT\n"
+            "\n"
+            "public:\n" // line 14
+            "    explicit MainWindow(QWidget *parent = 0);\n"
+            "    ~MainWindow();\n"
+            "\n"
+            "private:\n" // line 18
+            "    Ui::MainWindow *ui;\n"
+            "};\n"
+            "\n"
+            "#endif // MAINWINDOW_H\n";
+
+    Document::Ptr doc = Document::create("qtdesigner_integration");
+    doc->setSource(src);
+    doc->parse();
+    doc->check();
+
+    QCOMPARE(doc->diagnosticMessages().size(), 0);
+    QCOMPARE(doc->globalSymbolCount(), 2U);
+
+    Class *mainWindow = doc->globalSymbolAt(1)->asClass();
+    QVERIFY(mainWindow);
+    QCOMPARE(mainWindow->line(), 10U);
+    QCOMPARE(mainWindow->column(), 7U);
+
+    InsertionPointLocator find(doc);
+    InsertionLocation loc = find.methodDeclarationInClass(mainWindow, InsertionPointLocator::PrivateSlot);
+    QVERIFY(loc.isValid());
+    QCOMPARE(loc.prefix(), QLatin1String("private slots:\n"));
+    QCOMPARE(loc.suffix(), QLatin1String("\n"));
+    QCOMPARE(loc.line(), 18U);
+    QCOMPARE(loc.column(), 1U);
+}
+
+QTEST_APPLESS_MAIN(tst_Codegen)
+#include "tst_codegen.moc"
diff --git a/tests/auto/cplusplus/cplusplus.pro b/tests/auto/cplusplus/cplusplus.pro
index 947a140b4e0..c5d9930c7f1 100644
--- a/tests/auto/cplusplus/cplusplus.pro
+++ b/tests/auto/cplusplus/cplusplus.pro
@@ -1,3 +1,13 @@
 TEMPLATE = subdirs
-SUBDIRS = shared ast semantic lookup preprocessor findusages typeprettyprinter codeformatter
 CONFIG += ordered
+
+SUBDIRS = \
+    shared \
+    ast \
+    semantic \
+    lookup \
+    preprocessor \
+    findusages \
+    typeprettyprinter \
+    codeformatter \
+    codegen
-- 
GitLab