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
35class TestQmllint: public QQmlDataTest
36{
37 Q_OBJECT
38
39private 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
53private:
54 QString runQmllint(const QString &fileToLint, bool shouldSucceed);
55
56 QString m_qmllintPath;
57};
58
59void 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
72void 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
84void 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
110void 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
117void 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
173void 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
185void 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
208void TestQmllint::cleanQmlCode()
209{
210 QFETCH(QString, filename);
211 const QString warnings = runQmllint(fileToLint: filename, shouldSucceed: true);
212 QVERIFY(warnings.isEmpty());
213}
214
215QString 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
244QTEST_MAIN(TestQmllint)
245#include "tst_qmllint.moc"
246

source code of qtdeclarative/tests/auto/qml/qmllint/tst_qmllint.cpp