/**************************************************************************** ** ** 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_p.h" #include ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) : m_parentScope(parentScope), m_name(name), m_scopeType(type) {} ScopeTree *ScopeTree::createNewChildScope(ScopeType type, QString name) { Q_ASSERT(type != ScopeType::QMLScope|| !m_parentScope || m_parentScope->m_scopeType == ScopeType::QMLScope || m_parentScope->m_name == "global"); auto childScope = new ScopeTree{type, name, this}; m_childScopes.push_back(childScope); return childScope; } ScopeTree *ScopeTree::parentScope() { return m_parentScope; } void ScopeTree::insertJSIdentifier(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_currentScopeJSIdentifiers.insert(id); } else { m_currentScopeJSIdentifiers.insert(id); } } void ScopeTree::insertQMLIdentifier(QString id) { Q_ASSERT(m_scopeType == ScopeType::QMLScope); m_currentScopeQMLIdentifiers.insert(id); } void ScopeTree::insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc) { Q_ASSERT(m_scopeType == ScopeType::QMLScope); m_injectedSignalIdentifiers.insert(id, {method, loc}); } void ScopeTree::insertPropertyIdentifier(QString id) { this->insertQMLIdentifier(id); LanguageUtils::FakeMetaMethod method( id + QLatin1String("Changed"), "void"); this->addMethod(method); } bool ScopeTree::isIdInCurrentScope(const QString &id) const { return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); } void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair &id_loc_pair) { if (!isIdInCurrentScope(id_loc_pair.first)) { m_accessedIdentifiers.push_back(id_loc_pair); } } bool ScopeTree::isVisualRootScope() const { return m_parentScope && m_parentScope->m_parentScope && m_parentScope->m_parentScope->m_parentScope == nullptr; } QString ScopeTree::name() const { return m_name; } struct IssueLocationWithContext { IssueLocationWithContext(const QString& code, QQmlJS::AST::SourceLocation location) { int before = std::max(0,code.lastIndexOf('\n', location.offset)); beforeText = code.midRef(before+1, location.offset - (before+1) ); issueText = code.midRef(location.offset, location.length); int after = code.indexOf('\n', location.offset + location.length); afterText = code.midRef(location.offset+location.length, after - (location.offset+location.length)); } QStringRef beforeText; QStringRef issueText; QStringRef afterText; }; bool ScopeTree::recheckIdentifiers(const QString& code, const QHash &qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput& colorOut) const { bool noUnqualifiedIdentifier = true; // revisit all scopes QQueue workQueue; workQueue.enqueue(this); while (!workQueue.empty()) { const ScopeTree* currentScope = workQueue.dequeue(); for (auto idLocationPair : currentScope->m_accessedIdentifiers) { if (qmlIDs.contains(idLocationPair.first)) continue; if (currentScope->isIdInCurrentScope(idLocationPair.first)) { continue; } noUnqualifiedIdentifier = false; colorOut.write("Warning: ", Warning); auto location = idLocationPair.second; colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, location.startColumn), Normal); 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); // root(JS) --> program(qml) --> (first element) if (root->m_childScopes[0]->m_childScopes[0]->m_currentScopeQMLIdentifiers.contains(idLocationPair.first)) { ScopeTree *parentScope = currentScope->m_parentScope; while (parentScope && parentScope->scopeType() != ScopeType::QMLScope) { parentScope = parentScope->m_parentScope; } bool directChild = parentScope && parentScope->isVisualRootScope(); colorOut.write("Note: ", Info); colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); if (directChild) { colorOut.write(QLatin1String(" As the current element is a direct child of the root element,\n you can qualify the access with parent to avoid this warning:\n"), Normal); } else { colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); if (rootId == QLatin1String("")) { colorOut.write("Note: ", Warning); colorOut.write(("You first have to give the root element an id\n")); } } colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); colorOut.write( (directChild ? QLatin1String("parent") : rootId) + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText.toString(), Normal); colorOut.write(issueLocationWithContext.afterText + QLatin1Char('\n'), Normal); } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) { auto qmlScope = currentScope->getCurrentQMLScope(); MethodUsage methodUsage = qmlScope->m_injectedSignalIdentifiers[idLocationPair.first]; colorOut.write("Note:", Info); colorOut.write(idLocationPair.first + QString::asprintf(" is accessible in this scope because you are handling a signal at %d:%d\n", methodUsage.loc.startLine, 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("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(")", Hint); colorOut.write(" {...", Normal); } colorOut.write("\n", Normal); } for (auto const& childScope: currentScope->m_childScopes) { workQueue.enqueue(childScope); } } return noUnqualifiedIdentifier; } QMapconst &ScopeTree::methods() const { return m_methods; } bool ScopeTree::isIdInCurrentQMlScopes(QString id) const { auto qmlScope = getCurrentQMLScope(); return qmlScope->m_currentScopeQMLIdentifiers.contains(id); } bool ScopeTree::isIdInCurrentJSScopes(QString id) const { auto jsScope = this; while (jsScope) { if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_currentScopeJSIdentifiers.contains(id)) return true; jsScope = jsScope->m_parentScope; } return false; } bool ScopeTree::isIdInjectedFromSignal(QString id) const { auto qmlScope = getCurrentQMLScope(); return qmlScope->m_injectedSignalIdentifiers.contains(id); } const ScopeTree *ScopeTree::getCurrentQMLScope() const { auto qmlScope = this; while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { qmlScope = qmlScope->m_parentScope; } return qmlScope; } ScopeTree *ScopeTree::getCurrentQMLScope() { auto qmlScope = this; while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { qmlScope = qmlScope->m_parentScope; } return qmlScope; } ScopeType ScopeTree::scopeType() {return m_scopeType;} void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method) { m_methods.insert(method.methodName(), method); } void ScopeTree::addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject) { if (metaObject) { auto methodCount = metaObject->methodCount(); for (auto i = 0; i < methodCount; ++i) { auto method = metaObject->method(i); this->addMethod(method); } } }