aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmllint/scopetree.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qmllint/scopetree.cpp')
-rw-r--r--tools/qmllint/scopetree.cpp526
1 files changed, 0 insertions, 526 deletions
diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp
deleted file mode 100644
index 8c5358c7a5..0000000000
--- a/tools/qmllint/scopetree.cpp
+++ /dev/null
@@ -1,526 +0,0 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
-
-#include "scopetree.h"
-#include "qcoloroutput.h"
-
-#include <QtCore/qqueue.h>
-
-#include <algorithm>
-
-ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope)
- : m_parentScope(parentScope), m_name(std::move(name)), m_scopeType(type) {}
-
-ScopeTree::Ptr ScopeTree::createNewChildScope(ScopeType type, const QString &name)
-{
- Q_ASSERT(type != ScopeType::QMLScope
- || !m_parentScope
- || m_parentScope->m_scopeType == ScopeType::QMLScope
- || m_parentScope->m_name == "global");
- auto childScope = ScopeTree::Ptr(new ScopeTree{type, name, this});
- m_childScopes.push_back(childScope);
- return childScope;
-}
-
-void ScopeTree::insertJSIdentifier(const QString &id, QQmlJS::AST::VariableScope scope)
-{
- Q_ASSERT(m_scopeType != ScopeType::QMLScope);
- if (scope == QQmlJS::AST::VariableScope::Var) {
- auto targetScope = this;
- while (targetScope->scopeType() != ScopeType::JSFunctionScope) {
- targetScope = targetScope->m_parentScope;
- }
- targetScope->m_jsIdentifiers.insert(id);
- } else {
- m_jsIdentifiers.insert(id);
- }
-}
-
-void ScopeTree::insertSignalIdentifier(const QString &id, const MetaMethod &method,
- const QQmlJS::SourceLocation &loc,
- bool hasMultilineHandlerBody)
-{
- Q_ASSERT(m_scopeType == ScopeType::QMLScope);
- m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody});
-}
-
-void ScopeTree::insertPropertyIdentifier(const MetaProperty &property)
-{
- addProperty(property);
- MetaMethod method(property.propertyName() + QLatin1String("Changed"), "void");
- addMethod(method);
-}
-
-void ScopeTree::addUnmatchedSignalHandler(const QString &handler,
- const QQmlJS::SourceLocation &location)
-{
- m_unmatchedSignalHandlers.append(qMakePair(handler, location));
-}
-
-bool ScopeTree::isIdInCurrentScope(const QString &id) const
-{
- return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id);
-}
-
-void ScopeTree::addIdToAccessed(const QString &id, const QQmlJS::SourceLocation &location) {
- m_currentFieldMember = new FieldMemberList {id, QString(), location, {}};
- m_accessedIdentifiers.push_back(std::unique_ptr<FieldMemberList>(m_currentFieldMember));
-}
-
-void ScopeTree::accessMember(const QString &name, const QString &parentType,
- const QQmlJS::SourceLocation &location)
-{
- Q_ASSERT(m_currentFieldMember);
- auto *fieldMember = new FieldMemberList {name, parentType, location, {}};
- m_currentFieldMember->m_child.reset(fieldMember);
- m_currentFieldMember = fieldMember;
-}
-
-void ScopeTree::resetMemberScope()
-{
- m_currentFieldMember = nullptr;
-}
-
-bool ScopeTree::isVisualRootScope() const
-{
- return m_parentScope && m_parentScope->m_parentScope
- && m_parentScope->m_parentScope->m_parentScope == nullptr;
-}
-
-class IssueLocationWithContext
-{
-public:
- IssueLocationWithContext(const QString &code, const QQmlJS::SourceLocation &location) {
- int before = std::max(0,code.lastIndexOf('\n', location.offset));
- m_beforeText = code.midRef(before + 1, int(location.offset - (before + 1)));
- m_issueText = code.midRef(location.offset, location.length);
- int after = code.indexOf('\n', int(location.offset + location.length));
- m_afterText = code.midRef(int(location.offset + location.length),
- int(after - (location.offset+location.length)));
- }
-
- QStringRef beforeText() const { return m_beforeText; }
- QStringRef issueText() const { return m_issueText; }
- QStringRef afterText() const { return m_afterText; }
-
-private:
- QStringRef m_beforeText;
- QStringRef m_issueText;
- QStringRef m_afterText;
-};
-
-static const QStringList unknownBuiltins = {
- // TODO: "string" should be added to builtins.qmltypes, and the special handling below removed
- QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet
- QStringLiteral("QRectF"), // TODO: should be added to builtins.qmltypes
- QStringLiteral("QFont"), // TODO: should be added to builtins.qmltypes
- QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values.
- QStringLiteral("variant"), // Same for generic variants
-};
-
-bool ScopeTree::checkMemberAccess(
- const QString &code,
- FieldMemberList *members,
- const ScopeTree *scope,
- const QHash<QString, ScopeTree::ConstPtr> &types,
- ColorOutput& colorOut) const
-{
- if (!members->m_child)
- return true;
-
- Q_ASSERT(scope != nullptr);
-
- const QString scopeName = scope->name().isEmpty() ? scope->className() : scope->name();
- const auto &access = members->m_child;
-
- const auto scopeIt = scope->m_properties.find(access->m_name);
- if (scopeIt != scope->m_properties.end()) {
- const QString typeName = access->m_parentType.isEmpty() ? scopeIt->typeName()
- : access->m_parentType;
- if (scopeIt->isList() || typeName == QLatin1String("string")) {
- if (access->m_child && access->m_child->m_name != QLatin1String("length")) {
- colorOut.write("Warning: ", Warning);
- colorOut.write(
- QString::fromLatin1(
- "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5\n")
- .arg(access->m_name)
- .arg(QLatin1String(scopeIt->isList() ? "list" : "string"))
- .arg(access->m_child->m_name)
- .arg(access->m_child->m_location.startLine)
- .arg(access->m_child->m_location.startColumn), Normal);
- printContext(colorOut, code, access->m_child->m_location);
- return false;
- }
- return true;
- }
-
- if (!access->m_child)
- return true;
-
- if (const ScopeTree *type = scopeIt->type()) {
- if (access->m_parentType.isEmpty())
- return checkMemberAccess(code, access.get(), type, types, colorOut);
- }
-
- if (unknownBuiltins.contains(typeName))
- return true;
-
- const auto it = types.find(typeName);
- if (it != types.end())
- return checkMemberAccess(code, access.get(), it->get(), types, colorOut);
-
- colorOut.write("Warning: ", Warning);
- colorOut.write(
- QString::fromLatin1("Type \"%1\" of member \"%2\" not found at %3:%4.\n")
- .arg(typeName)
- .arg(access->m_name)
- .arg(access->m_location.startLine)
- .arg(access->m_location.startColumn), Normal);
- printContext(colorOut, code, access->m_location);
- return false;
- }
-
- const auto scopeMethodIt = scope->m_methods.find(access->m_name);
- if (scopeMethodIt != scope->m_methods.end())
- return true; // Access to property of JS function
-
- for (const auto enumerator : scope->m_enums) {
- for (const QString &key : enumerator.keys()) {
- if (access->m_name != key)
- continue;
-
- if (!access->m_child)
- return true;
-
- colorOut.write("Warning: ", Warning);
- colorOut.write(QString::fromLatin1(
- "\"%1\" is an enum value. You cannot access \"%2\" on it at %3:%4\n")
- .arg(access->m_name)
- .arg(access->m_child->m_name)
- .arg(access->m_child->m_location.startLine)
- .arg(access->m_child->m_location.startColumn), Normal);
- printContext(colorOut, code, access->m_child->m_location);
- return false;
- }
- }
-
- auto type = types.value(scopeName);
- while (type) {
- const auto typeIt = type->m_properties.find(access->m_name);
- if (typeIt != type->m_properties.end()) {
- const ScopeTree *propType = access->m_parentType.isEmpty()
- ? typeIt->type()
- : types.value(access->m_parentType).get();
- return checkMemberAccess(code, access.get(),
- propType ? propType : types.value(typeIt->typeName()).get(),
- types, colorOut);
- }
-
- const auto typeMethodIt = type->m_methods.find(access->m_name);
- if (typeMethodIt != type->m_methods.end()) {
- if (access->m_child == nullptr)
- return true;
-
- colorOut.write("Warning: ", Warning);
- colorOut.write(QString::fromLatin1(
- "\"%1\" is a method. You cannot access \"%2\" on it at %3:%4\n")
- .arg(access->m_name)
- .arg(access->m_child->m_name)
- .arg(access->m_child->m_location.startLine)
- .arg(access->m_child->m_location.startColumn), Normal);
- printContext(colorOut, code, access->m_child->m_location);
- return false;
- }
-
- type = types.value(type->superclassName());
- }
-
- if (access->m_name.front().isUpper() && scope->scopeType() == ScopeType::QMLScope) {
- // may be an attached type
- const auto it = types.find(access->m_name);
- if (it != types.end() && !(*it)->attachedTypeName().isEmpty()) {
- const auto attached = types.find((*it)->attachedTypeName());
- if (attached != types.end())
- return checkMemberAccess(code, access.get(), attached->get(), types, colorOut);
- }
- }
-
- colorOut.write("Warning: ", Warning);
- colorOut.write(QString::fromLatin1(
- "Property \"%1\" not found on type \"%2\" at %3:%4\n")
- .arg(access->m_name)
- .arg(scopeName)
- .arg(access->m_location.startLine)
- .arg(access->m_location.startColumn), Normal);
- printContext(colorOut, code, access->m_location);
- return false;
-}
-
-bool ScopeTree::recheckIdentifiers(
- const QString &code,
- const QHash<QString, const ScopeTree *> &qmlIDs,
- const QHash<QString, ScopeTree::ConstPtr> &types,
- const ScopeTree *root, const QString &rootId,
- ColorOutput& colorOut) const
-{
- bool noUnqualifiedIdentifier = true;
-
- // revisit all scopes
- QQueue<const ScopeTree *> workQueue;
- workQueue.enqueue(this);
- while (!workQueue.empty()) {
- const ScopeTree *currentScope = workQueue.dequeue();
- for (const auto &handler : currentScope->m_unmatchedSignalHandlers) {
- colorOut.write("Warning: ", Warning);
- colorOut.write(QString::fromLatin1(
- "no matching signal found for handler \"%1\" at %2:%3\n")
- .arg(handler.first).arg(handler.second.startLine)
- .arg(handler.second.startColumn), Normal);
- printContext(colorOut, code, handler.second);
- }
-
- for (const auto &memberAccessTree : qAsConst(currentScope->m_accessedIdentifiers)) {
- if (currentScope->isIdInCurrentJSScopes(memberAccessTree->m_name))
- continue;
-
- auto it = qmlIDs.find(memberAccessTree->m_name);
- if (it != qmlIDs.end()) {
- if (*it != nullptr) {
- if (!checkMemberAccess(code, memberAccessTree.get(), *it, types, colorOut))
- noUnqualifiedIdentifier = false;
- continue;
- } else if (memberAccessTree->m_child
- && memberAccessTree->m_child->m_name.front().isUpper()) {
- // It could be a qualified type name
- const QString qualified = memberAccessTree->m_name + QLatin1Char('.')
- + memberAccessTree->m_child->m_name;
- const auto typeIt = types.find(qualified);
- if (typeIt != types.end()) {
- if (!checkMemberAccess(code, memberAccessTree->m_child.get(), typeIt->get(),
- types, colorOut)) {
- noUnqualifiedIdentifier = false;
- }
- continue;
- }
- }
- }
-
- auto qmlScope = currentScope->currentQMLScope();
- if (qmlScope->methods().contains(memberAccessTree->m_name)) {
- // a property of a JavaScript function
- continue;
- }
-
- const auto qmlIt = qmlScope->m_properties.find(memberAccessTree->m_name);
- if (qmlIt != qmlScope->m_properties.end()) {
- if (!memberAccessTree->m_child || unknownBuiltins.contains(qmlIt->typeName()))
- continue;
-
- if (!qmlIt->type()) {
- colorOut.write("Warning: ", Warning);
- colorOut.write(QString::fromLatin1(
- "Type of property \"%2\" not found at %3:%4\n")
- .arg(memberAccessTree->m_name)
- .arg(memberAccessTree->m_location.startLine)
- .arg(memberAccessTree->m_location.startColumn), Normal);
- printContext(colorOut, code, memberAccessTree->m_location);
- noUnqualifiedIdentifier = false;
- } else if (!checkMemberAccess(code, memberAccessTree.get(), qmlIt->type(), types,
- colorOut)) {
- noUnqualifiedIdentifier = false;
- }
-
- continue;
- }
-
- // TODO: Lots of builtins are missing
- if (memberAccessTree->m_name == "Qt")
- continue;
-
- const auto typeIt = types.find(memberAccessTree->m_name);
- if (typeIt != types.end()) {
- if (!checkMemberAccess(code, memberAccessTree.get(), typeIt->get(), types,
- colorOut)) {
- noUnqualifiedIdentifier = false;
- }
- continue;
- }
-
- noUnqualifiedIdentifier = false;
- colorOut.write("Warning: ", Warning);
- auto location = memberAccessTree->m_location;
- colorOut.write(QString::fromLatin1("unqualified access at %1:%2\n")
- .arg(location.startLine).arg(location.startColumn),
- Normal);
-
- printContext(colorOut, code, location);
-
- // root(JS) --> program(qml) --> (first element)
- const auto firstElement = root->m_childScopes[0]->m_childScopes[0];
- if (firstElement->m_properties.contains(memberAccessTree->m_name)
- || firstElement->m_methods.contains(memberAccessTree->m_name)
- || firstElement->m_enums.contains(memberAccessTree->m_name)) {
- colorOut.write("Note: ", Info);
- colorOut.write(memberAccessTree->m_name + QLatin1String(" is a meber of the root element\n"), Normal );
- colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal);
- if (rootId == QLatin1String("<id>")) {
- colorOut.write("Note: ", Warning);
- colorOut.write(("You first have to give the root element an id\n"));
- }
- IssueLocationWithContext issueLocationWithContext {code, location};
- colorOut.write(issueLocationWithContext.beforeText().toString(), Normal);
- colorOut.write(rootId + QLatin1Char('.'), Hint);
- colorOut.write(issueLocationWithContext.issueText().toString(), Normal);
- colorOut.write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal);
- } else if (currentScope->isIdInjectedFromSignal(memberAccessTree->m_name)) {
- auto methodUsages = currentScope->currentQMLScope()->m_injectedSignalIdentifiers
- .values(memberAccessTree->m_name);
- auto location = memberAccessTree->m_location;
- // sort the list of signal handlers by their occurrence in the source code
- // then, we select the first one whose location is after the unqualified id
- // and go one step backwards to get the one which we actually need
- std::sort(methodUsages.begin(), methodUsages.end(),
- [](const MethodUsage &m1, const MethodUsage &m2) {
- return m1.loc.startLine < m2.loc.startLine
- || (m1.loc.startLine == m2.loc.startLine
- && m1.loc.startColumn < m2.loc.startColumn);
- });
- auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(),
- [&location](const MethodUsage &methodUsage) {
- return location.startLine < methodUsage.loc.startLine
- || (location.startLine == methodUsage.loc.startLine
- && location.startColumn < methodUsage.loc.startColumn);
- });
- auto methodUsage = *(--oneBehindIt);
- colorOut.write("Note:", Info);
- colorOut.write(
- memberAccessTree->m_name + QString::fromLatin1(
- " is accessible in this scope because "
- "you are handling a signal at %1:%2\n")
- .arg(methodUsage.loc.startLine).arg(methodUsage.loc.startColumn),
- Normal);
- colorOut.write("Consider using a function instead\n", Normal);
- IssueLocationWithContext context {code, methodUsage.loc};
- colorOut.write(context.beforeText() + QLatin1Char(' '));
- colorOut.write(methodUsage.hasMultilineHandlerBody ? "function(" : "(", Hint);
- const auto parameters = methodUsage.method.parameterNames();
- for (int numParams = parameters.size(); numParams > 0; --numParams) {
- colorOut.write(parameters.at(parameters.size() - numParams), Hint);
- if (numParams > 1)
- colorOut.write(", ", Hint);
- }
- colorOut.write(methodUsage.hasMultilineHandlerBody ? ")" : ") => ", Hint);
- colorOut.write(" {...", Normal);
- }
- colorOut.write("\n\n\n", Normal);
- }
- for (auto const &childScope: currentScope->m_childScopes)
- workQueue.enqueue(childScope.get());
- }
- return noUnqualifiedIdentifier;
-}
-
-bool ScopeTree::isIdInCurrentQMlScopes(const QString &id) const
-{
- const auto *qmlScope = currentQMLScope();
- return qmlScope->m_properties.contains(id)
- || qmlScope->m_methods.contains(id)
- || qmlScope->m_enums.contains(id);
-}
-
-bool ScopeTree::isIdInCurrentJSScopes(const QString &id) const
-{
- auto jsScope = this;
- while (jsScope) {
- if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_jsIdentifiers.contains(id))
- return true;
- jsScope = jsScope->m_parentScope;
- }
- return false;
-}
-
-bool ScopeTree::isIdInjectedFromSignal(const QString &id) const
-{
- return currentQMLScope()->m_injectedSignalIdentifiers.contains(id);
-}
-
-const ScopeTree *ScopeTree::currentQMLScope() const
-{
- auto qmlScope = this;
- while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope)
- qmlScope = qmlScope->m_parentScope;
- return qmlScope;
-}
-
-void ScopeTree::printContext(ColorOutput &colorOut, const QString &code,
- const QQmlJS::SourceLocation &location) const
-{
- IssueLocationWithContext issueLocationWithContext {code, location};
- colorOut.write(issueLocationWithContext.beforeText().toString(), Normal);
- colorOut.write(issueLocationWithContext.issueText().toString(), Error);
- colorOut.write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'), Normal);
- int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t'));
- colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText().length() - tabCount)
- + QString("\t").repeated(tabCount)
- + QString("^").repeated(location.length)
- + QLatin1Char('\n'), Normal);
-}
-
-void ScopeTree::addExport(const QString &name, const QString &package,
- const ComponentVersion &version)
-{
- m_exports.append(Export(package, name, version, 0));
-}
-
-void ScopeTree::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision)
-{
- m_exports[exportIndex].setMetaObjectRevision(metaObjectRevision);
-}
-
-void ScopeTree::updateParentProperty(const ScopeTree *scope)
-{
- auto it = m_properties.find(QLatin1String("parent"));
- if (it != m_properties.end()
- && scope->name() != QLatin1String("Component")
- && scope->name() != QLatin1String("program"))
- it->setType(scope);
-}
-
-ScopeTree::Export::Export(QString package, QString type, const ComponentVersion &version,
- int metaObjectRevision) :
- m_package(std::move(package)),
- m_type(std::move(type)),
- m_version(version),
- m_metaObjectRevision(metaObjectRevision)
-{
-}
-
-bool ScopeTree::Export::isValid() const
-{
- return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty();
-}