1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
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 http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include <QtTest>
38#include <QtQml>
39#include <QtCore/private/qhooks_p.h>
40#include <QtQml/private/qqmljsengine_p.h>
41#include <QtQml/private/qqmljslexer_p.h>
42#include <QtQml/private/qqmljsparser_p.h>
43#include <QtQml/private/qqmljsast_p.h>
44#include <QtQml/private/qqmljsastvisitor_p.h>
45#include <QtQml/private/qqmlmetatype_p.h>
46#include "../../auto/shared/visualtestutil.h"
47
48using namespace QQuickVisualTestUtil;
49
50Q_GLOBAL_STATIC(QObjectList, qt_qobjects)
51
52extern "C" Q_DECL_EXPORT void qt_addQObject(QObject *object)
53{
54 qt_qobjects->append(t: object);
55}
56
57extern "C" Q_DECL_EXPORT void qt_removeQObject(QObject *object)
58{
59 qt_qobjects->removeAll(t: object);
60}
61
62class tst_Sanity : public QObject
63{
64 Q_OBJECT
65
66private slots:
67 void init();
68 void cleanup();
69 void initTestCase();
70
71 void jsFiles();
72 void functions();
73 void functions_data();
74 void signalHandlers();
75 void signalHandlers_data();
76 void anchors();
77 void anchors_data();
78 void attachedObjects();
79 void attachedObjects_data();
80 void ids();
81 void ids_data();
82
83private:
84 QQmlEngine engine;
85 QMap<QString, QString> files;
86};
87
88void tst_Sanity::init()
89{
90 qtHookData[QHooks::AddQObject] = reinterpret_cast<quintptr>(&qt_addQObject);
91 qtHookData[QHooks::RemoveQObject] = reinterpret_cast<quintptr>(&qt_removeQObject);
92}
93
94void tst_Sanity::cleanup()
95{
96 qt_qobjects->clear();
97 qtHookData[QHooks::AddQObject] = 0;
98 qtHookData[QHooks::RemoveQObject] = 0;
99}
100
101class BaseValidator : public QQmlJS::AST::Visitor
102{
103public:
104 QString errors() const { return m_errors.join(sep: ", "); }
105
106 bool validate(const QString& filePath)
107 {
108 m_errors.clear();
109 m_fileName = QFileInfo(filePath).fileName();
110
111 QFile file(filePath);
112 if (!file.open(flags: QFile::ReadOnly)) {
113 m_errors += QString("%1: failed to open (%2)").arg(args&: m_fileName, args: file.errorString());
114 return false;
115 }
116
117 QQmlJS::Engine engine;
118 QQmlJS::Lexer lexer(&engine);
119 lexer.setCode(code: QString::fromUtf8(str: file.readAll()), /*line = */ lineno: 1);
120
121 QQmlJS::Parser parser(&engine);
122 if (!parser.parse()) {
123 const auto diagnosticMessages = parser.diagnosticMessages();
124 for (const QQmlJS::DiagnosticMessage &msg : diagnosticMessages)
125#if Q_QML_PRIVATE_API_VERSION >= 8
126 m_errors += QString("%s:%d : %s").arg(m_fileName).arg(msg.loc.startLine).arg(msg.message);
127#else
128 m_errors += QString("%s:%d : %s").arg(m_fileName).arg(msg.line).arg(msg.message);
129#endif
130 return false;
131 }
132
133 QQmlJS::AST::UiProgram* ast = parser.ast();
134 ast->accept(visitor: this);
135 return m_errors.isEmpty();
136 }
137
138protected:
139 void addError(const QString& error, QQmlJS::AST::Node *node)
140 {
141 m_errors += QString("%1:%2 : %3").arg(a: m_fileName).arg(a: node->firstSourceLocation().startLine).arg(a: error);
142 }
143
144 void throwRecursionDepthError() final
145 {
146 m_errors += QString::fromLatin1(str: "%1: Maximum statement or expression depth exceeded")
147 .arg(a: m_fileName);
148 }
149
150private:
151 QString m_fileName;
152 QStringList m_errors;
153};
154
155void tst_Sanity::initTestCase()
156{
157 QQmlEngine engine;
158 QQmlComponent component(&engine);
159 component.setData(QString("import QtQuick.Templates 2.%1; Control { }").arg(QT_VERSION_MINOR - 7).toUtf8(), baseUrl: QUrl());
160
161 const QStringList qmlTypeNames = QQmlMetaType::qmlTypeNames();
162
163 QDirIterator it(QQC2_IMPORT_PATH, QStringList() << "*.qml" << "*.js", QDir::Files, QDirIterator::Subdirectories);
164 while (it.hasNext()) {
165 it.next();
166 QFileInfo info = it.fileInfo();
167 if (qmlTypeNames.contains(QStringLiteral("QtQuick.Templates/") + info.baseName()))
168 files.insert(key: info.dir().dirName() + "/" + info.fileName(), value: info.filePath());
169 }
170}
171
172void tst_Sanity::jsFiles()
173{
174 QMap<QString, QString>::const_iterator it;
175 for (it = files.constBegin(); it != files.constEnd(); ++it) {
176 if (QFileInfo(it.value()).suffix() == QStringLiteral("js"))
177 QFAIL(qPrintable(it.value() + ": JS files are not allowed"));
178 }
179}
180
181class FunctionValidator : public BaseValidator
182{
183protected:
184 virtual bool visit(QQmlJS::AST::FunctionDeclaration *node)
185 {
186 addError(error: "function declarations are not allowed", node);
187 return true;
188 }
189};
190
191void tst_Sanity::functions()
192{
193 QFETCH(QString, control);
194 QFETCH(QString, filePath);
195
196 FunctionValidator validator;
197 if (!validator.validate(filePath))
198 QFAIL(qPrintable(validator.errors()));
199}
200
201void tst_Sanity::functions_data()
202{
203 QTest::addColumn<QString>(name: "control");
204 QTest::addColumn<QString>(name: "filePath");
205
206 QMap<QString, QString>::const_iterator it;
207 for (it = files.constBegin(); it != files.constEnd(); ++it)
208 QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
209}
210
211class SignalHandlerValidator : public BaseValidator
212{
213protected:
214 static bool isSignalHandler(const QStringRef &name)
215 {
216 return name.length() > 2 && name.startsWith(s: "on") && name.at(i: 2).isUpper();
217 }
218
219 virtual bool visit(QQmlJS::AST::UiScriptBinding *node)
220 {
221 QQmlJS::AST::UiQualifiedId* id = node->qualifiedId;
222 if ((id && isSignalHandler(name: id->name)) || (id && id->next && isSignalHandler(name: id->next->name)))
223 addError(error: "signal handlers are not allowed", node);
224 return true;
225 }
226};
227
228void tst_Sanity::signalHandlers()
229{
230 QFETCH(QString, control);
231 QFETCH(QString, filePath);
232
233 SignalHandlerValidator validator;
234 if (!validator.validate(filePath))
235 QFAIL(qPrintable(validator.errors()));
236}
237
238void tst_Sanity::signalHandlers_data()
239{
240 QTest::addColumn<QString>(name: "control");
241 QTest::addColumn<QString>(name: "filePath");
242
243 QMap<QString, QString>::const_iterator it;
244 for (it = files.constBegin(); it != files.constEnd(); ++it)
245 QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
246}
247
248class AnchorValidator : public BaseValidator
249{
250protected:
251 virtual bool visit(QQmlJS::AST::UiScriptBinding *node)
252 {
253 QQmlJS::AST::UiQualifiedId* id = node->qualifiedId;
254 if (id && id->name == QStringLiteral("anchors"))
255 addError(error: "anchors are not allowed", node);
256 return true;
257 }
258};
259
260void tst_Sanity::anchors()
261{
262 QFETCH(QString, control);
263 QFETCH(QString, filePath);
264
265 AnchorValidator validator;
266 if (!validator.validate(filePath))
267 QFAIL(qPrintable(validator.errors()));
268}
269
270void tst_Sanity::anchors_data()
271{
272 QTest::addColumn<QString>(name: "control");
273 QTest::addColumn<QString>(name: "filePath");
274
275 QMap<QString, QString>::const_iterator it;
276 for (it = files.constBegin(); it != files.constEnd(); ++it)
277 QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
278}
279
280class IdValidator : public BaseValidator
281{
282public:
283 IdValidator() : m_depth(0) { }
284
285protected:
286 bool visit(QQmlJS::AST::UiObjectBinding *) override
287 {
288 ++m_depth;
289 return true;
290 }
291
292 void endVisit(QQmlJS::AST::UiObjectBinding *) override
293 {
294 --m_depth;
295 }
296
297 bool visit(QQmlJS::AST::UiScriptBinding *node) override
298 {
299 if (m_depth == 0)
300 return true;
301
302 QQmlJS::AST::UiQualifiedId *id = node->qualifiedId;
303 if (id && id->name == QStringLiteral("id"))
304 addError(error: QString("Internal IDs are not allowed (%1)").arg(a: extractName(statement: node->statement)), node);
305 return true;
306 }
307
308private:
309 QString extractName(QQmlJS::AST::Statement *statement)
310 {
311 QQmlJS::AST::ExpressionStatement *expressionStatement = static_cast<QQmlJS::AST::ExpressionStatement *>(statement);
312 if (!expressionStatement)
313 return QString();
314
315 QQmlJS::AST::IdentifierExpression *expression = static_cast<QQmlJS::AST::IdentifierExpression *>(expressionStatement->expression);
316 if (!expression)
317 return QString();
318
319 return expression->name.toString();
320 }
321
322 int m_depth;
323};
324
325void tst_Sanity::ids()
326{
327 QFETCH(QString, control);
328 QFETCH(QString, filePath);
329
330 IdValidator validator;
331 if (!validator.validate(filePath))
332 QFAIL(qPrintable(validator.errors()));
333}
334
335void tst_Sanity::ids_data()
336{
337 QTest::addColumn<QString>(name: "control");
338 QTest::addColumn<QString>(name: "filePath");
339
340 QMap<QString, QString>::const_iterator it;
341 for (it = files.constBegin(); it != files.constEnd(); ++it)
342 QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
343}
344
345void tst_Sanity::attachedObjects()
346{
347 QFETCH(QUrl, url);
348
349 QQmlComponent component(&engine);
350 component.loadUrl(url);
351
352 QSet<QString> classNames;
353 QScopedPointer<QObject> object(component.create());
354 QVERIFY2(object.data(), qPrintable(component.errorString()));
355 for (QObject *object : qAsConst(t&: *qt_qobjects)) {
356 if (object->parent() == &engine)
357 continue; // allow "global" instances
358 QString className = object->metaObject()->className();
359 if (className.endsWith(s: "Attached") || className.endsWith(s: "Style"))
360 QVERIFY2(!classNames.contains(className), qPrintable(QString("Multiple %1 instances").arg(className)));
361 classNames.insert(value: className);
362 }
363}
364
365void tst_Sanity::attachedObjects_data()
366{
367 QTest::addColumn<QUrl>(name: "url");
368 addTestRowForEachControl(engine: &engine, sourcePath: "calendar", targetPath: "Qt/labs/calendar");
369 addTestRowForEachControl(engine: &engine, sourcePath: "controls", targetPath: "QtQuick/Controls.2");
370 addTestRowForEachControl(engine: &engine, sourcePath: "controls/fusion", targetPath: "QtQuick/Controls.2", skiplist: QStringList() << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator");
371 addTestRowForEachControl(engine: &engine, sourcePath: "controls/material", targetPath: "QtQuick/Controls.2/Material", skiplist: QStringList() << "Ripple" << "SliderHandle" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator" << "BoxShadow" << "ElevationEffect" << "CursorDelegate");
372 addTestRowForEachControl(engine: &engine, sourcePath: "controls/universal", targetPath: "QtQuick/Controls.2/Universal", skiplist: QStringList() << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator");
373}
374
375QTEST_MAIN(tst_Sanity)
376
377#include "tst_sanity.moc"
378

source code of qtquickcontrols2/tests/auto/sanity/tst_sanity.cpp