aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/loader/itemreaderastvisitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/corelib/loader/itemreaderastvisitor.cpp')
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.cpp391
1 files changed, 391 insertions, 0 deletions
diff --git a/src/lib/corelib/loader/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp
new file mode 100644
index 000000000..94be46bf3
--- /dev/null
+++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp
@@ -0,0 +1,391 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "itemreaderastvisitor.h"
+
+#include "astimportshandler.h"
+#include "astpropertiesitemhandler.h"
+#include "itemreadervisitorstate.h"
+
+#include <api/languageinfo.h>
+#include <jsextensions/jsextensions.h>
+#include <language/asttools.h>
+#include <language/builtindeclarations.h>
+#include <language/filecontext.h>
+#include <language/identifiersearch.h>
+#include <language/item.h>
+#include <language/value.h>
+#include <parser/qmljsast_p.h>
+#include <tools/codelocation.h>
+#include <tools/error.h>
+#include <tools/qbsassert.h>
+#include <tools/qttools.h>
+#include <tools/stringconstants.h>
+#include <logging/translator.h>
+
+#include <algorithm>
+
+using namespace QbsQmlJS;
+
+namespace qbs {
+namespace Internal {
+
+ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState,
+ FileContextPtr file, ItemPool *itemPool, Logger &logger)
+ : m_visitorState(visitorState)
+ , m_file(std::move(file))
+ , m_itemPool(itemPool)
+ , m_logger(logger)
+{
+}
+
+bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram)
+{
+ ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file);
+ importsHandler.handleImports(uiProgram->imports);
+ m_typeNameToFile = importsHandler.typeNameFileMap();
+ return true;
+}
+
+static ItemValuePtr findItemProperty(const Item *container, const Item *item)
+{
+ ItemValuePtr itemValue;
+ const auto &srcprops = container->properties();
+ auto it = std::find_if(srcprops.begin(), srcprops.end(), [item] (const ValuePtr &v) {
+ return v->type() == Value::ItemValueType
+ && std::static_pointer_cast<ItemValue>(v)->item() == item;
+ });
+ if (it != srcprops.end())
+ itemValue = std::static_pointer_cast<ItemValue>(it.value());
+ return itemValue;
+}
+
+bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast)
+{
+ const QString typeName = ast->qualifiedTypeNameId->name.toString();
+ const CodeLocation itemLocation = toCodeLocation(ast->qualifiedTypeNameId->identifierToken);
+ const Item *baseItem = nullptr;
+ Item *mostDerivingItem = nullptr;
+
+ Item *item = Item::create(m_itemPool, ItemType::Unknown);
+ item->setFile(m_file);
+ item->setLocation(itemLocation);
+
+ // Inheritance resolving, part 1: Find out our actual type name (needed for setting
+ // up children and alternatives).
+ const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId);
+ const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName);
+ ItemType itemType;
+ if (!baseTypeFileName.isEmpty()) {
+ const bool isMostDerivingItem = (m_visitorState.mostDerivingItem() == nullptr);
+ if (isMostDerivingItem)
+ m_visitorState.setMostDerivingItem(item);
+ mostDerivingItem = m_visitorState.mostDerivingItem();
+ baseItem = m_visitorState.readFile(baseTypeFileName, m_file->searchPaths(), m_itemPool);
+ if (isMostDerivingItem)
+ m_visitorState.setMostDerivingItem(nullptr);
+ QBS_CHECK(baseItem->type() <= ItemType::LastActualItem);
+ itemType = baseItem->type();
+ } else {
+ if (fullTypeName.size() > 1) {
+ throw ErrorInfo(Tr::tr("Invalid item '%1'. Did you mean to set a module property?")
+ .arg(fullTypeName.join(QLatin1Char('.'))), itemLocation);
+ }
+ itemType = BuiltinDeclarations::instance().typeForName(typeName, itemLocation);
+ checkDeprecationStatus(itemType, typeName, itemLocation);
+ if (itemType == ItemType::Properties && m_item && m_item->type() == ItemType::SubProject)
+ itemType = ItemType::PropertiesInSubProject;
+ }
+
+ item->m_type = itemType;
+
+ if (m_item)
+ Item::addChild(m_item, item); // Add this item to the children of the parent item.
+ else
+ m_item = item; // This is the root item.
+
+ if (ast->initializer) {
+ Item *mdi = m_visitorState.mostDerivingItem();
+ m_visitorState.setMostDerivingItem(nullptr);
+ qSwap(m_item, item);
+ const ItemType oldInstanceItemType = m_instanceItemType;
+ if (itemType == ItemType::Parameters || itemType == ItemType::Depends)
+ m_instanceItemType = ItemType::ModuleParameters;
+ ast->initializer->accept(this);
+ m_instanceItemType = oldInstanceItemType;
+ qSwap(m_item, item);
+ m_visitorState.setMostDerivingItem(mdi);
+ }
+
+ ASTPropertiesItemHandler(item).handlePropertiesItems();
+
+ // Inheritance resolving, part 2 (depends on alternatives having been set up).
+ if (baseItem) {
+ inheritItem(item, baseItem);
+ if (baseItem->file()->idScope()) {
+ // Make ids from the derived file visible in the base file.
+ // ### Do we want to turn off this feature? It's QMLish but kind of strange.
+ item->file()->ensureIdScope(m_itemPool);
+ baseItem->file()->idScope()->setPrototype(item->file()->idScope());
+
+ // Replace the base item with the most deriving item.
+ ItemValuePtr baseItemIdValue = findItemProperty(baseItem->file()->idScope(), baseItem);
+ if (baseItemIdValue)
+ baseItemIdValue->setItem(mostDerivingItem);
+ }
+ } else {
+ // Only the item at the top of the inheritance chain is a built-in item.
+ // We cannot do this in "part 1", because then the visitor would complain about duplicate
+ // bindings.
+ item->setupForBuiltinType(m_visitorState.deprecationWarningMode(), m_logger);
+ }
+
+ return false;
+}
+
+void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName,
+ const AST::SourceLocation &sourceLocation)
+{
+ if (Q_UNLIKELY(item->hasOwnProperty(bindingName.last()))) {
+ QString msg = Tr::tr("Duplicate binding for '%1'");
+ throw ErrorInfo(msg.arg(bindingName.join(QLatin1Char('.'))),
+ toCodeLocation(sourceLocation));
+ }
+}
+
+bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast)
+{
+ PropertyDeclaration p;
+ if (Q_UNLIKELY(ast->name.isEmpty()))
+ throw ErrorInfo(Tr::tr("public member without name"));
+ if (Q_UNLIKELY(ast->memberType.isEmpty()))
+ throw ErrorInfo(Tr::tr("public member without type"));
+ if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal))
+ throw ErrorInfo(Tr::tr("public member with signal type not supported"));
+ p.setName(ast->name.toString());
+ p.setType(PropertyDeclaration::propertyTypeFromString(ast->memberType.toString()));
+ if (p.type() == PropertyDeclaration::UnknownType) {
+ throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.")
+ .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken));
+ }
+ if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) {
+ throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg(
+ ast->typeModifier.toString()));
+ }
+ if (ast->isReadonlyMember)
+ p.setFlags(PropertyDeclaration::ReadOnlyFlag);
+
+ m_item->m_propertyDeclarations.insert(p.name(), p);
+
+ const JSSourceValuePtr value = JSSourceValue::create();
+ value->setFile(m_file);
+ if (ast->statement) {
+ handleBindingRhs(ast->statement, value);
+ const QStringList bindingName(p.name());
+ checkDuplicateBinding(m_item, bindingName, ast->colonToken);
+ }
+
+ m_item->setProperty(p.name(), value);
+ return false;
+}
+
+bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast)
+{
+ QBS_CHECK(ast->qualifiedId);
+ QBS_CHECK(!ast->qualifiedId->name.isEmpty());
+
+ const QStringList bindingName = toStringList(ast->qualifiedId);
+
+ if (bindingName.length() == 1 && bindingName.front() == QStringLiteral("id")) {
+ const auto * const expStmt = AST::cast<AST::ExpressionStatement *>(ast->statement);
+ if (Q_UNLIKELY(!expStmt))
+ throw ErrorInfo(Tr::tr("id: must be followed by identifier"));
+ const auto * const idExp = AST::cast<AST::IdentifierExpression *>(expStmt->expression);
+ if (Q_UNLIKELY(!idExp || idExp->name.isEmpty()))
+ throw ErrorInfo(Tr::tr("id: must be followed by identifier"));
+ m_item->m_id = idExp->name.toString();
+ m_file->ensureIdScope(m_itemPool);
+ ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id());
+ if (existingId) {
+ ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id()));
+ e.append(Tr::tr("First occurrence is here."), existingId->item()->location());
+ e.append(Tr::tr("Next occurrence is here."), m_item->location());
+ throw e;
+ }
+ m_file->idScope()->setProperty(m_item->id(), ItemValue::create(m_item));
+ return false;
+ }
+
+ const JSSourceValuePtr value = JSSourceValue::create();
+ handleBindingRhs(ast->statement, value);
+
+ Item * const targetItem = targetItemForBinding(bindingName, value);
+ checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken);
+ targetItem->setProperty(bindingName.last(), value);
+ return false;
+}
+
+bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement,
+ const JSSourceValuePtr &value)
+{
+ QBS_CHECK(statement);
+ QBS_CHECK(value);
+
+ if (AST::cast<AST::Block *>(statement))
+ value->setHasFunctionForm();
+
+ value->setFile(m_file);
+ value->setSourceCode(textViewOf(m_file->content(), statement));
+ value->setLocation(statement->firstSourceLocation().startLine,
+ statement->firstSourceLocation().startColumn);
+
+ bool usesBase, usesOuter, usesOriginal;
+ IdentifierSearch idsearch;
+ idsearch.add(StringConstants::baseVar(), &usesBase);
+ idsearch.add(StringConstants::outerVar(), &usesOuter);
+ idsearch.add(StringConstants::originalVar(), &usesOriginal);
+ idsearch.start(statement);
+ if (usesBase)
+ value->setSourceUsesBase();
+ if (usesOuter)
+ value->setSourceUsesOuter();
+ if (usesOriginal)
+ value->setSourceUsesOriginal();
+ return false;
+}
+
+CodeLocation ItemReaderASTVisitor::toCodeLocation(const AST::SourceLocation &location) const
+{
+ return CodeLocation(m_file->filePath(), location.startLine, location.startColumn);
+}
+
+Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName,
+ const JSSourceValueConstPtr &value)
+{
+ Item *targetItem = m_item;
+ const int c = bindingName.size() - 1;
+ for (int i = 0; i < c; ++i) {
+ ValuePtr v = targetItem->ownProperty(bindingName.at(i));
+ if (!v) {
+ const ItemType itemType = i < c - 1 ? ItemType::ModulePrefix : m_instanceItemType;
+ Item *newItem = Item::create(m_itemPool, itemType);
+ newItem->setLocation(value->location());
+ v = ItemValue::create(newItem);
+ targetItem->setProperty(bindingName.at(i), v);
+ }
+ if (Q_UNLIKELY(v->type() != Value::ItemValueType)) {
+ QString msg = Tr::tr("Binding to non-item property.");
+ throw ErrorInfo(msg, value->location());
+ }
+ targetItem = std::static_pointer_cast<ItemValue>(v)->item();
+ }
+ return targetItem;
+}
+
+void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src)
+{
+ int insertPos = 0;
+ for (Item *child : qAsConst(src->m_children)) {
+ dst->m_children.insert(insertPos++, child);
+ child->m_parent = dst;
+ }
+
+ for (const PropertyDeclaration &pd : src->propertyDeclarations()) {
+ if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)
+ && dst->hasOwnProperty(pd.name())) {
+ throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
+ dst->property(pd.name())->location());
+ }
+ dst->setPropertyDeclaration(pd.name(), pd);
+ }
+
+ for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) {
+ ValuePtr &v = dst->m_properties[it.key()];
+ if (!v) {
+ v = it.value();
+ continue;
+ }
+ if (v->type() == Value::ItemValueType && it.value()->type() != Value::ItemValueType)
+ throw ErrorInfo(Tr::tr("Binding to non-item property."), v->location());
+ if (v->type() != it.value()->type())
+ continue;
+ switch (v->type()) {
+ case Value::JSSourceValueType: {
+ JSSourceValuePtr sv = std::static_pointer_cast<JSSourceValue>(v);
+ QBS_CHECK(!sv->baseValue());
+ const JSSourceValuePtr baseValue = std::static_pointer_cast<JSSourceValue>(it.value());
+ sv->setBaseValue(baseValue);
+ for (const JSSourceValue::Alternative &alt : sv->m_alternatives)
+ alt.value->setBaseValue(baseValue);
+ break;
+ }
+ case Value::ItemValueType:
+ inheritItem(std::static_pointer_cast<ItemValue>(v)->item(),
+ std::static_pointer_cast<const ItemValue>(it.value())->item());
+ break;
+ default:
+ QBS_CHECK(!"unexpected value type");
+ }
+ }
+}
+
+void ItemReaderASTVisitor::checkDeprecationStatus(ItemType itemType, const QString &itemName,
+ const CodeLocation &itemLocation)
+{
+ const ItemDeclaration itemDecl = BuiltinDeclarations::instance().declarationsForType(itemType);
+ const ErrorInfo error = itemDecl.checkForDeprecation(m_visitorState.deprecationWarningMode(),
+ itemName, itemLocation, m_logger);
+ if (error.hasError())
+ throw error;
+}
+
+void ItemReaderASTVisitor::doCheckItemTypes(const Item *item)
+{
+ const ItemDeclaration decl = BuiltinDeclarations::instance().declarationsForType(item->type());
+ for (const Item * const child : item->children()) {
+ if (!decl.isChildTypeAllowed(child->type())) {
+ throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.")
+ .arg(item->typeName(), child->typeName()), child->location());
+ }
+ doCheckItemTypes(child);
+ }
+}
+
+} // namespace Internal
+} // namespace qbs