2020-03-30 15:42:48 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2020 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$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include "checkidentifiers.h"
|
2021-03-25 14:34:42 +00:00
|
|
|
|
|
|
|
#include <QtQmlCompiler/private/qcoloroutput_p.h>
|
2020-03-30 15:42:48 +00:00
|
|
|
|
|
|
|
#include <QtCore/qqueue.h>
|
2021-04-29 12:00:43 +00:00
|
|
|
#include <QtCore/private/qduplicatetracker_p.h>
|
2020-03-30 15:42:48 +00:00
|
|
|
#include <QtCore/qsharedpointer.h>
|
2020-06-18 18:52:58 +00:00
|
|
|
#include <stack>
|
2020-03-30 15:42:48 +00:00
|
|
|
|
|
|
|
static const QStringList unknownBuiltins = {
|
|
|
|
QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet
|
|
|
|
QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values.
|
2021-04-07 11:01:53 +00:00
|
|
|
QStringLiteral("QVariant") // Same for generic variants
|
2020-03-30 15:42:48 +00:00
|
|
|
};
|
|
|
|
|
2021-02-01 14:03:19 +00:00
|
|
|
template<typename Visitor>
|
|
|
|
static bool walkRelatedScopes(QQmlJSScope::ConstPtr rootType, const Visitor &visit)
|
2020-06-18 18:52:58 +00:00
|
|
|
{
|
2020-10-14 10:55:08 +00:00
|
|
|
if (rootType.isNull())
|
2020-06-18 18:52:58 +00:00
|
|
|
return false;
|
2020-10-01 12:23:27 +00:00
|
|
|
std::stack<QQmlJSScope::ConstPtr> stack;
|
2021-04-29 12:00:43 +00:00
|
|
|
QDuplicateTracker<QQmlJSScope::ConstPtr> seenTypes;
|
2020-06-18 18:52:58 +00:00
|
|
|
stack.push(rootType);
|
2021-01-20 11:17:42 +00:00
|
|
|
|
2020-06-18 18:52:58 +00:00
|
|
|
while (!stack.empty()) {
|
|
|
|
const auto type = stack.top();
|
|
|
|
stack.pop();
|
|
|
|
|
2021-04-29 12:00:43 +00:00
|
|
|
// If we've seen this type before (can be caused by self attaching types), ignore it
|
|
|
|
if (seenTypes.hasSeen(type))
|
|
|
|
continue;
|
|
|
|
|
2020-06-18 18:52:58 +00:00
|
|
|
if (visit(type))
|
|
|
|
return true;
|
|
|
|
|
2020-09-29 15:33:17 +00:00
|
|
|
if (auto attachedType = type->attachedType())
|
2020-06-18 18:52:58 +00:00
|
|
|
stack.push(attachedType);
|
2021-02-01 14:03:19 +00:00
|
|
|
|
|
|
|
if (auto baseType = type->baseType())
|
|
|
|
stack.push(baseType);
|
|
|
|
|
|
|
|
// Push extension type last. It overrides the base type.
|
|
|
|
if (auto extensionType = type->extensionType())
|
|
|
|
stack.push(extensionType);
|
2020-06-18 18:52:58 +00:00
|
|
|
}
|
2021-02-01 14:03:19 +00:00
|
|
|
|
2020-06-18 18:52:58 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
void CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members,
|
2020-10-01 12:23:27 +00:00
|
|
|
const QQmlJSScope::ConstPtr &outerScope,
|
2020-10-01 12:28:08 +00:00
|
|
|
const QQmlJSMetaProperty *prop) const
|
2020-03-30 15:42:48 +00:00
|
|
|
{
|
2020-06-17 20:39:28 +00:00
|
|
|
|
2020-03-30 15:42:48 +00:00
|
|
|
QStringList expectedNext;
|
|
|
|
QString detectedRestrictiveName;
|
|
|
|
QString detectedRestrictiveKind;
|
|
|
|
|
2020-06-17 20:39:28 +00:00
|
|
|
if (prop != nullptr && prop->isList()) {
|
|
|
|
detectedRestrictiveKind = QLatin1String("list");
|
|
|
|
expectedNext.append(QLatin1String("length"));
|
2021-04-08 10:52:34 +00:00
|
|
|
} else if (outerScope->internalName() == QLatin1String("QString")) {
|
|
|
|
detectedRestrictiveKind = QLatin1String("QString");
|
|
|
|
expectedNext.append(QLatin1String("length"));
|
2020-06-17 20:39:28 +00:00
|
|
|
}
|
|
|
|
|
2020-10-01 12:23:27 +00:00
|
|
|
QQmlJSScope::ConstPtr scope = outerScope;
|
2021-03-17 16:51:58 +00:00
|
|
|
for (qsizetype i = 0; i < members.size(); i++) {
|
|
|
|
const FieldMember &access = members.at(i);
|
|
|
|
|
2020-03-31 13:46:20 +00:00
|
|
|
if (scope.isNull()) {
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(
|
|
|
|
QString::fromLatin1("Type \"%1\" of base \"%2\" not found when accessing member \"%3\"")
|
2020-03-30 15:42:48 +00:00
|
|
|
.arg(detectedRestrictiveKind)
|
|
|
|
.arg(detectedRestrictiveName)
|
2021-03-25 14:34:42 +00:00
|
|
|
.arg(access.m_name), Log_Type, access.m_location);
|
|
|
|
return;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!detectedRestrictiveKind.isEmpty()) {
|
|
|
|
if (expectedNext.contains(access.m_name)) {
|
|
|
|
expectedNext.clear();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(
|
|
|
|
QLatin1String("\"%1\" is a %2. You cannot access \"%3\" it from here")
|
|
|
|
.arg(detectedRestrictiveName)
|
|
|
|
.arg(detectedRestrictiveKind)
|
|
|
|
.arg(access.m_name), Log_Type, access.m_location);
|
|
|
|
return;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
const auto property = scope->property(access.m_name);
|
|
|
|
if (!property.propertyName().isEmpty()) {
|
2021-04-26 09:10:46 +00:00
|
|
|
const auto binding = scope->propertyBinding(access.m_name);
|
|
|
|
const QString typeName = access.m_parentType.isEmpty()
|
2021-04-27 11:54:32 +00:00
|
|
|
? (binding.hasValue() ? binding.valueTypeName() : property.typeName())
|
2021-04-26 09:10:46 +00:00
|
|
|
: access.m_parentType;
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
if (property.isList()) {
|
2020-03-30 15:42:48 +00:00
|
|
|
detectedRestrictiveKind = QLatin1String("list");
|
|
|
|
detectedRestrictiveName = access.m_name;
|
|
|
|
expectedNext.append(QLatin1String("length"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-07 08:13:03 +00:00
|
|
|
if (typeName == QLatin1String("QString")) {
|
2020-03-30 15:42:48 +00:00
|
|
|
detectedRestrictiveKind = typeName;
|
|
|
|
detectedRestrictiveName = access.m_name;
|
|
|
|
expectedNext.append(QLatin1String("length"));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-29 15:33:17 +00:00
|
|
|
if (access.m_parentType.isEmpty()) {
|
2021-04-27 11:54:32 +00:00
|
|
|
if (binding.hasValue())
|
|
|
|
scope = binding.value();
|
2021-04-26 09:10:46 +00:00
|
|
|
else
|
|
|
|
scope = property.type();
|
|
|
|
|
2020-09-29 15:33:17 +00:00
|
|
|
if (scope.isNull()) {
|
|
|
|
// Properties should always have a type. Otherwise something
|
|
|
|
// was missing from the import already.
|
|
|
|
detectedRestrictiveKind = typeName;
|
|
|
|
detectedRestrictiveName = access.m_name;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
2020-09-29 15:33:17 +00:00
|
|
|
continue;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unknownBuiltins.contains(typeName))
|
2021-03-25 14:34:42 +00:00
|
|
|
return;
|
2020-03-30 15:42:48 +00:00
|
|
|
|
2020-09-29 15:56:46 +00:00
|
|
|
const auto it = m_types.find(typeName);
|
|
|
|
if (it == m_types.end()) {
|
2020-09-29 15:33:17 +00:00
|
|
|
detectedRestrictiveKind = typeName;
|
|
|
|
detectedRestrictiveName = access.m_name;
|
2020-10-14 10:55:08 +00:00
|
|
|
scope = QQmlJSScope::ConstPtr();
|
2020-09-29 15:33:17 +00:00
|
|
|
} else {
|
|
|
|
scope = *it;
|
|
|
|
}
|
2020-09-25 10:39:17 +00:00
|
|
|
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
if (scope->hasMethod(access.m_name))
|
2021-03-25 14:34:42 +00:00
|
|
|
return; // Access to property of JS function
|
2020-03-30 15:42:48 +00:00
|
|
|
|
2020-10-01 12:23:27 +00:00
|
|
|
auto checkEnums = [&](const QQmlJSScope::ConstPtr &scope) {
|
2021-01-20 13:20:48 +00:00
|
|
|
if (scope->hasEnumeration(access.m_name)) {
|
|
|
|
detectedRestrictiveKind = QLatin1String("enum");
|
|
|
|
detectedRestrictiveName = access.m_name;
|
|
|
|
expectedNext.append(scope->enumeration(access.m_name).keys());
|
|
|
|
return true;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
2021-01-20 13:20:48 +00:00
|
|
|
|
|
|
|
if (scope->hasEnumerationKey(access.m_name)) {
|
|
|
|
detectedRestrictiveKind = QLatin1String("enum");
|
|
|
|
detectedRestrictiveName = access.m_name;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-23 14:06:58 +00:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
checkEnums(scope);
|
|
|
|
|
2020-03-30 15:42:48 +00:00
|
|
|
if (!detectedRestrictiveName.isEmpty())
|
|
|
|
continue;
|
|
|
|
|
2020-10-01 12:23:27 +00:00
|
|
|
QQmlJSScope::ConstPtr rootType;
|
2020-09-25 10:39:17 +00:00
|
|
|
if (!access.m_parentType.isEmpty())
|
2020-09-29 15:56:46 +00:00
|
|
|
rootType = m_types.value(access.m_parentType);
|
2020-09-25 10:39:17 +00:00
|
|
|
else
|
|
|
|
rootType = scope;
|
|
|
|
|
2020-06-18 18:52:58 +00:00
|
|
|
bool typeFound =
|
2021-02-01 14:03:19 +00:00
|
|
|
walkRelatedScopes(rootType, [&](QQmlJSScope::ConstPtr type) {
|
2020-10-22 09:27:54 +00:00
|
|
|
const auto typeProperties = type->ownProperties();
|
2020-06-18 18:52:58 +00:00
|
|
|
const auto typeIt = typeProperties.find(access.m_name);
|
|
|
|
if (typeIt != typeProperties.end()) {
|
2020-09-29 15:33:17 +00:00
|
|
|
scope = typeIt->type();
|
2020-06-18 18:52:58 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
const auto typeMethods = type->ownMethods();
|
2020-06-18 18:52:58 +00:00
|
|
|
const auto typeMethodIt = typeMethods.find(access.m_name);
|
|
|
|
if (typeMethodIt != typeMethods.end()) {
|
|
|
|
detectedRestrictiveName = access.m_name;
|
|
|
|
detectedRestrictiveKind = QLatin1String("method");
|
|
|
|
return true;
|
|
|
|
}
|
2020-06-23 14:06:58 +00:00
|
|
|
|
|
|
|
return checkEnums(type);
|
2020-06-18 18:52:58 +00:00
|
|
|
});
|
2020-03-30 15:42:48 +00:00
|
|
|
if (typeFound)
|
|
|
|
continue;
|
|
|
|
|
2020-10-02 12:48:22 +00:00
|
|
|
if (access.m_name.front().isUpper() && scope->scopeType() == QQmlJSScope::QMLScope) {
|
2020-03-30 15:42:48 +00:00
|
|
|
// may be an attached type
|
2021-03-17 16:51:58 +00:00
|
|
|
|
|
|
|
auto it = m_types.find(access.m_name);
|
|
|
|
|
|
|
|
// Something was found but it wasn't the attached type we were looking for, it could be a prefix
|
|
|
|
if (it != m_types.end() && !(*it) && i+1 < members.length()) {
|
|
|
|
// See whether this is due to us getting the prefixed property in two accesses (i.e. "T" and "Item")
|
|
|
|
// by checking again with a fixed name.
|
|
|
|
it = m_types.find(access.m_name + QLatin1Char('.') + members[++i].m_name);
|
|
|
|
|
|
|
|
if (it == m_types.end() || !(*it) || (*it)->attachedTypeName().isEmpty())
|
|
|
|
--i;
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:06:33 +00:00
|
|
|
if (it != m_types.end() && *it && !(*it)->attachedTypeName().isEmpty()) {
|
2020-09-29 15:33:17 +00:00
|
|
|
if (const auto attached = (*it)->attachedType()) {
|
|
|
|
scope = attached;
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(QLatin1String(
|
|
|
|
"Property \"%1\" not found on type \"%2\"")
|
|
|
|
.arg(access.m_name)
|
|
|
|
.arg(scope->internalName().isEmpty()
|
|
|
|
? scope->baseTypeName() : scope->internalName()), Log_Type, access.m_location);
|
|
|
|
return;
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
void CheckIdentifiers::operator()(
|
2020-10-01 12:23:27 +00:00
|
|
|
const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs,
|
2020-09-30 12:44:24 +00:00
|
|
|
const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers,
|
2020-09-30 13:31:43 +00:00
|
|
|
const MemberAccessChains &memberAccessChains,
|
2020-10-01 12:23:27 +00:00
|
|
|
const QQmlJSScope::ConstPtr &root, const QString &rootId) const
|
2020-03-30 15:42:48 +00:00
|
|
|
{
|
|
|
|
// revisit all scopes
|
2020-10-01 12:23:27 +00:00
|
|
|
QQueue<QQmlJSScope::ConstPtr> workQueue;
|
2020-03-30 15:42:48 +00:00
|
|
|
workQueue.enqueue(root);
|
|
|
|
while (!workQueue.empty()) {
|
2020-10-01 12:23:27 +00:00
|
|
|
const QQmlJSScope::ConstPtr currentScope = workQueue.dequeue();
|
2020-03-30 15:42:48 +00:00
|
|
|
|
2020-09-30 13:31:43 +00:00
|
|
|
const auto scopeMemberAccessChains = memberAccessChains[currentScope];
|
|
|
|
for (auto memberAccessChain : scopeMemberAccessChains) {
|
2020-03-30 15:42:48 +00:00
|
|
|
if (memberAccessChain.isEmpty())
|
|
|
|
continue;
|
|
|
|
|
2021-01-26 10:14:13 +00:00
|
|
|
auto memberAccessBase = memberAccessChain.takeFirst();
|
2020-09-30 12:44:24 +00:00
|
|
|
const auto jsId = currentScope->findJSIdentifier(memberAccessBase.m_name);
|
2021-02-11 12:17:29 +00:00
|
|
|
if (jsId.has_value() && jsId->kind != QQmlJSScope::JavaScriptIdentifier::Injected) {
|
|
|
|
if (memberAccessBase.m_location.end() < jsId->location.begin()) {
|
2021-03-25 14:34:42 +00:00
|
|
|
// TODO: Is there a more fitting category?
|
|
|
|
m_logger->log(
|
2021-04-28 10:22:06 +00:00
|
|
|
QStringLiteral("Variable \"%1\" is used here before its declaration. "
|
|
|
|
"The declaration is at %4:%5.")
|
|
|
|
.arg(memberAccessBase.m_name)
|
|
|
|
.arg(jsId->location.startLine)
|
|
|
|
.arg(jsId->location.startColumn),
|
|
|
|
Log_Type, memberAccessBase.m_location);
|
2021-02-11 12:17:29 +00:00
|
|
|
}
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
2021-02-11 12:17:29 +00:00
|
|
|
}
|
2020-03-30 15:42:48 +00:00
|
|
|
|
|
|
|
auto it = qmlIDs.find(memberAccessBase.m_name);
|
|
|
|
if (it != qmlIDs.end()) {
|
2020-10-14 10:55:08 +00:00
|
|
|
if (!it->isNull()) {
|
2021-03-25 14:34:42 +00:00
|
|
|
checkMemberAccess(memberAccessChain, *it);
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
} else if (!memberAccessChain.isEmpty()) {
|
|
|
|
// It could be a qualified type name
|
|
|
|
const QString scopedName = memberAccessChain.first().m_name;
|
|
|
|
if (scopedName.front().isUpper()) {
|
|
|
|
const QString qualified = memberAccessBase.m_name + QLatin1Char('.')
|
|
|
|
+ scopedName;
|
2020-09-29 15:56:46 +00:00
|
|
|
const auto typeIt = m_types.find(qualified);
|
|
|
|
if (typeIt != m_types.end()) {
|
2020-03-30 15:42:48 +00:00
|
|
|
memberAccessChain.takeFirst();
|
2021-03-25 14:34:42 +00:00
|
|
|
checkMemberAccess(memberAccessChain, *typeIt);
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-01 12:23:27 +00:00
|
|
|
auto qmlScope = QQmlJSScope::findCurrentQMLScope(currentScope);
|
2020-10-22 09:27:54 +00:00
|
|
|
if (qmlScope->hasMethod(memberAccessBase.m_name)) {
|
|
|
|
// a property of a JavaScript function, or a method
|
2021-04-14 11:18:02 +00:00
|
|
|
auto methods = qmlScope->methods(memberAccessBase.m_name);
|
|
|
|
const QQmlJSMetaMethod &method = methods.constFirst();
|
|
|
|
const auto &annotations = method.annotations();
|
|
|
|
auto deprecationAnn = std::find_if(annotations.constBegin(), annotations.constEnd(), [](const QQmlJSAnnotation& annotation) {
|
|
|
|
return annotation.isDeprecation();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Once we encountered one possible method that is not deprecated,
|
|
|
|
// we can assume that the one beyond that is not what was being referenced
|
|
|
|
if (deprecationAnn == annotations.constEnd())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
|
|
|
|
|
|
|
|
QString message = QStringLiteral("Method \"%1(%2)\" is deprecated")
|
|
|
|
.arg(memberAccessBase.m_name, method.parameterNames().join(QStringLiteral(", ")));
|
|
|
|
|
|
|
|
if (!deprecation.reason.isEmpty())
|
|
|
|
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
|
|
|
|
|
|
|
|
m_logger->log(message, Log_Deprecation, memberAccessBase.m_location);
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
const auto property = qmlScope->property(memberAccessBase.m_name);
|
|
|
|
if (!property.propertyName().isEmpty()) {
|
2021-02-24 15:42:51 +00:00
|
|
|
for (const QQmlJSAnnotation &annotation : property.annotations()) {
|
|
|
|
if (annotation.isDeprecation()) {
|
|
|
|
QQQmlJSDeprecation deprecation = annotation.deprecation();
|
|
|
|
|
|
|
|
QString message = QStringLiteral("Property \"%1\" is deprecated")
|
|
|
|
.arg(memberAccessBase.m_name);
|
|
|
|
|
|
|
|
if (!deprecation.reason.isEmpty())
|
|
|
|
message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(message, Log_Deprecation, memberAccessBase.m_location);
|
2021-02-24 15:42:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 09:27:54 +00:00
|
|
|
if (memberAccessChain.isEmpty() || unknownBuiltins.contains(property.typeName()))
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
|
2021-04-26 09:10:46 +00:00
|
|
|
const auto binding = qmlScope->propertyBinding(memberAccessBase.m_name);
|
2021-04-27 11:54:32 +00:00
|
|
|
if (binding.hasValue()) {
|
|
|
|
checkMemberAccess(memberAccessChain, binding.value(), &property);
|
2021-04-26 09:10:46 +00:00
|
|
|
} else if (!property.type()) {
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(QString::fromLatin1(
|
|
|
|
"Type of property \"%2\" not found")
|
|
|
|
.arg(memberAccessBase.m_name), Log_Type, memberAccessBase.m_location);
|
|
|
|
} else {
|
|
|
|
checkMemberAccess(memberAccessChain, property.type(), &property);
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-01-26 10:14:13 +00:00
|
|
|
const QString baseName = memberAccessBase.m_name;
|
|
|
|
auto typeIt = m_types.find(memberAccessBase.m_name);
|
|
|
|
bool baseIsPrefixed = false;
|
|
|
|
while (typeIt != m_types.end() && typeIt->isNull()) {
|
|
|
|
// This is a namespaced import. Check with the full name.
|
|
|
|
if (!memberAccessChain.isEmpty()) {
|
|
|
|
auto location = memberAccessBase.m_location;
|
|
|
|
memberAccessBase = memberAccessChain.takeFirst();
|
|
|
|
memberAccessBase.m_name.prepend(baseName + u'.');
|
|
|
|
location.length = memberAccessBase.m_location.offset - location.offset
|
|
|
|
+ memberAccessBase.m_location.length;
|
|
|
|
memberAccessBase.m_location = location;
|
|
|
|
typeIt = m_types.find(memberAccessBase.m_name);
|
|
|
|
baseIsPrefixed = true;
|
2020-12-04 14:09:25 +00:00
|
|
|
}
|
2021-01-26 10:14:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeIt != m_types.end() && !typeIt->isNull()) {
|
2021-03-25 14:34:42 +00:00
|
|
|
checkMemberAccess(memberAccessChain, *typeIt);
|
2020-03-30 15:42:48 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto location = memberAccessBase.m_location;
|
2021-01-26 10:14:13 +00:00
|
|
|
|
|
|
|
if (baseIsPrefixed) {
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(
|
|
|
|
QLatin1String("Type not found in namespace"),
|
|
|
|
Log_Type, location);
|
2021-01-26 10:14:13 +00:00
|
|
|
} else {
|
2021-03-25 14:34:42 +00:00
|
|
|
m_logger->log(
|
|
|
|
QLatin1String("Unqualified access"),
|
|
|
|
Log_UnqualifiedAccess, location);
|
2021-01-26 10:14:13 +00:00
|
|
|
}
|
2020-03-30 15:42:48 +00:00
|
|
|
|
2020-10-09 10:02:22 +00:00
|
|
|
// root(JS) --> (first element)
|
|
|
|
const auto firstElement = root->childScopes()[0];
|
2021-03-25 14:34:42 +00:00
|
|
|
|
|
|
|
QColorOutput &colorOut = m_logger->colorOutput();
|
|
|
|
|
2021-03-30 10:58:26 +00:00
|
|
|
if (!m_logger->isCategoryDisabled(Log_UnqualifiedAccess) &&
|
2021-03-25 14:34:42 +00:00
|
|
|
(firstElement->hasProperty(memberAccessBase.m_name)
|
2020-10-22 09:27:54 +00:00
|
|
|
|| firstElement->hasMethod(memberAccessBase.m_name)
|
2021-03-25 14:34:42 +00:00
|
|
|
|| firstElement->hasEnumeration(memberAccessBase.m_name))) {
|
|
|
|
|
|
|
|
colorOut.writePrefixedMessage(
|
2020-10-06 10:52:42 +00:00
|
|
|
memberAccessBase.m_name
|
|
|
|
+ QLatin1String(" is a member of the root element\n")
|
|
|
|
+ QLatin1String(" You can qualify the access with its id "
|
|
|
|
"to avoid this warning:\n"),
|
2021-03-25 14:34:42 +00:00
|
|
|
QtInfoMsg, QStringLiteral("Note"));
|
|
|
|
IssueLocationWithContext issueLocationWithContext {m_code, location};
|
|
|
|
colorOut.write(issueLocationWithContext.beforeText().toString());
|
|
|
|
colorOut.write(rootId + QLatin1Char('.'), QtDebugMsg);
|
|
|
|
colorOut.write(issueLocationWithContext.issueText().toString());
|
|
|
|
colorOut.write(issueLocationWithContext.afterText() + QLatin1String("\n\n"));
|
|
|
|
|
2020-03-30 15:42:48 +00:00
|
|
|
if (rootId == QLatin1String("<id>")) {
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.writePrefixedMessage(
|
2020-10-06 10:52:42 +00:00
|
|
|
QLatin1String("You first have to give the root element an id\n"),
|
2021-03-25 14:34:42 +00:00
|
|
|
QtInfoMsg, QStringLiteral("Note"));
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
2021-04-30 10:40:43 +00:00
|
|
|
|
|
|
|
colorOut.write(QLatin1String("\n\n\n"));
|
2020-10-02 12:48:22 +00:00
|
|
|
} else if (jsId.has_value()
|
|
|
|
&& jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
|
|
|
|
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.writePrefixedMessage(
|
2020-03-30 15:42:48 +00:00
|
|
|
memberAccessBase.m_name + QString::fromLatin1(
|
|
|
|
" is accessible in this scope because "
|
2020-04-23 08:10:31 +00:00
|
|
|
"you are handling a signal at %1:%2:%3\n")
|
|
|
|
.arg(m_fileName)
|
2020-09-30 12:44:24 +00:00
|
|
|
.arg(id.location.startLine).arg(id.location.startColumn),
|
2021-03-25 14:34:42 +00:00
|
|
|
QtInfoMsg, QStringLiteral("Note"));
|
|
|
|
colorOut.write(QLatin1String("Consider using a function instead\n"));
|
2020-09-30 12:44:24 +00:00
|
|
|
IssueLocationWithContext context {m_code, id.location};
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.write(context.beforeText() + QLatin1Char(' '));
|
2020-09-30 12:44:24 +00:00
|
|
|
|
|
|
|
const auto handler = signalHandlers[id.location];
|
|
|
|
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.write(QLatin1String(handler.isMultiline ? "function(" : "("), QtInfoMsg);
|
2020-09-30 12:44:24 +00:00
|
|
|
const auto parameters = handler.signal.parameterNames();
|
2020-03-30 15:42:48 +00:00
|
|
|
for (int numParams = parameters.size(); numParams > 0; --numParams) {
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.write(parameters.at(parameters.size() - numParams), QtInfoMsg);
|
2020-03-30 15:42:48 +00:00
|
|
|
if (numParams > 1)
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.write(QLatin1String(", "), QtInfoMsg);
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
2021-03-25 14:34:42 +00:00
|
|
|
colorOut.write(QLatin1String(handler.isMultiline ? ")" : ") => "), QtInfoMsg);
|
2021-04-30 10:40:43 +00:00
|
|
|
colorOut.write(QLatin1String(" {...\n\n\n"));
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto childScopes = currentScope->childScopes();
|
|
|
|
for (auto const &childScope : childScopes)
|
2020-03-31 13:46:20 +00:00
|
|
|
workQueue.enqueue(childScope);
|
2020-03-30 15:42:48 +00:00
|
|
|
}
|
|
|
|
}
|