2019-06-14 12:21:25 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
**
|
|
|
|
** This file is part of the tools applications of the Qt Toolkit.
|
|
|
|
**
|
|
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
|
|
** Commercial License Usage
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
**
|
|
|
|
** GNU General Public License Usage
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
**
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
#include "findwarnings.h"
|
2019-11-11 16:35:09 +00:00
|
|
|
#include "importedmembersvisitor.h"
|
2019-06-14 12:21:25 +00:00
|
|
|
#include "scopetree.h"
|
2019-11-11 17:18:04 +00:00
|
|
|
#include "typedescriptionreader.h"
|
2020-03-30 15:42:48 +00:00
|
|
|
#include "checkidentifiers.h"
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2019-11-11 17:18:04 +00:00
|
|
|
#include <QtQml/private/qqmljsast_p.h>
|
|
|
|
#include <QtQml/private/qqmljslexer_p.h>
|
|
|
|
#include <QtQml/private/qqmljsparser_p.h>
|
|
|
|
#include <QtQml/private/qv4codegen_p.h>
|
|
|
|
#include <QtQml/private/qqmldirparser_p.h>
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2019-11-11 17:18:04 +00:00
|
|
|
#include <QtCore/qfile.h>
|
|
|
|
#include <QtCore/qdiriterator.h>
|
|
|
|
#include <QtCore/qscopedvaluerollback.h>
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2019-11-08 17:43:31 +00:00
|
|
|
static const QString prefixedName(const QString &prefix, const QString &name)
|
|
|
|
{
|
2020-03-16 11:15:10 +00:00
|
|
|
Q_ASSERT(!prefix.endsWith('.'));
|
2019-11-08 17:43:31 +00:00
|
|
|
return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
|
|
|
|
}
|
|
|
|
|
2019-09-13 09:34:08 +00:00
|
|
|
static QQmlDirParser createQmldirParserForFile(const QString &filename)
|
|
|
|
{
|
|
|
|
QFile f(filename);
|
|
|
|
f.open(QFile::ReadOnly);
|
|
|
|
QQmlDirParser parser;
|
|
|
|
parser.parse(f.readAll());
|
|
|
|
return parser;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::enterEnvironment(ScopeType type, const QString &name)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-03-31 13:46:20 +00:00
|
|
|
m_currentScope = ScopeTree::create(type, name, m_currentScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::leaveEnvironment()
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
m_currentScope = m_currentScope->parentScope();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::parseHeaders(QQmlJS::AST::UiHeaderItemList *header)
|
2019-11-08 17:43:31 +00:00
|
|
|
{
|
|
|
|
using namespace QQmlJS::AST;
|
|
|
|
|
|
|
|
while (header) {
|
|
|
|
if (auto import = cast<UiImport *>(header->headerItem)) {
|
|
|
|
if (import->version) {
|
|
|
|
QString path;
|
|
|
|
auto uri = import->importUri;
|
|
|
|
while (uri) {
|
|
|
|
path.append(uri->name);
|
|
|
|
path.append("/");
|
|
|
|
uri = uri->next;
|
|
|
|
}
|
|
|
|
path.chop(1);
|
2020-03-16 11:15:10 +00:00
|
|
|
importHelper(path,
|
|
|
|
import->asToken.isValid() ? import->importId.toString() : QString(),
|
2020-03-18 10:44:13 +00:00
|
|
|
import->version->version);
|
2019-11-08 17:43:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
header = header->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
ScopeTree::Ptr FindWarningVisitor::parseProgram(QQmlJS::AST::Program *program,
|
2020-03-31 13:46:20 +00:00
|
|
|
const QString &name)
|
2019-11-08 17:43:31 +00:00
|
|
|
{
|
|
|
|
using namespace QQmlJS::AST;
|
2020-03-31 13:46:20 +00:00
|
|
|
ScopeTree::Ptr result = ScopeTree::create(ScopeType::JSLexicalScope, name);
|
2019-11-08 17:43:31 +00:00
|
|
|
for (auto *statement = program->statements; statement; statement = statement->next) {
|
|
|
|
if (auto *function = cast<FunctionDeclaration *>(statement->statement)) {
|
|
|
|
MetaMethod method(function->name.toString());
|
|
|
|
method.setMethodType(MetaMethod::Method);
|
|
|
|
for (auto *parameters = function->formals; parameters; parameters = parameters->next)
|
|
|
|
method.addParameter(parameters->element->bindingIdentifier.toString(), "");
|
2019-11-11 16:35:09 +00:00
|
|
|
result->addMethod(method);
|
2019-11-08 17:43:31 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-11 16:35:09 +00:00
|
|
|
return result;
|
2019-11-08 17:43:31 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 09:34:08 +00:00
|
|
|
enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath };
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
QStringList completeImportPaths(const QString &uri, const QString &basePath, QTypeRevision version)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
static const QLatin1Char Slash('/');
|
|
|
|
static const QLatin1Char Backslash('\\');
|
|
|
|
|
2020-02-25 15:10:40 +00:00
|
|
|
const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), Qt::SkipEmptyParts);
|
2019-06-14 12:21:25 +00:00
|
|
|
|
|
|
|
QStringList qmlDirPathsPaths;
|
|
|
|
// fully & partially versioned parts + 1 unversioned for each base path
|
2019-11-14 16:08:15 +00:00
|
|
|
qmlDirPathsPaths.reserve(2 * parts.count() + 1);
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
auto versionString = [](QTypeRevision version, ImportVersion mode)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-01-22 12:12:56 +00:00
|
|
|
if (mode == FullyVersioned) {
|
2019-06-14 12:21:25 +00:00
|
|
|
// extension with fully encoded version number (eg. MyModule.3.2)
|
2020-01-22 12:12:56 +00:00
|
|
|
return QString::fromLatin1(".%1.%2").arg(version.majorVersion())
|
|
|
|
.arg(version.minorVersion());
|
2019-11-11 17:18:04 +00:00
|
|
|
}
|
2020-01-22 12:12:56 +00:00
|
|
|
if (mode == PartiallyVersioned) {
|
2019-06-14 12:21:25 +00:00
|
|
|
// extension with encoded version major (eg. MyModule.3)
|
2020-01-22 12:12:56 +00:00
|
|
|
return QString::fromLatin1(".%1").arg(version.majorVersion());
|
2019-11-11 17:18:04 +00:00
|
|
|
}
|
|
|
|
// else extension without version number (eg. MyModule)
|
2019-06-14 12:21:25 +00:00
|
|
|
return QString();
|
|
|
|
};
|
2019-11-11 17:18:04 +00:00
|
|
|
auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep) {
|
2019-06-14 12:21:25 +00:00
|
|
|
QString str;
|
|
|
|
for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
|
|
|
|
if (it != refs.cbegin())
|
|
|
|
str += sep;
|
|
|
|
str += *it;
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
};
|
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
const ImportVersion initial = (version.hasMinorVersion())
|
2019-12-11 14:31:55 +00:00
|
|
|
? FullyVersioned
|
2020-01-22 12:12:56 +00:00
|
|
|
: (version.hasMajorVersion() ? PartiallyVersioned : Unversioned);
|
|
|
|
for (int mode = initial; mode <= BasePath; ++mode) {
|
|
|
|
const QString ver = versionString(version, ImportVersion(mode));
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2019-11-14 16:08:15 +00:00
|
|
|
QString dir = basePath;
|
|
|
|
if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
|
|
|
|
dir += Slash;
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
if (mode == BasePath) {
|
2019-11-14 16:08:15 +00:00
|
|
|
qmlDirPathsPaths += dir;
|
|
|
|
} else {
|
|
|
|
// append to the end
|
|
|
|
qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver;
|
|
|
|
}
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
if (mode < Unversioned) {
|
2019-11-14 16:08:15 +00:00
|
|
|
// insert in the middle
|
|
|
|
for (int index = parts.count() - 2; index >= 0; --index) {
|
|
|
|
qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
|
|
|
|
+ ver + Slash
|
|
|
|
+ joinStringRefs(parts.mid(index + 1), Slash);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return qmlDirPathsPaths;
|
|
|
|
}
|
|
|
|
|
2019-11-14 16:08:15 +00:00
|
|
|
static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
|
|
|
|
static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes");
|
2019-11-11 17:18:04 +00:00
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::readQmltypes(const QString &filename,
|
2020-04-29 08:47:19 +00:00
|
|
|
QHash<QString, ScopeTree::ConstPtr> *objects, QStringList *dependencies)
|
2019-11-14 16:08:15 +00:00
|
|
|
{
|
2020-04-29 08:47:19 +00:00
|
|
|
const QFileInfo fileInfo(filename);
|
|
|
|
if (!fileInfo.exists()) {
|
|
|
|
m_colorOut.write(QLatin1String("warning: "), Warning);
|
|
|
|
m_colorOut.writeUncolored(QLatin1String("QML types file does not exist: ") + filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fileInfo.isDir()) {
|
|
|
|
m_colorOut.write(QLatin1String("warning: "), Warning);
|
|
|
|
m_colorOut.writeUncolored(QLatin1String("QML types file cannot be a directory: ") + filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile file(filename);
|
|
|
|
file.open(QFile::ReadOnly);
|
|
|
|
TypeDescriptionReader reader { filename, file.readAll() };
|
|
|
|
auto succ = reader(objects, dependencies);
|
2019-11-14 16:08:15 +00:00
|
|
|
if (!succ)
|
|
|
|
m_colorOut.writeUncolored(reader.errorMessage());
|
|
|
|
}
|
2019-11-11 17:18:04 +00:00
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
FindWarningVisitor::Import FindWarningVisitor::readQmldir(const QString &path)
|
2019-11-14 16:08:15 +00:00
|
|
|
{
|
|
|
|
Import result;
|
|
|
|
auto reader = createQmldirParserForFile(path + SlashQmldir);
|
|
|
|
const auto imports = reader.imports();
|
|
|
|
for (const QString &import : imports)
|
|
|
|
result.dependencies.append(import);
|
|
|
|
|
2020-03-31 13:46:20 +00:00
|
|
|
QHash<QString, ScopeTree::Ptr> qmlComponents;
|
2019-11-14 16:08:15 +00:00
|
|
|
const auto components = reader.components();
|
|
|
|
for (auto it = components.begin(), end = components.end(); it != end; ++it) {
|
|
|
|
const QString filePath = path + QLatin1Char('/') + it->fileName;
|
|
|
|
if (!QFile::exists(filePath)) {
|
|
|
|
m_colorOut.write(QLatin1String("warning: "), Warning);
|
|
|
|
m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ")
|
|
|
|
+ path + SlashQmldir
|
|
|
|
+ QLatin1String(" but does not exist.\n"));
|
|
|
|
continue;
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
2019-11-14 16:08:15 +00:00
|
|
|
|
|
|
|
auto mo = qmlComponents.find(it.key());
|
|
|
|
if (mo == qmlComponents.end())
|
2019-11-08 17:43:31 +00:00
|
|
|
mo = qmlComponents.insert(it.key(), localFile2ScopeTree(filePath));
|
2019-11-14 16:08:15 +00:00
|
|
|
|
|
|
|
(*mo)->addExport(
|
|
|
|
it.key(), reader.typeNamespace(),
|
2020-01-22 12:12:56 +00:00
|
|
|
ComponentVersion(it->version));
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
2019-11-14 16:08:15 +00:00
|
|
|
for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
|
|
|
|
result.objects.insert( it.key(), ScopeTree::ConstPtr(it.value()));
|
|
|
|
|
|
|
|
if (!reader.plugins().isEmpty() && QFile::exists(path + SlashPluginsDotQmltypes))
|
2020-04-29 08:47:19 +00:00
|
|
|
readQmltypes(path + SlashPluginsDotQmltypes, &result.objects, &result.dependencies);
|
2019-11-14 16:08:15 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::processImport(const QString &prefix, const FindWarningVisitor::Import &import)
|
2019-11-14 16:08:15 +00:00
|
|
|
{
|
|
|
|
for (auto const &dependency : qAsConst(import.dependencies)) {
|
2019-06-14 12:21:25 +00:00
|
|
|
auto const split = dependency.split(" ");
|
2019-11-11 17:18:04 +00:00
|
|
|
auto const &id = split.at(0);
|
2019-12-11 14:31:55 +00:00
|
|
|
if (split.length() > 1) {
|
|
|
|
const auto version = split.at(1).split('.');
|
2020-01-22 12:12:56 +00:00
|
|
|
importHelper(id, QString(), QTypeRevision::fromVersion(
|
|
|
|
version.at(0).toInt(),
|
|
|
|
version.length() > 1 ? version.at(1).toInt() : -1));
|
2019-12-11 14:31:55 +00:00
|
|
|
} else {
|
2020-01-22 12:12:56 +00:00
|
|
|
importHelper(id, QString(), QTypeRevision());
|
2019-12-11 14:31:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
2019-11-14 16:08:15 +00:00
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
// add objects
|
2019-11-14 16:08:15 +00:00
|
|
|
for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
|
|
|
|
const auto &val = it.value();
|
2019-11-08 13:48:32 +00:00
|
|
|
m_types[it.key()] = val;
|
2019-11-08 17:43:31 +00:00
|
|
|
m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val);
|
2019-11-11 17:18:04 +00:00
|
|
|
|
|
|
|
const auto exports = val->exports();
|
|
|
|
for (const auto &valExport : exports)
|
2019-11-08 17:43:31 +00:00
|
|
|
m_exportedName2Scope.insert(prefixedName(prefix, valExport.type()), val);
|
2019-11-11 17:18:04 +00:00
|
|
|
|
|
|
|
const auto enums = val->enums();
|
|
|
|
for (const auto &valEnum : enums)
|
|
|
|
m_currentScope->addEnum(valEnum);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::importHelper(const QString &module, const QString &prefix,
|
2020-01-22 12:12:56 +00:00
|
|
|
QTypeRevision version)
|
2019-11-14 16:08:15 +00:00
|
|
|
{
|
2019-12-11 14:31:55 +00:00
|
|
|
const QString id = QString(module).replace(QLatin1Char('/'), QLatin1Char('.'));
|
2019-11-14 16:08:15 +00:00
|
|
|
QPair<QString, QString> importId { id, prefix };
|
|
|
|
if (m_alreadySeenImports.contains(importId))
|
|
|
|
return;
|
|
|
|
m_alreadySeenImports.insert(importId);
|
|
|
|
|
|
|
|
for (const QString &qmltypeDir : m_qmltypeDirs) {
|
2020-01-22 12:12:56 +00:00
|
|
|
auto qmltypesPaths = completeImportPaths(id, qmltypeDir, version);
|
2019-11-14 16:08:15 +00:00
|
|
|
|
|
|
|
for (auto const &qmltypesPath : qmltypesPaths) {
|
|
|
|
if (QFile::exists(qmltypesPath + SlashQmldir)) {
|
|
|
|
processImport(prefix, readQmldir(qmltypesPath));
|
|
|
|
|
|
|
|
// break so that we don't import unversioned qml components
|
|
|
|
// in addition to versioned ones
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-03-26 09:57:12 +00:00
|
|
|
if (!m_qmltypeFiles.isEmpty())
|
2019-11-14 16:08:15 +00:00
|
|
|
continue;
|
2020-03-26 09:57:12 +00:00
|
|
|
|
|
|
|
Import result;
|
|
|
|
|
|
|
|
QDirIterator it { qmltypesPath, QStringList() << QLatin1String("*.qmltypes"), QDir::Files };
|
|
|
|
|
|
|
|
while (it.hasNext())
|
2020-04-29 08:47:19 +00:00
|
|
|
readQmltypes(it.next(), &result.objects, &result.dependencies);
|
2020-03-26 09:57:12 +00:00
|
|
|
|
2019-11-14 16:08:15 +00:00
|
|
|
processImport(prefix, result);
|
|
|
|
}
|
|
|
|
}
|
2020-03-26 09:57:12 +00:00
|
|
|
|
|
|
|
if (!m_qmltypeFiles.isEmpty())
|
|
|
|
{
|
|
|
|
Import result;
|
|
|
|
|
|
|
|
for (const auto &qmltypeFile : m_qmltypeFiles)
|
2020-04-29 08:47:19 +00:00
|
|
|
readQmltypes(qmltypeFile, &result.objects, &result.dependencies);
|
2020-03-26 09:57:12 +00:00
|
|
|
|
|
|
|
processImport("", result);
|
|
|
|
}
|
2019-11-14 16:08:15 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
ScopeTree::Ptr FindWarningVisitor::localFile2ScopeTree(const QString &filePath)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
using namespace QQmlJS::AST;
|
2019-11-08 17:43:31 +00:00
|
|
|
const QFileInfo info { filePath };
|
|
|
|
QString baseName = info.baseName();
|
2019-11-11 16:35:09 +00:00
|
|
|
const QString scopeName = baseName.endsWith(".ui") ? baseName.chopped(3) : baseName;
|
2019-06-14 12:21:25 +00:00
|
|
|
|
|
|
|
QQmlJS::Engine engine;
|
|
|
|
QQmlJS::Lexer lexer(&engine);
|
|
|
|
|
2019-11-08 17:43:31 +00:00
|
|
|
const QString lowerSuffix = info.suffix().toLower();
|
|
|
|
const bool isESModule = lowerSuffix == QLatin1String("mjs");
|
2019-11-11 16:35:09 +00:00
|
|
|
const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
|
|
|
|
|
|
|
|
QFile file(filePath);
|
|
|
|
if (!file.open(QFile::ReadOnly)) {
|
2020-03-31 13:46:20 +00:00
|
|
|
return ScopeTree::create(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
|
|
|
|
scopeName);
|
2019-11-11 16:35:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString code = file.readAll();
|
|
|
|
file.close();
|
|
|
|
|
2019-11-08 17:43:31 +00:00
|
|
|
lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript);
|
2019-06-14 12:21:25 +00:00
|
|
|
QQmlJS::Parser parser(&engine);
|
2019-11-08 17:43:31 +00:00
|
|
|
|
|
|
|
const bool success = isJavaScript ? (isESModule ? parser.parseModule()
|
|
|
|
: parser.parseProgram())
|
|
|
|
: parser.parse();
|
2019-11-11 16:35:09 +00:00
|
|
|
if (!success) {
|
2020-03-31 13:46:20 +00:00
|
|
|
return ScopeTree::create(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
|
|
|
|
scopeName);
|
2019-11-11 16:35:09 +00:00
|
|
|
}
|
2019-11-08 17:43:31 +00:00
|
|
|
|
|
|
|
if (!isJavaScript) {
|
|
|
|
QQmlJS::AST::UiProgram *program = parser.ast();
|
|
|
|
parseHeaders(program->headers);
|
2019-11-11 16:35:09 +00:00
|
|
|
ImportedMembersVisitor membersVisitor(&m_colorOut);
|
|
|
|
program->members->accept(&membersVisitor);
|
|
|
|
return membersVisitor.result(scopeName);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
2019-11-08 17:43:31 +00:00
|
|
|
|
2019-11-11 16:35:09 +00:00
|
|
|
// TODO: Anything special to do with ES modules here?
|
|
|
|
return parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scopeName);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::importFileOrDirectory(const QString &fileOrDirectory,
|
2019-11-08 17:43:31 +00:00
|
|
|
const QString &prefix)
|
2019-08-16 15:03:30 +00:00
|
|
|
{
|
2019-11-08 17:43:31 +00:00
|
|
|
QString name = fileOrDirectory;
|
|
|
|
|
|
|
|
if (QFileInfo(name).isRelative())
|
|
|
|
name = QDir(QFileInfo { m_filePath }.path()).filePath(name);
|
|
|
|
|
|
|
|
if (QFileInfo(name).isFile()) {
|
|
|
|
m_exportedName2Scope.insert(prefix, ScopeTree::ConstPtr(localFile2ScopeTree(name)));
|
|
|
|
return;
|
|
|
|
}
|
2019-08-16 15:03:30 +00:00
|
|
|
|
2019-11-08 17:43:31 +00:00
|
|
|
QDirIterator it { name, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
|
2019-08-16 15:03:30 +00:00
|
|
|
while (it.hasNext()) {
|
2019-11-08 17:43:31 +00:00
|
|
|
ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next()));
|
2020-01-09 16:27:38 +00:00
|
|
|
if (!scope->className().isEmpty())
|
2019-11-08 17:43:31 +00:00
|
|
|
m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), scope);
|
2019-08-16 15:03:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::importExportedNames(const QStringRef &prefix, QString name)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-03-16 13:50:24 +00:00
|
|
|
QList<ScopeTree::ConstPtr> scopes;
|
2019-06-14 12:21:25 +00:00
|
|
|
for (;;) {
|
2020-01-09 16:27:38 +00:00
|
|
|
ScopeTree::ConstPtr scope = m_exportedName2Scope.value(m_exportedName2Scope.contains(name)
|
|
|
|
? name
|
|
|
|
: prefix + QLatin1Char('.') + name);
|
2019-11-11 17:18:04 +00:00
|
|
|
if (scope) {
|
2020-03-16 13:50:24 +00:00
|
|
|
if (scopes.contains(scope)) {
|
|
|
|
QString inheritenceCycle = name;
|
2020-05-20 16:34:32 +00:00
|
|
|
for (const auto &seen: qAsConst(scopes)) {
|
2020-03-16 13:50:24 +00:00
|
|
|
inheritenceCycle.append(QLatin1String(" -> "));
|
|
|
|
inheritenceCycle.append(seen->superclassName());
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
|
|
|
|
if (m_warnInheritanceCycle) {
|
|
|
|
m_colorOut.write(QLatin1String("Warning: "), Warning);
|
|
|
|
m_colorOut.write(QString::fromLatin1("%1 is part of an inheritance cycle: %2\n")
|
|
|
|
.arg(name)
|
|
|
|
.arg(inheritenceCycle));
|
|
|
|
}
|
|
|
|
|
2020-03-16 13:50:24 +00:00
|
|
|
m_unknownImports.insert(name);
|
|
|
|
m_visitFailed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
scopes.append(scope);
|
2019-11-11 17:18:04 +00:00
|
|
|
const auto properties = scope->properties();
|
2019-11-08 13:48:32 +00:00
|
|
|
for (auto property : properties) {
|
2020-03-31 13:46:20 +00:00
|
|
|
property.setType(m_exportedName2Scope.value(property.typeName()));
|
2019-11-11 17:18:04 +00:00
|
|
|
m_currentScope->insertPropertyIdentifier(property);
|
2019-11-08 13:48:32 +00:00
|
|
|
}
|
2019-11-11 17:18:04 +00:00
|
|
|
|
|
|
|
m_currentScope->addMethods(scope->methods());
|
|
|
|
name = scope->superclassName();
|
|
|
|
if (name.isEmpty() || name == QLatin1String("QObject"))
|
2019-06-14 12:21:25 +00:00
|
|
|
break;
|
|
|
|
} else {
|
2019-07-23 14:10:24 +00:00
|
|
|
m_colorOut.write(QLatin1String("warning: "), Warning);
|
2019-11-11 17:18:04 +00:00
|
|
|
m_colorOut.write(name + QLatin1String(" was not found."
|
|
|
|
" Did you add all import paths?\n"));
|
2019-07-23 14:10:24 +00:00
|
|
|
m_unknownImports.insert(name);
|
2019-11-14 15:31:48 +00:00
|
|
|
m_visitFailed = true;
|
2019-06-14 12:21:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::throwRecursionDepthError()
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-08-16 08:14:49 +00:00
|
|
|
m_colorOut.write(QStringLiteral("Error"), Error);
|
|
|
|
m_colorOut.write(QStringLiteral("Maximum statement or expression depth exceeded"), Error);
|
|
|
|
m_visitFailed = true;
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiProgram *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::QMLScope, "program");
|
2019-11-11 17:18:04 +00:00
|
|
|
QHash<QString, ScopeTree::ConstPtr> objects;
|
2019-06-14 12:21:25 +00:00
|
|
|
QStringList dependencies;
|
|
|
|
for (auto const &dir : m_qmltypeDirs) {
|
|
|
|
QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter,
|
|
|
|
QDirIterator::Subdirectories };
|
|
|
|
while (it.hasNext()) {
|
2020-04-29 08:47:19 +00:00
|
|
|
readQmltypes(it.next(), &objects, &dependencies);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
}
|
2020-03-26 09:57:12 +00:00
|
|
|
|
|
|
|
if (!m_qmltypeFiles.isEmpty())
|
|
|
|
{
|
|
|
|
for (const auto &qmltypeFile : m_qmltypeFiles) {
|
2020-04-29 08:47:19 +00:00
|
|
|
readQmltypes(qmltypeFile, &objects, &dependencies);
|
2020-03-26 09:57:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
// add builtins
|
2019-11-08 13:48:32 +00:00
|
|
|
for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) {
|
|
|
|
auto val = objectIt.value();
|
|
|
|
m_types[objectIt.key()] = val;
|
2019-11-11 17:18:04 +00:00
|
|
|
|
|
|
|
const auto exports = val->exports();
|
|
|
|
for (const auto &valExport : exports)
|
2020-01-09 16:27:38 +00:00
|
|
|
m_exportedName2Scope.insert(valExport.type(), val);
|
2019-11-11 17:18:04 +00:00
|
|
|
|
|
|
|
const auto enums = val->enums();
|
|
|
|
for (const auto &valEnum : enums)
|
|
|
|
m_currentScope->addEnum(valEnum);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
// add "self" (as we only ever check the first part of a qualified identifier, we get away with
|
2019-11-11 17:18:04 +00:00
|
|
|
// using an empty ScopeTree
|
2020-01-09 16:27:38 +00:00
|
|
|
m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {});
|
2019-06-14 12:21:25 +00:00
|
|
|
|
2019-11-08 17:43:31 +00:00
|
|
|
importFileOrDirectory(".", QString());
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::UiProgram *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::ClassExpression *ast)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::ClassExpression *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::ForStatement *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "forloop");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::ForStatement *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::ForEachStatement *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "foreachloop");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::Block *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "block");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::Block *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::CaseBlock *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "case");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::CaseBlock *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::Catch *catchStatement)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "catch");
|
2019-11-11 17:18:04 +00:00
|
|
|
m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(),
|
2020-03-30 15:42:48 +00:00
|
|
|
ScopeType::JSLexicalScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::Catch *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-04-24 13:10:53 +00:00
|
|
|
if (m_warnWithStatement) {
|
|
|
|
m_colorOut.write(QString::fromLatin1("Warning: "), Warning);
|
|
|
|
m_colorOut.write(QString::fromLatin1(
|
|
|
|
"%1:%2: with statements are strongly discouraged in QML "
|
|
|
|
"and might cause false positives when analysing unqalified identifiers\n")
|
|
|
|
.arg(withStatement->firstSourceLocation().startLine)
|
|
|
|
.arg(withStatement->firstSourceLocation().startColumn),
|
|
|
|
Normal);
|
|
|
|
}
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
enterEnvironment(ScopeType::JSLexicalScope, "with");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::WithStatement *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2019-12-17 08:29:31 +00:00
|
|
|
static QString signalName(QStringView handlerName)
|
2019-08-16 09:31:51 +00:00
|
|
|
{
|
2019-12-17 08:29:31 +00:00
|
|
|
if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
|
2019-08-16 09:31:51 +00:00
|
|
|
QString signal = handlerName.mid(2).toString();
|
|
|
|
for (int i = 0; i < signal.length(); ++i) {
|
2019-12-17 08:29:31 +00:00
|
|
|
QChar &ch = signal[i];
|
2019-08-16 09:31:51 +00:00
|
|
|
if (ch.isLower())
|
|
|
|
return QString();
|
|
|
|
if (ch.isUpper()) {
|
|
|
|
ch = ch.toLower();
|
|
|
|
return signal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
using namespace QQmlJS::AST;
|
|
|
|
auto name = uisb->qualifiedId->name;
|
|
|
|
if (name == QLatin1String("id")) {
|
|
|
|
// found id
|
2019-11-11 17:18:04 +00:00
|
|
|
auto expstat = cast<ExpressionStatement *>(uisb->statement);
|
|
|
|
auto identexp = cast<IdentifierExpression *>(expstat->expression);
|
2019-06-14 12:21:25 +00:00
|
|
|
QString elementName = m_currentScope->name();
|
2019-11-08 13:48:32 +00:00
|
|
|
m_qmlid2scope.insert(identexp->name.toString(), m_currentScope);
|
2019-11-11 17:18:04 +00:00
|
|
|
if (m_currentScope->isVisualRootScope())
|
2019-06-14 12:21:25 +00:00
|
|
|
m_rootId = identexp->name.toString();
|
2019-08-16 09:31:51 +00:00
|
|
|
} else {
|
|
|
|
const QString signal = signalName(name);
|
|
|
|
if (signal.isEmpty())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!m_currentScope->methods().contains(signal)) {
|
|
|
|
m_currentScope->addUnmatchedSignalHandler(name.toString(), uisb->firstSourceLocation());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto statement = uisb->statement;
|
2019-06-14 12:21:25 +00:00
|
|
|
if (statement->kind == Node::Kind::Kind_ExpressionStatement) {
|
2019-11-11 17:18:04 +00:00
|
|
|
if (cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
|
2019-06-14 12:21:25 +00:00
|
|
|
// functions are already handled
|
|
|
|
// they do not get names inserted according to the signal, but access their formal
|
|
|
|
// parameters
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2019-08-16 09:31:51 +00:00
|
|
|
|
2020-04-09 16:43:43 +00:00
|
|
|
const auto methods = m_currentScope->methods();
|
|
|
|
const auto methodsRange = methods.equal_range(signal);
|
|
|
|
for (auto method = methodsRange.first; method != methodsRange.second; ++method) {
|
|
|
|
if (method->methodType() != MetaMethod::Signal)
|
|
|
|
continue;
|
|
|
|
for (auto const ¶m : method->parameterNames()) {
|
|
|
|
const auto firstSourceLocation = statement->firstSourceLocation();
|
|
|
|
bool hasMultilineStatementBody
|
|
|
|
= statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
|
|
|
|
m_currentScope->insertSignalIdentifier(param, *method, firstSourceLocation,
|
|
|
|
hasMultilineStatementBody);
|
|
|
|
}
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiPublicMember *uipm)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
// property bool inactive: !active
|
|
|
|
// extract name inactive
|
2019-11-08 13:48:32 +00:00
|
|
|
MetaProperty property(
|
2019-11-11 17:18:04 +00:00
|
|
|
uipm->name.toString(),
|
|
|
|
// TODO: signals, complex types etc.
|
|
|
|
uipm->memberType ? uipm->memberType->name.toString() : QString(),
|
|
|
|
uipm->typeModifier == QLatin1String("list"),
|
|
|
|
!uipm->isReadonlyMember,
|
2019-11-11 16:35:09 +00:00
|
|
|
false,
|
|
|
|
uipm->memberType ? (uipm->memberType->name == QLatin1String("alias")) : false,
|
|
|
|
0);
|
2020-03-31 13:46:20 +00:00
|
|
|
property.setType(m_exportedName2Scope.value(property.typeName()));
|
2019-11-08 13:48:32 +00:00
|
|
|
m_currentScope->insertPropertyIdentifier(property);
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
auto name = idexp->name;
|
2019-11-08 13:48:32 +00:00
|
|
|
m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation());
|
|
|
|
m_fieldMemberBase = idexp;
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
FindWarningVisitor::FindWarningVisitor(QStringList qmltypeDirs, QStringList qmltypeFiles, QString code,
|
|
|
|
QString fileName, bool silent, bool warnUnqualified,
|
|
|
|
bool warnWithStatement, bool warnInheritanceCycle)
|
2020-03-31 13:46:20 +00:00
|
|
|
: m_rootScope(ScopeTree::create(ScopeType::JSFunctionScope, "global")),
|
2019-11-11 17:18:04 +00:00
|
|
|
m_qmltypeDirs(std::move(qmltypeDirs)),
|
2020-03-26 09:57:12 +00:00
|
|
|
m_qmltypeFiles(std::move(qmltypeFiles)),
|
2019-11-11 17:18:04 +00:00
|
|
|
m_code(std::move(code)),
|
2019-06-14 12:21:25 +00:00
|
|
|
m_rootId(QLatin1String("<id>")),
|
2019-11-11 17:18:04 +00:00
|
|
|
m_filePath(std::move(fileName)),
|
2020-04-24 13:10:53 +00:00
|
|
|
m_colorOut(silent),
|
|
|
|
m_warnUnqualified(warnUnqualified),
|
|
|
|
m_warnWithStatement(warnWithStatement),
|
|
|
|
m_warnInheritanceCycle(warnInheritanceCycle)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-03-31 13:46:20 +00:00
|
|
|
m_currentScope = m_rootScope;
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
// setup color output
|
2019-11-11 17:18:04 +00:00
|
|
|
m_colorOut.insertMapping(Error, ColorOutput::RedForeground);
|
|
|
|
m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground);
|
|
|
|
m_colorOut.insertMapping(Info, ColorOutput::BlueForeground);
|
|
|
|
m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor);
|
|
|
|
m_colorOut.insertMapping(Hint, ColorOutput::GreenForeground);
|
2019-06-14 12:21:25 +00:00
|
|
|
QLatin1String jsGlobVars[] = {
|
2019-07-24 06:42:17 +00:00
|
|
|
/* Not listed on the MDN page; browser and QML extensions: */
|
|
|
|
// console/debug api
|
|
|
|
QLatin1String("console"), QLatin1String("print"),
|
|
|
|
// garbage collector
|
|
|
|
QLatin1String("gc"),
|
|
|
|
// i18n
|
2019-11-11 17:18:04 +00:00
|
|
|
QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"),
|
|
|
|
QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
|
2019-07-24 06:42:17 +00:00
|
|
|
// XMLHttpRequest
|
|
|
|
QLatin1String("XMLHttpRequest")
|
2019-06-14 12:21:25 +00:00
|
|
|
};
|
2019-11-11 17:18:04 +00:00
|
|
|
for (const char **globalName = QV4::Compiler::Codegen::s_globalNames;
|
|
|
|
*globalName != nullptr;
|
|
|
|
++globalName) {
|
|
|
|
m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName),
|
2020-03-30 15:42:48 +00:00
|
|
|
ScopeType::JSLexicalScope);
|
2019-07-24 06:42:17 +00:00
|
|
|
}
|
2019-06-14 12:21:25 +00:00
|
|
|
for (const auto& jsGlobVar: jsGlobVars)
|
2020-03-30 15:42:48 +00:00
|
|
|
m_currentScope->insertJSIdentifier(jsGlobVar, ScopeType::JSLexicalScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::check()
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-08-16 08:14:49 +00:00
|
|
|
if (m_visitFailed)
|
|
|
|
return false;
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
// now that all ids are known, revisit any Connections whose target were perviously unknown
|
2019-11-11 17:18:04 +00:00
|
|
|
for (auto const &outstandingConnection: m_outstandingConnections) {
|
|
|
|
auto targetScope = m_qmlid2scope[outstandingConnection.targetName];
|
2020-05-06 11:21:21 +00:00
|
|
|
if (outstandingConnection.scope && targetScope != nullptr)
|
2019-11-11 17:18:04 +00:00
|
|
|
outstandingConnection.scope->addMethods(targetScope->methods());
|
2020-03-31 13:46:20 +00:00
|
|
|
QScopedValueRollback<ScopeTree::Ptr> rollback(m_currentScope, outstandingConnection.scope);
|
2019-06-14 12:21:25 +00:00
|
|
|
outstandingConnection.uiod->initializer->accept(this);
|
|
|
|
}
|
2020-03-30 15:42:48 +00:00
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
if (!m_warnUnqualified)
|
|
|
|
return true;
|
|
|
|
|
2020-04-23 08:10:31 +00:00
|
|
|
CheckIdentifiers check(&m_colorOut, m_code, m_exportedName2Scope, m_filePath);
|
2020-03-31 13:46:20 +00:00
|
|
|
return check(m_qmlid2scope, m_rootScope, m_rootId);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
while (vdl) {
|
2020-03-30 15:42:48 +00:00
|
|
|
m_currentScope->insertJSIdentifier(
|
|
|
|
vdl->declaration->bindingIdentifier.toString(),
|
|
|
|
(vdl->declaration->scope == QQmlJS::AST::VariableScope::Var)
|
|
|
|
? ScopeType::JSFunctionScope
|
|
|
|
: ScopeType::JSLexicalScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
vdl = vdl->next;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
using namespace QQmlJS::AST;
|
2019-11-11 17:18:04 +00:00
|
|
|
auto name = fexpr->name.toString();
|
|
|
|
if (!name.isEmpty()) {
|
|
|
|
if (m_currentScope->scopeType() == ScopeType::QMLScope)
|
|
|
|
m_currentScope->addMethod(MetaMethod(name, QLatin1String("void")));
|
|
|
|
else
|
2020-03-30 15:42:48 +00:00
|
|
|
m_currentScope->insertJSIdentifier(name, ScopeType::JSLexicalScope);
|
2019-11-11 17:18:04 +00:00
|
|
|
enterEnvironment(ScopeType::JSFunctionScope, name);
|
|
|
|
} else {
|
|
|
|
enterEnvironment(ScopeType::JSFunctionScope, QLatin1String("<anon>"));
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
visitFunctionExpressionHelper(fexpr);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::FunctionExpression *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
visitFunctionExpressionHelper(fdecl);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
leaveEnvironment();
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2020-03-30 15:42:48 +00:00
|
|
|
for (auto const &boundName : fpl->boundNames())
|
|
|
|
m_currentScope->insertJSIdentifier(boundName.id, ScopeType::JSLexicalScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiImport *import)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
// construct path
|
|
|
|
QString prefix = QLatin1String("");
|
|
|
|
if (import->asToken.isValid()) {
|
2019-11-08 17:43:31 +00:00
|
|
|
prefix += import->importId;
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
auto dirname = import->fileName.toString();
|
2019-08-16 15:03:30 +00:00
|
|
|
if (!dirname.isEmpty())
|
2019-11-08 17:43:31 +00:00
|
|
|
importFileOrDirectory(dirname, prefix);
|
2019-08-16 15:03:30 +00:00
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
QString path {};
|
|
|
|
if (!import->importId.isEmpty()) {
|
2019-11-11 17:18:04 +00:00
|
|
|
// TODO: do not put imported ids into the same space as qml IDs
|
2019-11-08 13:48:32 +00:00
|
|
|
const QString importId = import->importId.toString();
|
2020-03-31 13:46:20 +00:00
|
|
|
m_qmlid2scope.insert(importId, m_exportedName2Scope.value(importId));
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
if (import->version) {
|
|
|
|
auto uri = import->importUri;
|
|
|
|
while (uri) {
|
|
|
|
path.append(uri->name);
|
|
|
|
path.append("/");
|
|
|
|
uri = uri->next;
|
|
|
|
}
|
|
|
|
path.chop(1);
|
|
|
|
|
2020-01-22 12:12:56 +00:00
|
|
|
importHelper(path, prefix, import->version->version);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-11-11 17:18:04 +00:00
|
|
|
MetaEnum qmlEnum(uied->name.toString());
|
|
|
|
for (const auto *member = uied->members; member; member = member->next)
|
|
|
|
qmlEnum.addKey(member->member.toString());
|
|
|
|
m_currentScope->addEnum(qmlEnum);
|
2019-06-14 12:21:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
|
|
|
// property QtObject __styleData: QtObject {...}
|
2019-11-11 17:18:04 +00:00
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
QString name {};
|
|
|
|
auto id = uiob->qualifiedTypeNameId;
|
|
|
|
QStringRef prefix = uiob->qualifiedTypeNameId->name;
|
|
|
|
while (id) {
|
|
|
|
name += id->name.toString() + QLatin1Char('.');
|
|
|
|
id = id->next;
|
|
|
|
}
|
|
|
|
name.chop(1);
|
2019-11-11 17:18:04 +00:00
|
|
|
|
2019-11-11 16:35:09 +00:00
|
|
|
MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true,
|
|
|
|
name == QLatin1String("alias"), 0);
|
2020-03-31 13:46:20 +00:00
|
|
|
prop.setType(m_exportedName2Scope.value(uiob->qualifiedTypeNameId->name.toString()));
|
2019-11-11 17:18:04 +00:00
|
|
|
m_currentScope->addProperty(prop);
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
enterEnvironment(ScopeType::QMLScope, name);
|
|
|
|
importExportedNames(prefix, name);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-11-08 13:48:32 +00:00
|
|
|
const auto childScope = m_currentScope;
|
2019-06-14 12:21:25 +00:00
|
|
|
leaveEnvironment();
|
2019-11-08 13:48:32 +00:00
|
|
|
MetaProperty property(uiob->qualifiedId->name.toString(),
|
|
|
|
uiob->qualifiedTypeNameId->name.toString(),
|
2019-11-11 16:35:09 +00:00
|
|
|
false, true, true,
|
|
|
|
uiob->qualifiedTypeNameId->name == QLatin1String("alias"),
|
|
|
|
0);
|
2019-11-08 13:48:32 +00:00
|
|
|
property.setType(childScope);
|
|
|
|
m_currentScope->addProperty(property);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-11-11 17:18:04 +00:00
|
|
|
using namespace QQmlJS::AST;
|
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
QString name {};
|
|
|
|
auto id = uiod->qualifiedTypeNameId;
|
|
|
|
QStringRef prefix = uiod->qualifiedTypeNameId->name;
|
|
|
|
while (id) {
|
|
|
|
name += id->name.toString() + QLatin1Char('.');
|
|
|
|
id = id->next;
|
|
|
|
}
|
|
|
|
name.chop(1);
|
|
|
|
enterEnvironment(ScopeType::QMLScope, name);
|
|
|
|
if (name.isLower())
|
|
|
|
return false; // Ignore grouped properties for now
|
2019-11-11 17:18:04 +00:00
|
|
|
|
2019-06-14 12:21:25 +00:00
|
|
|
importExportedNames(prefix, name);
|
|
|
|
if (name.endsWith("Connections")) {
|
|
|
|
QString target;
|
|
|
|
auto member = uiod->initializer->members;
|
|
|
|
while (member) {
|
|
|
|
if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
|
|
|
|
auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
|
|
|
|
if (asBinding->qualifiedId->name == QLatin1String("target")) {
|
|
|
|
if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
|
|
|
|
auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
|
|
|
|
if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
|
|
|
|
target = idexpr->name.toString();
|
|
|
|
} else {
|
|
|
|
// more complex expressions are not supported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
member = member->next;
|
|
|
|
}
|
2020-03-31 13:46:20 +00:00
|
|
|
ScopeTree::ConstPtr targetScope;
|
2019-06-14 12:21:25 +00:00
|
|
|
if (target.isEmpty()) {
|
|
|
|
// no target set, connection comes from parentF
|
2020-03-31 13:46:20 +00:00
|
|
|
ScopeTree::Ptr scope = m_currentScope;
|
2019-06-14 12:21:25 +00:00
|
|
|
do {
|
|
|
|
scope = scope->parentScope(); // TODO: rename method
|
|
|
|
} while (scope->scopeType() != ScopeType::QMLScope);
|
2020-03-31 13:46:20 +00:00
|
|
|
targetScope = m_exportedName2Scope.value(scope->name());
|
2019-06-14 12:21:25 +00:00
|
|
|
} else {
|
|
|
|
// there was a target, check if we already can find it
|
2019-11-11 17:18:04 +00:00
|
|
|
auto scopeIt = m_qmlid2scope.find(target);
|
|
|
|
if (scopeIt != m_qmlid2scope.end()) {
|
|
|
|
targetScope = *scopeIt;
|
2019-06-14 12:21:25 +00:00
|
|
|
} else {
|
|
|
|
m_outstandingConnections.push_back({target, m_currentScope, uiod});
|
|
|
|
return false; // visit children later once target is known
|
|
|
|
}
|
|
|
|
}
|
2019-11-11 17:18:04 +00:00
|
|
|
if (targetScope)
|
|
|
|
m_currentScope->addMethods(targetScope->methods());
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element)
|
2019-09-17 15:13:18 +00:00
|
|
|
{
|
|
|
|
if (element->isVariableDeclaration()) {
|
|
|
|
QQmlJS::AST::BoundNames names;
|
|
|
|
element->boundNames(&names);
|
2020-03-30 15:42:48 +00:00
|
|
|
for (const auto &name : names) {
|
|
|
|
m_currentScope->insertJSIdentifier(
|
|
|
|
name.id, (element->scope == QQmlJS::AST::VariableScope::Var)
|
|
|
|
? ScopeType::JSFunctionScope
|
|
|
|
: ScopeType::JSLexicalScope);
|
|
|
|
}
|
2019-09-17 15:13:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
|
2019-06-14 12:21:25 +00:00
|
|
|
{
|
2019-11-14 14:59:56 +00:00
|
|
|
auto childScope = m_currentScope;
|
2019-06-14 12:21:25 +00:00
|
|
|
leaveEnvironment();
|
2019-11-14 14:59:56 +00:00
|
|
|
childScope->updateParentProperty(m_currentScope);
|
2019-06-14 12:21:25 +00:00
|
|
|
}
|
2019-11-08 13:48:32 +00:00
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::FieldMemberExpression *)
|
2019-11-08 13:48:32 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
|
2019-11-08 13:48:32 +00:00
|
|
|
{
|
2019-11-15 15:59:46 +00:00
|
|
|
using namespace QQmlJS::AST;
|
|
|
|
ExpressionNode *base = fieldMember->base;
|
|
|
|
while (auto *nested = cast<NestedExpression *>(base))
|
|
|
|
base = nested->expression;
|
|
|
|
|
|
|
|
if (m_fieldMemberBase == base) {
|
|
|
|
QString type;
|
|
|
|
if (auto *binary = cast<BinaryExpression *>(base)) {
|
|
|
|
if (binary->op == QSOperator::As) {
|
2020-05-18 14:47:34 +00:00
|
|
|
if (auto *right = cast<TypeExpression *>(binary->right))
|
|
|
|
type = right->m_type->toString();
|
2019-11-15 15:59:46 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-08 13:48:32 +00:00
|
|
|
m_currentScope->accessMember(fieldMember->name.toString(),
|
2019-11-15 15:59:46 +00:00
|
|
|
type,
|
2019-11-08 13:48:32 +00:00
|
|
|
fieldMember->identifierToken);
|
|
|
|
m_fieldMemberBase = fieldMember;
|
|
|
|
} else {
|
|
|
|
m_fieldMemberBase = nullptr;
|
|
|
|
}
|
|
|
|
}
|
2019-11-15 15:59:46 +00:00
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
bool FindWarningVisitor::visit(QQmlJS::AST::BinaryExpression *)
|
2019-11-15 15:59:46 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-24 13:10:53 +00:00
|
|
|
void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
|
2019-11-15 15:59:46 +00:00
|
|
|
{
|
|
|
|
if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left)
|
|
|
|
m_fieldMemberBase = binExp;
|
|
|
|
else
|
|
|
|
m_fieldMemberBase = nullptr;
|
|
|
|
}
|