qmllint: allow mixing ID based with text based translation

Since 6.10 Qt i18n component allows mixing id based with text
based translation and this is reflected in lrelease, lupdate,
and documentation.
This patch is a partial revert of
8b61addfa4 and removes the
qmllint warning in the case of mixing these two.

Pick-to: 6.10
Change-Id: Iae2407da2bc5cc21fc3664051834c99b8c72fe58
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
This commit is contained in:
Masoud Jami 2025-08-06 16:29:05 +02:00
parent db1a9e1ac5
commit 142b267ed1
9 changed files with 67 additions and 110 deletions

View File

@ -93,6 +93,19 @@ private:
Element m_qtObject;
};
class QQmlJSTranslationFunctionMismatchCheck : public QQmlSA::PropertyPass
{
public:
using QQmlSA::PropertyPass::PropertyPass;
void onCall(const QQmlSA::Element &element, const QString &propertyName,
const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override;
private:
enum TranslationType : quint8 { None, Normal, IdBased };
TranslationType m_lastTranslationFunction = None;
};
void QdsBindingValidator::onRead(const QQmlSA::Element &element, const QString &propertyName,
const QQmlSA::Element &readScope, QQmlSA::SourceLocation location)
{
@ -142,6 +155,51 @@ void QdsBindingValidator::onWrite(const QQmlSA::Element &, const QString &proper
}
}
void QQmlJSTranslationFunctionMismatchCheck::onCall(const QQmlSA::Element &element,
const QString &propertyName,
const QQmlSA::Element &readScope,
QQmlSA::SourceLocation location)
{
Q_UNUSED(readScope);
const QQmlSA::Element globalJSObject = resolveBuiltinType(u"GlobalObject");
if (element != globalJSObject)
return;
constexpr std::array translationFunctions = {
"qsTranslate"_L1,
"QT_TRANSLATE_NOOP"_L1,
"qsTr"_L1,
"QT_TR_NOOP"_L1,
};
constexpr std::array idTranslationFunctions = {
"qsTrId"_L1,
"QT_TRID_NOOP"_L1,
};
const bool isTranslation =
std::find(translationFunctions.cbegin(), translationFunctions.cend(), propertyName)
!= translationFunctions.cend();
const bool isIdTranslation =
std::find(idTranslationFunctions.cbegin(), idTranslationFunctions.cend(), propertyName)
!= idTranslationFunctions.cend();
if (!isTranslation && !isIdTranslation)
return;
const TranslationType current = isTranslation ? Normal : IdBased;
if (m_lastTranslationFunction == None) {
m_lastTranslationFunction = current;
return;
}
if (m_lastTranslationFunction != current) {
emitWarning("Do not mix translation functions", qmlTranslationFunctionMismatch, location);
}
}
void QmlLintQdsPlugin::registerPasses(PassManager *manager, const Element &rootElement)
{
if (!rootElement.filePath().endsWith(u".ui.qml"))
@ -151,6 +209,8 @@ void QmlLintQdsPlugin::registerPasses(PassManager *manager, const Element &rootE
QAnyStringView(), QAnyStringView());
manager->registerPropertyPass(std::make_shared<QdsBindingValidator>(manager, rootElement),
QAnyStringView(), QAnyStringView());
manager->registerPropertyPass(std::make_unique<QQmlJSTranslationFunctionMismatchCheck>(manager),
QString(), QString(), QString());
manager->registerElementPass(std::make_unique<QdsElementValidator>(manager));
}

View File

@ -47,7 +47,6 @@ qt_internal_add_module(QmlCompiler
qqmlsaconstants.h
qqmlsasourcelocation.cpp qqmlsasourcelocation.h qqmlsasourcelocation_p.h
qresourcerelocater.cpp qresourcerelocater_p.h
qqmljstranslationfunctionmismatchcheck_p.h qqmljstranslationfunctionmismatchcheck.cpp
qqmljscontextproperties_p.h qqmljscontextproperties.cpp
qqmljsusercontextproperties_p.h qqmljsusercontextproperties.cpp
NO_UNITY_BUILD_SOURCES

View File

@ -8,7 +8,6 @@
#include <QtQmlCompiler/private/qqmljsimporter_p.h>
#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h>
#include <QtQmlCompiler/private/qqmljstranslationfunctionmismatchcheck_p.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qfileinfo.h>
@ -657,9 +656,6 @@ QQmlJSLinter::lintFile(const QString &filename, const QString *fileContents, con
&QQmlSA::PassManagerPrivate::deletePassManager);
passMan->registerPropertyPass(std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()),
QString(), QString(), QString());
passMan->registerPropertyPass(
std::make_unique<QQmlJSTranslationFunctionMismatchCheck>(passMan.get()), QString(),
QString(), QString());
QQmlSA::PropertyPassBuilder(passMan.get())
.withOnCall([](QQmlSA::PropertyPass *self, const QQmlSA::Element &, const QString &,

View File

@ -1,55 +0,0 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qqmljstranslationfunctionmismatchcheck_p.h"
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
void QQmlJSTranslationFunctionMismatchCheck::onCall(const QQmlSA::Element &element,
const QString &propertyName,
const QQmlSA::Element &readScope,
QQmlSA::SourceLocation location)
{
Q_UNUSED(readScope);
const QQmlSA::Element globalJSObject = resolveBuiltinType(u"GlobalObject");
if (element != globalJSObject)
return;
constexpr std::array translationFunctions = {
"qsTranslate"_L1,
"QT_TRANSLATE_NOOP"_L1,
"qsTr"_L1,
"QT_TR_NOOP"_L1,
};
constexpr std::array idTranslationFunctions = {
"qsTrId"_L1,
"QT_TRID_NOOP"_L1,
};
const bool isTranslation =
std::find(translationFunctions.cbegin(), translationFunctions.cend(), propertyName)
!= translationFunctions.cend();
const bool isIdTranslation =
std::find(idTranslationFunctions.cbegin(), idTranslationFunctions.cend(), propertyName)
!= idTranslationFunctions.cend();
if (!isTranslation && !isIdTranslation)
return;
const TranslationType current = isTranslation ? Normal : IdBased;
if (m_lastTranslationFunction == None) {
m_lastTranslationFunction = current;
return;
}
if (m_lastTranslationFunction != current) {
emitWarning("Do not mix translation functions", qmlTranslationFunctionMismatch, location);
}
}
QT_END_NAMESPACE

View File

@ -1,35 +0,0 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef QQMLJSTRANSLATIONMISMATCHCHECK_P_H
#define QQMLJSTRANSLATIONMISMATCHCHECK_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
#include "qqmlsa.h"
QT_BEGIN_NAMESPACE
class QQmlJSTranslationFunctionMismatchCheck : public QQmlSA::PropertyPass
{
public:
using QQmlSA::PropertyPass::PropertyPass;
void onCall(const QQmlSA::Element &element, const QString &propertyName,
const QQmlSA::Element &readScope, QQmlSA::SourceLocation location) override;
private:
enum TranslationType: quint8 { None, Normal, IdBased };
TranslationType m_lastTranslationFunction = None;
};
QT_END_NAMESPACE
#endif // QQMLJSTRANSLATIONMISMATCHCHECK_P_H

View File

@ -1,6 +0,0 @@
import QtQuick
Item {
property string qsTrNoop: QT_TR_NOOP("hello")
property string qsTrIdString: qsTrId(qsTrNoop)
}

View File

@ -3,4 +3,5 @@ import QtQuick
Item {
property string translated: qsTrId("Hello")
property string translatedNoOP: QT_TRID_NOOP("Hello")
property string qsTrInExpression: "Hello" + qsTr("World") + "!"
}

View File

@ -570,15 +570,6 @@ void TestQmllint::dirtyQmlCode_data()
QTest::newRow("BadScriptBindingOnGroup")
<< QStringLiteral("badScriptBinding.group.qml")
<< Result{ { { "Could not find property \"bogusProperty\"."_L1, 3, 10 } } };
{
const QString warning = u"Do not mix translation functions"_s;
QTest::addRow("BadTranslationMix")
<< testFile(u"translations/BadMix.qml"_s)
<< Result{ { { warning, 5, 49 }, { warning, 6, 56 }, } };
QTest::addRow("BadTranslationMixWithMacros")
<< testFile(u"translations/BadMixWithMacros.qml"_s)
<< Result{ { { warning, 5, 29 } } };
}
QTest::newRow("CoerceToVoid")
<< QStringLiteral("coercetovoid.qml")
<< Result{ { { "Function without return type annotation returns double"_L1 } } };
@ -3420,6 +3411,12 @@ void TestQmllint::qdsPlugin_data()
<< Result{ { Message{ functionError, 4, 5 }, Message{ functionError, 13, 9 } },
{ Message{ functionError, 7, 9 }, Message{ functionError, 10, 9 } } };
}
{
const QString warning = u"Do not mix translation functions"_s;
QTest::addRow("BadTranslationMix")
<< u"qdsPlugin/BadMix.ui.qml"_s
<< Result{ { Message{ warning, 5, 49 }, { warning, 6, 56 } } };
}
}
void TestQmllint::qdsPlugin()