qqmljsimportvisitor: give base types to attached scopes
It seems that our attached scopes have no base type: they don't inherit the attached properties or methods because their baseTypeName is not set. Therefore, set their baseTypeName, and move the resolveTypes calls inside of setScopeName() instead of potentially forgetting them after enterEnvironment/RootScope() calls. With the (resolved) base type, we know about inherited signals, like in: ``` Keys.onPressed: { /*here is the "event" argument available*/ } ``` for example where we can insert the "event" JS identifier inside the QQmlJSScope of the block of the signal handler, now that we now that "Keys" has a "pressed"-method with one argument "event" on its base type. Add a test to make sure that the body of an attached signal handler contains the JS identifier of the arguments of the attached signal to be handled. This JS identifier is used later on in qmlls to provide completions in the body of the attached signal handler. Also fix LinterVisitor::leaveEnvironment() that starts complaining about "Component" attached properties that have no child, now that attached properties have base types. Pick-to: 6.10 6.9 6.8 Fixes: QTBUG-137736 Change-Id: I8de0158ca9946d5e0e4f4f0a46614385f0edca69 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
d232d16c79
commit
9a907040a0
|
@ -116,14 +116,31 @@ bool QQmlJSImportVisitor::safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const
|
|||
\internal
|
||||
Sets the name of \a scope to \a name based on \a type.
|
||||
*/
|
||||
inline void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name)
|
||||
void QQmlJSImportVisitor::setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type,
|
||||
const QString &name)
|
||||
{
|
||||
Q_ASSERT(scope);
|
||||
if (type == QQmlSA::ScopeType::GroupedPropertyScope
|
||||
|| type == QQmlSA::ScopeType::AttachedPropertyScope)
|
||||
switch (type) {
|
||||
case QQmlSA::ScopeType::GroupedPropertyScope:
|
||||
scope->setInternalName(name);
|
||||
return;
|
||||
case QQmlSA::ScopeType::AttachedPropertyScope:
|
||||
scope->setInternalName(name);
|
||||
else
|
||||
scope->setBaseTypeName(name);
|
||||
QQmlJSScope::resolveTypes(scope, m_rootScopeImports.contextualTypes(), &m_usedTypes);
|
||||
return;
|
||||
case QQmlSA::ScopeType::QMLScope:
|
||||
scope->setBaseTypeName(name);
|
||||
QQmlJSScope::resolveTypes(scope, m_rootScopeImports.contextualTypes(), &m_usedTypes);
|
||||
return;
|
||||
case QQmlSA::ScopeType::JSFunctionScope:
|
||||
case QQmlSA::ScopeType::BindingFunctionScope:
|
||||
case QQmlSA::ScopeType::SignalHandlerFunctionScope:
|
||||
case QQmlSA::ScopeType::JSLexicalScope:
|
||||
case QQmlSA::ScopeType::EnumScope:
|
||||
scope->setBaseTypeName(name);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -224,10 +241,10 @@ void QQmlJSImportVisitor::populateCurrentScope(
|
|||
QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location)
|
||||
{
|
||||
m_currentScope->setScopeType(type);
|
||||
setScopeName(m_currentScope, type, name);
|
||||
m_currentScope->setIsComposite(true);
|
||||
m_currentScope->setFilePath(m_logger->filePath());
|
||||
m_currentScope->setSourceLocation(location);
|
||||
setScopeName(m_currentScope, type, name);
|
||||
m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, m_currentScope);
|
||||
}
|
||||
|
||||
|
@ -1754,8 +1771,7 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition)
|
|||
m_currentScope->setIsSingleton(m_rootIsSingleton);
|
||||
}
|
||||
|
||||
const QTypeRevision revision = QQmlJSScope::resolveTypes(
|
||||
m_currentScope, m_rootScopeImports.contextualTypes(), &m_usedTypes);
|
||||
const QTypeRevision revision = m_currentScope->baseTypeRevision();
|
||||
if (auto base = m_currentScope->baseType(); base) {
|
||||
if (isRoot && base->internalName() == u"QQmlComponent") {
|
||||
m_logger->log(u"Qml top level type cannot be 'Component'."_s, qmlTopLevelComponent,
|
||||
|
@ -3034,7 +3050,6 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
|
|||
|
||||
enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName,
|
||||
uiob->qualifiedTypeNameId->identifierToken);
|
||||
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), &m_usedTypes);
|
||||
|
||||
m_qmlTypes.append(m_currentScope); // new QMLScope is created here, so add it
|
||||
m_objectBindingScopes << m_currentScope;
|
||||
|
|
|
@ -160,6 +160,8 @@ protected:
|
|||
|
||||
virtual bool checkCustomParser(const QQmlJSScope::ConstPtr &scope);
|
||||
|
||||
void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name);
|
||||
|
||||
QString m_implicitImportDirectory;
|
||||
QStringList m_qmldirFiles;
|
||||
QQmlJSScope::Ptr m_currentScope;
|
||||
|
|
|
@ -30,6 +30,9 @@ void LinterVisitor::leaveEnvironment()
|
|||
{
|
||||
const auto leaveEnv = qScopeGuard([this] { QQmlJSImportVisitor::leaveEnvironment(); });
|
||||
|
||||
if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope)
|
||||
return;
|
||||
|
||||
if (auto base = m_currentScope->baseType()) {
|
||||
if (base->internalName() == u"QQmlComponent"_s) {
|
||||
const auto nChildren = std::count_if(
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
Keys.onPressed: {}
|
||||
}
|
|
@ -107,6 +107,7 @@ private Q_SLOTS:
|
|||
void groupedPropertiesConsistency();
|
||||
void groupedPropertySyntax();
|
||||
void attachedProperties();
|
||||
void attachedSignalHandler();
|
||||
void scriptIndices();
|
||||
void extensions();
|
||||
void emptyBlockBinding();
|
||||
|
@ -484,6 +485,27 @@ void tst_qqmljsscope::attachedProperties()
|
|||
QCOMPARE(value(bindingsOfBaseType, u"priority"_s).bindingType(), QQmlSA::BindingType::Script);
|
||||
}
|
||||
|
||||
void tst_qqmljsscope::attachedSignalHandler()
|
||||
{
|
||||
QQmlJSScope::ConstPtr root = run(u"attachedSignalHandler.qml"_s);
|
||||
QVERIFY(root);
|
||||
|
||||
const auto binding = std::find_if(root->childScopesBegin(), root->childScopesEnd(),
|
||||
[](const QQmlJSScope::ConstPtr &child) {
|
||||
return child->baseTypeName() == "signalHandler"_L1;
|
||||
});
|
||||
QCOMPARE_NE(binding, root->childScopesEnd());
|
||||
|
||||
const auto block = std::find_if(
|
||||
(**binding).childScopesBegin(), (**binding).childScopesEnd(),
|
||||
[](const QQmlJSScope::ConstPtr &child) { return child->baseTypeName() == "block"_L1; });
|
||||
QCOMPARE_NE(block, (**binding).childScopesEnd());
|
||||
|
||||
// note: the event JS identifier is inserted only if the "Keys" basetype of the attached
|
||||
// property was resolved before the creation of the "block" scope.
|
||||
QVERIFY((**block).jsIdentifier("event"_L1));
|
||||
}
|
||||
|
||||
inline QString getScopeName(const QQmlJSScope::ConstPtr &scope)
|
||||
{
|
||||
Q_ASSERT(scope);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
Keys.onPressed: {
|
||||
|
||||
}
|
||||
}
|
|
@ -4412,6 +4412,9 @@ void tst_qmlls_utils::completions_data()
|
|||
QTest::newRow("insideComment2")
|
||||
<< testFile("completions/Comments.qml") << 6 << 9 << ExpectedCompletions{}
|
||||
<< QStringList{ u"x"_s, u"Item"_s, forStatementCompletion };
|
||||
QTest::newRow("attachedSignalHandler")
|
||||
<< testFile("completions/attachedSignalHandler.qml") << 5 << 9
|
||||
<< ExpectedCompletions{ { "event", CompletionItemKind::Variable } } << QStringList{};
|
||||
}
|
||||
|
||||
void tst_qmlls_utils::completions()
|
||||
|
|
Loading…
Reference in New Issue