diff options
-rw-r--r-- | src/qml/compiler/qqmlirbuilder.cpp | 122 | ||||
-rw-r--r-- | src/qml/compiler/qqmlirbuilder_p.h | 136 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 39 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsmetatypes_p.h | 19 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/bad_QT_TR_NOOP.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/data/bad_qsTr.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 13 |
7 files changed, 226 insertions, 113 deletions
diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index fc0da9db1b..7b24154701 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -1209,119 +1209,17 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST void IRBuilder::tryGeneratingTranslationBinding(QStringView base, AST::ArgumentList *args, QV4::CompiledData::Binding *binding) { - if (base == QLatin1String("qsTr")) { - QV4::CompiledData::TranslationData translationData; - translationData.number = -1; - translationData.commentIndex = 0; // empty string - translationData.padding = 0; - - if (!args || !args->expression) - return; // no arguments, stop - - QStringView translation; - if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { - translation = arg1->value; - } else { - return; // first argument is not a string, stop - } - translationData.stringIndex = jsGenerator->registerString(translation.toString()); - - args = args->next; - - if (args) { - QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression); - if (!arg2) - return; // second argument is not a string, stop - translationData.commentIndex = jsGenerator->registerString(arg2->value.toString()); - - args = args->next; - if (args) { - if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { - translationData.number = int(arg3->value); - args = args->next; - } else { - return; // third argument is not a translation number, stop - } - } - } - - if (args) - return; // too many arguments, stop - - binding->type = QV4::CompiledData::Binding::Type_Translation; - binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData); - } else if (base == QLatin1String("qsTrId")) { - QV4::CompiledData::TranslationData translationData; - translationData.number = -1; - translationData.commentIndex = 0; // empty string, but unused - translationData.padding = 0; - - if (!args || !args->expression) - return; // no arguments, stop - - QStringView id; - if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { - id = arg1->value; - } else { - return; // first argument is not a string, stop - } - translationData.stringIndex = jsGenerator->registerString(id.toString()); - - args = args->next; - - if (args) { - if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { - translationData.number = int(arg3->value); - args = args->next; - } else { - return; // third argument is not a translation number, stop - } - } - - if (args) - return; // too many arguments, stop - - binding->type = QV4::CompiledData::Binding::Type_TranslationById; - binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData); - } else if (base == QLatin1String("QT_TR_NOOP") || base == QLatin1String("QT_TRID_NOOP")) { - if (!args || !args->expression) - return; // no arguments, stop - - QStringView str; - if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { - str = arg1->value; - } else { - return; // first argument is not a string, stop - } - - args = args->next; - if (args) - return; // too many arguments, stop - - binding->type = QV4::CompiledData::Binding::Type_String; - binding->stringIndex = jsGenerator->registerString(str.toString()); - } else if (base == QLatin1String("QT_TRANSLATE_NOOP")) { - if (!args || !args->expression) - return; // no arguments, stop - - args = args->next; - if (!args || !args->expression) - return; // no second arguments, stop - - QStringView str; - if (QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { - str = arg2->value; - } else { - return; // first argument is not a string, stop - } - - args = args->next; - if (args) - return; // too many arguments, stop + auto registerMainString = [&](QStringView mainString) { return jsGenerator->registerString(mainString.toString()) ; }; + auto registerCommentString = [&](QStringView commentString) { return jsGenerator->registerString(commentString.toString()); }; + auto finalizeTranslationData = [&](QV4::CompiledData::Binding::ValueType type, QV4::CompiledData::TranslationData translationData) { + binding->type = type; + if (type == QV4::CompiledData::Binding::Type_Translation || type == QV4::CompiledData::Binding::Type_TranslationById) + binding->value.translationDataIndex = jsGenerator->registerTranslation(translationData); + else if (type == QV4::CompiledData::Binding::Type_String) + binding->stringIndex = translationData.number; + }; - binding->type = QV4::CompiledData::Binding::Type_String; - binding->stringIndex = jsGenerator->registerString(str.toString()); - } + tryGeneratingTranslationBindingBase(base, args, registerMainString, registerCommentString, finalizeTranslationData); } void IRBuilder::appendBinding(QQmlJS::AST::UiQualifiedId *name, QQmlJS::AST::Statement *value, QQmlJS::AST::Node *parentNode) diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 8555aedaca..213d43134a 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -615,6 +615,142 @@ private: Document *document; }; +// RegisterStringN ~= std::function<int(QStringView)> +// FinalizeTranlationData ~= std::function<void(QV4::CompiledData::Binding::ValueType, QV4::CompiledData::TranslationData)> +/* + \internal + \a base: name of the potential translation function + \a args: arguments to the function call + \a registerMainString: Takes the first argument passed to the translation function, and it's + result will be stored in a TranslationData's stringIndex for translation bindings and in numbeIndex + for string bindings. + \a registerCommentString: Takes the comment argument passed to some of the translation functions. + Result will be stored in a TranslationData's commentIndex + \a finalizeTranslationData: Takes the type of the binding and the previously set up TranslationData + */ +template<typename RegisterString1, typename RegisterString2, typename FinalizeTranslationData> +void tryGeneratingTranslationBindingBase(QStringView base, QQmlJS::AST::ArgumentList *args, + RegisterString1 registerMainString, + RegisterString2 registerCommentString, + FinalizeTranslationData finalizeTranslationData + ) +{ + if (base == QLatin1String("qsTr")) { + QV4::CompiledData::TranslationData translationData; + translationData.number = -1; + translationData.commentIndex = 0; // empty string + translationData.padding = 0; + + if (!args || !args->expression) + return; // no arguments, stop + + QStringView translation; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + translation = arg1->value; + } else { + return; // first argument is not a string, stop + } + + translationData.stringIndex = registerMainString(translation); + + args = args->next; + + if (args) { + QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression); + if (!arg2) + return; // second argument is not a string, stop + translationData.commentIndex = registerCommentString(arg2->value); + + args = args->next; + if (args) { + if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { + translationData.number = int(arg3->value); + args = args->next; + } else { + return; // third argument is not a translation number, stop + } + } + } + + if (args) + return; // too many arguments, stop + + finalizeTranslationData(QV4::CompiledData::Binding::Type_Translation, translationData); + } else if (base == QLatin1String("qsTrId")) { + QV4::CompiledData::TranslationData translationData; + translationData.number = -1; + translationData.commentIndex = 0; // empty string, but unused + translationData.padding = 0; + + if (!args || !args->expression) + return; // no arguments, stop + + QStringView id; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + id = arg1->value; + } else { + return; // first argument is not a string, stop + } + translationData.stringIndex = registerMainString(id); + + args = args->next; + + if (args) { + if (QQmlJS::AST::NumericLiteral *arg3 = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(args->expression)) { + translationData.number = int(arg3->value); + args = args->next; + } else { + return; // third argument is not a translation number, stop + } + } + + if (args) + return; // too many arguments, stop + + finalizeTranslationData(QV4::CompiledData::Binding::Type_TranslationById, translationData); + } else if (base == QLatin1String("QT_TR_NOOP") || base == QLatin1String("QT_TRID_NOOP")) { + if (!args || !args->expression) + return; // no arguments, stop + + QStringView str; + if (QQmlJS::AST::StringLiteral *arg1 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + str = arg1->value; + } else { + return; // first argument is not a string, stop + } + + args = args->next; + if (args) + return; // too many arguments, stop + + QV4::CompiledData::TranslationData translationData; + translationData.number = registerMainString(str); + finalizeTranslationData(QV4::CompiledData::Binding::Type_String, translationData); + } else if (base == QLatin1String("QT_TRANSLATE_NOOP")) { + if (!args || !args->expression) + return; // no arguments, stop + + args = args->next; + if (!args || !args->expression) + return; // no second arguments, stop + + QStringView str; + if (QQmlJS::AST::StringLiteral *arg2 = QQmlJS::AST::cast<QQmlJS::AST::StringLiteral *>(args->expression)) { + str = arg2->value; + } else { + return; // first argument is not a string, stop + } + + args = args->next; + if (args) + return; // too many arguments, stop + + QV4::CompiledData::TranslationData fakeTranslationData; + fakeTranslationData.number = registerMainString(str); + finalizeTranslationData(QV4::CompiledData::Binding::Type_String, fakeTranslationData); + } +} + } // namespace QmlIR QT_END_NAMESPACE diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 254c282e26..fab5982c2a 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -39,6 +39,7 @@ #include <QtQml/private/qv4codegen_p.h> #include <QtQml/private/qqmlstringconverters_p.h> +#include <QtQml/private/qqmlirbuilder_p.h> #include <algorithm> @@ -1323,6 +1324,32 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *) leaveEnvironment(); } + +// ### TODO: add warning about suspicious translation binding when returning false? +static std::optional<QQmlJSMetaPropertyBinding> handleTranslationBinding(QStringView base, QQmlJS::AST::ArgumentList *args, + const QHash<QString, QQmlJSScope::ConstPtr> &rootScopeImports) +{ + std::optional<QQmlJSMetaPropertyBinding> maybeBinding = std::nullopt; + QStringView mainString; + auto registerMainString = [&](QStringView string) { + mainString = string; + return 0; + }; + auto discardCommentString = [](QStringView) {return -1;}; + auto finalizeBinding = [&](QV4::CompiledData::Binding::ValueType type, QV4::CompiledData::TranslationData) { + QQmlJSMetaPropertyBinding binding; + if (type == QV4::CompiledData::Binding::Type_Translation) + binding.setTranslation(mainString); + else if (type == QV4::CompiledData::Binding::Type_TranslationById) + binding.setTarnslationId(mainString); + else + binding.setLiteral(QQmlJSMetaPropertyBinding::StringLiteral, u"string"_qs, mainString.toString(), rootScopeImports[u"string"_qs]); + maybeBinding = binding; + }; + QmlIR::tryGeneratingTranslationBindingBase(base, args, registerMainString, discardCommentString, finalizeBinding); + return maybeBinding; +} + void QQmlJSImportVisitor::parseLiteralBinding(const QString name, const QQmlJS::AST::Statement *statement) { @@ -1389,6 +1416,18 @@ void QQmlJSImportVisitor::parseLiteralBinding(const QString name, bindingType = QQmlJSMetaPropertyBinding::NumberLiteral; value = -lit->value; } + } else if (QQmlJS::AST::CallExpression *call = QQmlJS::AST::cast<QQmlJS::AST::CallExpression *>(expr)) { + if (QQmlJS::AST::IdentifierExpression *base = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression *>(call->base)) { + if (auto translationBindingOpt = handleTranslationBinding(base->name, call->arguments, m_rootScopeImports)) { + auto translationBinding = translationBindingOpt.value(); + translationBinding.setPropertyName(name); + translationBinding.setSourceLocation(expr->firstSourceLocation()); + m_currentScope->addOwnPropertyBinding(translationBinding); + if (translationBinding.bindingType() == QQmlJSMetaPropertyBinding::BindingType::StringLiteral) + m_literalScopesToCheck << m_currentScope; + return; + } + } } break; } diff --git a/src/qmlcompiler/qqmljsmetatypes_p.h b/src/qmlcompiler/qqmljsmetatypes_p.h index 17e23d0ffd..231020ad74 100644 --- a/src/qmlcompiler/qqmljsmetatypes_p.h +++ b/src/qmlcompiler/qqmljsmetatypes_p.h @@ -394,7 +394,11 @@ private: // binding. separating the non-overlapping bits would indicate what we // require for each binding type more clearly - QVariant m_literalValue; // constant in literal (or null) expression + //union { + QString m_translationString; + QString m_translationId; + QVariant m_literalValue; // constant in literal (or null) expression + //}; QWeakPointer<const QQmlJSScope> m_value; // object type of Object binding *OR* a literal type QString m_valueTypeName; @@ -452,6 +456,19 @@ public: m_literalValue = value; } + // ### TODO: we might need comment and translation number at some point + void setTranslation(QStringView translation) + { + setBindingTypeOnce(BindingType::Translation); + m_translationString = translation.toString(); + } + + void setTarnslationId(QStringView id) + { + setBindingTypeOnce(BindingType::TranslationById); + m_translationId = id.toString(); + } + void setObject(const QString &typeName, const QSharedPointer<const QQmlJSScope> &type) { setBindingTypeOnce(BindingType::Object); diff --git a/tests/auto/qml/qmllint/data/bad_QT_TR_NOOP.qml b/tests/auto/qml/qmllint/data/bad_QT_TR_NOOP.qml new file mode 100644 index 0000000000..1e7b315dc5 --- /dev/null +++ b/tests/auto/qml/qmllint/data/bad_QT_TR_NOOP.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property int i: QT_TR_NOOP("fail") +} diff --git a/tests/auto/qml/qmllint/data/bad_qsTr.qml b/tests/auto/qml/qmllint/data/bad_qsTr.qml new file mode 100644 index 0000000000..3723324a72 --- /dev/null +++ b/tests/auto/qml/qmllint/data/bad_qsTr.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property int i: qsTr("fail") +} diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 1b943f068c..b2f8d43ca9 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -525,6 +525,16 @@ void TestQmllint::dirtyQmlCode_data() << QStringLiteral("Cannot assign a numeric constant to a string property") << QString() << false; + QTest::newRow("bad tranlsation binding (qsTr)") + << QStringLiteral("bad_qsTr.qml") + << QStringLiteral("") + << QString() + << false; + QTest::newRow("bad string binding (QT_TR_NOOP)") + << QStringLiteral("bad_QT_TR_NOOP.qml") + << QStringLiteral("Cannot assign binding of type string to int") + << QString() + << false; QTest::newRow("BadBinding") << QStringLiteral("badBinding.qml") << QStringLiteral("Binding assigned to \"doesNotExist\", but no property " @@ -830,6 +840,9 @@ void TestQmllint::dirtyQmlCode() "We're currently not able to verify any non-trivial QString conversion that " "requires QQmlStringConverters", Abort); + QEXPECT_FAIL("bad tranlsation binding (qsTr)", + "We currently do not check translation binding", + Abort); if (exitsNormally) QVERIFY(process.exitCode() == 0); |