aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp122
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h136
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp39
-rw-r--r--src/qmlcompiler/qqmljsmetatypes_p.h19
-rw-r--r--tests/auto/qml/qmllint/data/bad_QT_TR_NOOP.qml5
-rw-r--r--tests/auto/qml/qmllint/data/bad_qsTr.qml5
-rw-r--r--tests/auto/qml/qmllint/tst_qmllint.cpp13
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);