| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sergio Martins <sergio.martins@kdab.com> |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the plugins of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtTest/QtTest> |
| 30 | #include <QProcess> |
| 31 | #include <QString> |
| 32 | |
| 33 | #include <util.h> |
| 34 | |
| 35 | class TestQmllint: public QQmlDataTest |
| 36 | { |
| 37 | Q_OBJECT |
| 38 | |
| 39 | private Q_SLOTS: |
| 40 | void initTestCase() override; |
| 41 | |
| 42 | void testUnqualified(); |
| 43 | void testUnqualified_data(); |
| 44 | |
| 45 | void cleanQmlCode_data(); |
| 46 | void cleanQmlCode(); |
| 47 | |
| 48 | void dirtyQmlCode_data(); |
| 49 | void dirtyQmlCode(); |
| 50 | |
| 51 | void testUnknownCausesFail(); |
| 52 | |
| 53 | private: |
| 54 | QString runQmllint(const QString &fileToLint, bool shouldSucceed); |
| 55 | |
| 56 | QString m_qmllintPath; |
| 57 | }; |
| 58 | |
| 59 | void TestQmllint::initTestCase() |
| 60 | { |
| 61 | QQmlDataTest::initTestCase(); |
| 62 | m_qmllintPath = QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/qmllint" ); |
| 63 | #ifdef Q_OS_WIN |
| 64 | m_qmllintPath += QLatin1String(".exe" ); |
| 65 | #endif |
| 66 | if (!QFileInfo(m_qmllintPath).exists()) { |
| 67 | QString message = QStringLiteral("qmllint executable not found (looked for %0)" ).arg(a: m_qmllintPath); |
| 68 | QFAIL(qPrintable(message)); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | void TestQmllint::testUnqualified() |
| 73 | { |
| 74 | QFETCH(QString, filename); |
| 75 | QFETCH(QString, warningMessage); |
| 76 | QFETCH(int, warningLine); |
| 77 | QFETCH(int, warningColumn); |
| 78 | |
| 79 | const QString output = runQmllint(fileToLint: filename, shouldSucceed: false); |
| 80 | QVERIFY(output.contains(QString::asprintf("Warning: unqualified access at %d:%d" , warningLine, warningColumn))); |
| 81 | QVERIFY(output.contains(warningMessage)); |
| 82 | } |
| 83 | |
| 84 | void TestQmllint::testUnqualified_data() |
| 85 | { |
| 86 | QTest::addColumn<QString>(name: "filename" ); |
| 87 | QTest::addColumn<QString>(name: "warningMessage" ); |
| 88 | QTest::addColumn<int>(name: "warningLine" ); |
| 89 | QTest::addColumn<int>(name: "warningColumn" ); |
| 90 | |
| 91 | // check for false positive due to and warning about with statement |
| 92 | QTest::newRow(dataTag: "WithStatement" ) << QStringLiteral("WithStatement.qml" ) << QStringLiteral("with statements are strongly discouraged" ) << 10 << 25; |
| 93 | // id from nowhere (as with setContextProperty) |
| 94 | QTest::newRow(dataTag: "IdFromOuterSpaceDirect" ) << QStringLiteral("IdFromOuterSpace.qml" ) << "alien.x" << 4 << 8; |
| 95 | QTest::newRow(dataTag: "IdFromOuterSpaceAccess" ) << QStringLiteral("IdFromOuterSpace.qml" ) << "console.log(alien)" << 7 << 21; |
| 96 | // access property of root object |
| 97 | QTest::newRow(dataTag: "FromRootDirect" ) << QStringLiteral("FromRoot.qml" ) << QStringLiteral("x: root.unqualified" ) << 9 << 16; // new property |
| 98 | QTest::newRow(dataTag: "FromRootAccess" ) << QStringLiteral("FromRoot.qml" ) << QStringLiteral("property int check: root.x" ) << 13 << 33; // builtin property |
| 99 | // access injected name from signal |
| 100 | QTest::newRow(dataTag: "SignalHandler1" ) << QStringLiteral("SignalHandler.qml" ) << QStringLiteral("onDoubleClicked: function(mouse) {..." ) << 5 << 21; |
| 101 | QTest::newRow(dataTag: "SignalHandler2" ) << QStringLiteral("SignalHandler.qml" ) << QStringLiteral("onPositionChanged: function(mouse) {..." ) << 10 << 21; |
| 102 | QTest::newRow(dataTag: "SignalHandlerShort1" ) << QStringLiteral("SignalHandler.qml" ) << QStringLiteral("onClicked: (mouse) => {..." ) << 8 << 29; |
| 103 | QTest::newRow(dataTag: "SignalHandlerShort2" ) << QStringLiteral("SignalHandler.qml" ) << QStringLiteral("onPressAndHold: (mouse) => {..." ) << 12 << 34; |
| 104 | // access catch identifier outside catch block |
| 105 | QTest::newRow(dataTag: "CatchStatement" ) << QStringLiteral("CatchStatement.qml" ) << QStringLiteral("err" ) << 6 << 21; |
| 106 | |
| 107 | QTest::newRow(dataTag: "NonSpuriousParent" ) << QStringLiteral("nonSpuriousParentWarning.qml" ) << QStringLiteral("property int x: <id>.parent.x" ) << 6 << 25; |
| 108 | } |
| 109 | |
| 110 | void TestQmllint::testUnknownCausesFail() |
| 111 | { |
| 112 | const QString unknownNotFound = runQmllint(fileToLint: "unknownElement.qml" , shouldSucceed: false); |
| 113 | QVERIFY(unknownNotFound.contains( |
| 114 | QStringLiteral("warning: Unknown was not found. Did you add all import paths?" ))); |
| 115 | } |
| 116 | |
| 117 | void TestQmllint::dirtyQmlCode_data() |
| 118 | { |
| 119 | QTest::addColumn<QString>(name: "filename" ); |
| 120 | QTest::addColumn<QString>(name: "warningMessage" ); |
| 121 | QTest::addColumn<QString>(name: "notContained" ); |
| 122 | |
| 123 | QTest::newRow(dataTag: "Invalid_syntax_QML" ) |
| 124 | << QStringLiteral("failure1.qml" ) |
| 125 | << QStringLiteral("failure1.qml:4 : Expected token `:'" ) |
| 126 | << QString(); |
| 127 | QTest::newRow(dataTag: "Invalid_syntax_JS" ) |
| 128 | << QStringLiteral("failure1.js" ) |
| 129 | << QStringLiteral("failure1.js:4 : Expected token `;'" ) |
| 130 | << QString(); |
| 131 | QTest::newRow(dataTag: "AutomatchedSignalHandler" ) |
| 132 | << QStringLiteral("AutomatchedSignalHandler.qml" ) |
| 133 | << QString("Warning: unqualified access at 12:36" ) |
| 134 | << QStringLiteral("no matching signal found" ); |
| 135 | QTest::newRow(dataTag: "MemberNotFound" ) |
| 136 | << QStringLiteral("memberNotFound.qml" ) |
| 137 | << QString("Warning: Property \"foo\" not found on type \"QtObject\" at 6:31" ) |
| 138 | << QString(); |
| 139 | QTest::newRow(dataTag: "UnknownJavascriptMethd" ) |
| 140 | << QStringLiteral("unknownJavascriptMethod.qml" ) |
| 141 | << QString("Warning: Property \"foo2\" not found on type \"Methods\" at 5:25" ) |
| 142 | << QString(); |
| 143 | QTest::newRow(dataTag: "badAlias" ) |
| 144 | << QStringLiteral("badAlias.qml" ) |
| 145 | << QString("Warning: unqualified access at 4:27" ) |
| 146 | << QString(); |
| 147 | QTest::newRow(dataTag: "badAliasProperty" ) |
| 148 | << QStringLiteral("badAliasProperty.qml" ) |
| 149 | << QString("Warning: Property \"nowhere\" not found on type \"QtObject\" at 5:32" ) |
| 150 | << QString(); |
| 151 | QTest::newRow(dataTag: "badParent" ) |
| 152 | << QStringLiteral("badParent.qml" ) |
| 153 | << QString("Warning: Property \"rrr\" not found on type \"Item\" at 5:34" ) |
| 154 | << QString(); |
| 155 | QTest::newRow(dataTag: "parentIsComponent" ) |
| 156 | << QStringLiteral("parentIsComponent.qml" ) |
| 157 | << QString("Warning: Property \"progress\" not found on type \"QQuickItem\" at 7:39" ) |
| 158 | << QString(); |
| 159 | QTest::newRow(dataTag: "badTypeAssertion" ) |
| 160 | << QStringLiteral("badTypeAssertion.qml" ) |
| 161 | << QString("Warning: Property \"rrr\" not found on type \"Item\" at 5:39" ) |
| 162 | << QString(); |
| 163 | QTest::newRow(dataTag: "incompleteQmltypes" ) |
| 164 | << QStringLiteral("incompleteQmltypes.qml" ) |
| 165 | << QString("Warning: Type \"QPalette\" of member \"palette\" not found at 5:26" ) |
| 166 | << QString(); |
| 167 | QTest::newRow(dataTag: "inheritanceCylce" ) |
| 168 | << QStringLiteral("Cycle1.qml" ) |
| 169 | << QString("Warning: Cycle2 is part of an inheritance cycle: Cycle2 -> Cycle3 -> Cycle1 -> Cycle2" ) |
| 170 | << QString(); |
| 171 | } |
| 172 | |
| 173 | void TestQmllint::dirtyQmlCode() |
| 174 | { |
| 175 | QFETCH(QString, filename); |
| 176 | QFETCH(QString, warningMessage); |
| 177 | QFETCH(QString, notContained); |
| 178 | |
| 179 | const QString output = runQmllint(fileToLint: filename, shouldSucceed: false); |
| 180 | QVERIFY(output.contains(warningMessage)); |
| 181 | if (!notContained.isEmpty()) |
| 182 | QVERIFY(!output.contains(notContained)); |
| 183 | } |
| 184 | |
| 185 | void TestQmllint::cleanQmlCode_data() |
| 186 | { |
| 187 | QTest::addColumn<QString>(name: "filename" ); |
| 188 | QTest::newRow(dataTag: "Simple_QML" ) << QStringLiteral("Simple.qml" ); |
| 189 | QTest::newRow(dataTag: "QML_importing_JS" ) << QStringLiteral("importing_js.qml" ); |
| 190 | QTest::newRow(dataTag: "JS_with_pragma_and_import" ) << QStringLiteral("QTBUG-45916.js" ); |
| 191 | QTest::newRow(dataTag: "uiQml" ) << QStringLiteral("FormUser.qml" ); |
| 192 | QTest::newRow(dataTag: "methodInScope" ) << QStringLiteral("MethodInScope.qml" ); |
| 193 | QTest::newRow(dataTag: "importWithPrefix" ) << QStringLiteral("ImportWithPrefix.qml" ); |
| 194 | QTest::newRow(dataTag: "catchIdentifier" ) << QStringLiteral("catchIdentifierNoWarning.qml" ); |
| 195 | QTest::newRow(dataTag: "qmldirAndQmltypes" ) << QStringLiteral("qmldirAndQmltypes.qml" ); |
| 196 | QTest::newRow(dataTag: "forLoop" ) << QStringLiteral("forLoop.qml" ); |
| 197 | QTest::newRow(dataTag: "esmodule" ) << QStringLiteral("esmodule.mjs" ); |
| 198 | QTest::newRow(dataTag: "methodsInJavascript" ) << QStringLiteral("javascriptMethods.qml" ); |
| 199 | QTest::newRow(dataTag: "goodAlias" ) << QStringLiteral("goodAlias.qml" ); |
| 200 | QTest::newRow(dataTag: "goodParent" ) << QStringLiteral("goodParent.qml" ); |
| 201 | QTest::newRow(dataTag: "goodTypeAssertion" ) << QStringLiteral("goodTypeAssertion.qml" ); |
| 202 | QTest::newRow(dataTag: "AttachedProps" ) << QStringLiteral("AttachedProps.qml" ); |
| 203 | QTest::newRow(dataTag: "unknownBuiltinFont" ) << QStringLiteral("ButtonLoader.qml" ); |
| 204 | QTest::newRow(dataTag: "confusingImport" ) << QStringLiteral("Dialog.qml" ); |
| 205 | QTest::newRow(dataTag: "qualifiedAttached" ) << QStringLiteral("Drawer.qml" ); |
| 206 | } |
| 207 | |
| 208 | void TestQmllint::cleanQmlCode() |
| 209 | { |
| 210 | QFETCH(QString, filename); |
| 211 | const QString warnings = runQmllint(fileToLint: filename, shouldSucceed: true); |
| 212 | QVERIFY(warnings.isEmpty()); |
| 213 | } |
| 214 | |
| 215 | QString TestQmllint::runQmllint(const QString &fileToLint, bool shouldSucceed) |
| 216 | { |
| 217 | auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); |
| 218 | QStringList args; |
| 219 | args << QStringLiteral("-U" ) << testFile(fileName: fileToLint) |
| 220 | << QStringLiteral("-I" ) << qmlImportDir |
| 221 | << QStringLiteral("-I" ) << dataDirectory() |
| 222 | << QStringLiteral("--silent" ); |
| 223 | QString errors; |
| 224 | auto verify = [&](bool isSilent) { |
| 225 | QProcess process; |
| 226 | process.start(program: m_qmllintPath, arguments: args); |
| 227 | QVERIFY(process.waitForFinished()); |
| 228 | QCOMPARE(process.exitStatus(), QProcess::NormalExit); |
| 229 | if (shouldSucceed) |
| 230 | QCOMPARE(process.exitCode(), 0); |
| 231 | else |
| 232 | QVERIFY(process.exitCode() != 0); |
| 233 | errors = process.readAllStandardError(); |
| 234 | |
| 235 | if (isSilent) |
| 236 | QVERIFY(errors.isEmpty()); |
| 237 | }; |
| 238 | verify(true); |
| 239 | args.removeLast(); |
| 240 | verify(false); |
| 241 | return errors; |
| 242 | } |
| 243 | |
| 244 | QTEST_MAIN(TestQmllint) |
| 245 | #include "tst_qmllint.moc" |
| 246 | |