qqmljscontextproperties: "grep" for setContextProperty calls
Add code to Grep the project folder for "setContextProperty" calls, so that a later commit can warn about usages of those context properties. Use grep to find files with setContextProperty calls, and then run a QRegularExpression on the files to extract the names and locations of the context properties. Add a fallback in case there is no grep on the system, and don't run grep on windows. Also avoid unused functions errors on windows where grep is not run so its output also does not need to be parsed. Task-number: QTBUG-128232 Change-Id: I15ceb2bbeb6ae6ac83f4bfa85b8a44d5a897d0a7 Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
This commit is contained in:
parent
76d7080fbe
commit
892ff6c8e2
|
@ -48,6 +48,7 @@ qt_internal_add_module(QmlCompiler
|
|||
qqmlsasourcelocation.cpp qqmlsasourcelocation.h qqmlsasourcelocation_p.h
|
||||
qresourcerelocater.cpp qresourcerelocater_p.h
|
||||
qqmljstranslationfunctionmismatchcheck_p.h qqmljstranslationfunctionmismatchcheck.cpp
|
||||
qqmljscontextproperties_p.h qqmljscontextproperties.cpp
|
||||
NO_UNITY_BUILD_SOURCES
|
||||
qqmljsoptimizations.cpp
|
||||
PUBLIC_LIBRARIES
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "qqmljscontextproperties_p.h"
|
||||
#include <QtCore/qtconfigmacros.h>
|
||||
#include <QtCore/qregularexpression.h>
|
||||
#include <QtCore/qdirlisting.h>
|
||||
#include <QtCore/qfile.h>
|
||||
|
||||
#if QT_CONFIG(process)
|
||||
# include <QtCore/qprocess.h>
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace QQmlJS {
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
// There are many ways to set context properties without triggering the regexp in s_pattern,
|
||||
// but its supposed to catch most context properties set via "setContextProperty".
|
||||
static constexpr QLatin1StringView s_pattern =
|
||||
R"x((\.|->)setContextProperty\s*\(\s*(QStringLiteral\s*\(|QString\s*\(|QLatin1String(View)?\s*\(|u)?\s*"([^"]*)")x"_L1;
|
||||
static constexpr int s_contextPropertyNameIdxInPattern = 4;
|
||||
|
||||
// TODO: use a central list of file extensions that can also be used by qmetatypesjsonprocessor.cpp
|
||||
// (that needs header file extensions) and Qt6QmlMacros.cmake.
|
||||
static constexpr std::array s_fileFilters = {
|
||||
"*.cpp"_L1, "*.cxx"_L1, "*.cc"_L1, "*.c"_L1, "*.c++"_L1,
|
||||
"*.hpp"_L1, "*.hxx"_L1, "*.hh"_L1, "*.h"_L1, "*.h++"_L1,
|
||||
};
|
||||
|
||||
static const QRegularExpression s_matchSetContextProperty{ s_pattern,
|
||||
QRegularExpression::MultilineOption };
|
||||
|
||||
static void collectAllFromFile(const QString &filePath, ContextProperties *output)
|
||||
{
|
||||
Q_ASSERT(output);
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return;
|
||||
|
||||
const QString fileContent = QString::fromUtf8(file.readAll());
|
||||
for (const auto &match : s_matchSetContextProperty.globalMatch(fileContent)) {
|
||||
const quint32 offset = match.capturedStart(s_contextPropertyNameIdxInPattern);
|
||||
const quint32 length = match.capturedLength(s_contextPropertyNameIdxInPattern);
|
||||
const auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(fileContent, offset);
|
||||
const QQmlJS::SourceLocation sourceLocation{ offset, length, row, column };
|
||||
|
||||
(*output)[match.captured(s_contextPropertyNameIdxInPattern)].append(
|
||||
ContextProperty{ filePath, sourceLocation });
|
||||
}
|
||||
}
|
||||
|
||||
static ContextProperties grepFallback(const QList<QString> &rootUrls)
|
||||
{
|
||||
ContextProperties result;
|
||||
|
||||
const QStringList fileFilters{ s_fileFilters.begin(), s_fileFilters.end() };
|
||||
|
||||
for (const QString &url : rootUrls) {
|
||||
for (const auto &dirEntry : QDirListing{ url, fileFilters,
|
||||
QDirListing::IteratorFlag::Recursive
|
||||
| QDirListing::IteratorFlag::FilesOnly }) {
|
||||
|
||||
const QString filePath = dirEntry.filePath();
|
||||
collectAllFromFile(filePath, &result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
|
||||
static ContextProperties parseGrepOutput(const QString &output)
|
||||
{
|
||||
ContextProperties result;
|
||||
|
||||
for (const auto line : QStringTokenizer{ output, "\n"_L1, Qt::SkipEmptyParts })
|
||||
collectAllFromFile(line.toString(), &result);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Uses grep to find files that have setContextProperty()-calls, and then search matching files
|
||||
with QRegularExpression to extract the location and name of the found context properties.
|
||||
*/
|
||||
ContextProperties ContextProperty::collectAllFrom(const QList<QString> &rootUrls)
|
||||
{
|
||||
#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
|
||||
if (qEnvironmentVariableIsSet("QT_QML_NO_GREP"))
|
||||
return grepFallback(rootUrls);
|
||||
|
||||
QProcess grep;
|
||||
QStringList arguments{ "--recursive"_L1,
|
||||
"--null-data"_L1, // match multiline patterns
|
||||
"--files-with-matches"_L1,
|
||||
"--extended-regexp"_L1, // the pattern is "extended"
|
||||
"-e"_L1,
|
||||
s_pattern };
|
||||
|
||||
// don't search non-cpp files
|
||||
for (const auto fileFilter : s_fileFilters)
|
||||
arguments << "--include"_L1 << fileFilter;
|
||||
|
||||
arguments.append(rootUrls);
|
||||
grep.start("grep"_L1, arguments);
|
||||
grep.waitForFinished();
|
||||
if (grep.exitStatus() == QProcess::NormalExit && grep.exitCode() == 0) {
|
||||
const QString output = QString::fromUtf8(grep.readAllStandardOutput());
|
||||
return parseGrepOutput(output);
|
||||
}
|
||||
#endif
|
||||
return grepFallback(rootUrls);
|
||||
}
|
||||
|
||||
} // namespace QQmlJS
|
||||
|
||||
QT_END_NAMESPACE
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#ifndef QQMLJSCONTEXTPROPERTIES_P_H
|
||||
#define QQMLJSCONTEXTPROPERTIES_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 <qtqmlcompilerexports.h>
|
||||
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qhash.h>
|
||||
#include <QtCore/qlist.h>
|
||||
|
||||
#include <QtQml/private/qqmljssourcelocation_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace QQmlJS {
|
||||
class LoggerCategory;
|
||||
|
||||
struct ContextProperty;
|
||||
using ContextProperties = QHash<QString, QList<ContextProperty>>;
|
||||
|
||||
struct Q_QMLCOMPILER_EXPORT ContextProperty
|
||||
{
|
||||
QString filename;
|
||||
QQmlJS::SourceLocation location;
|
||||
|
||||
static ContextProperties collectAllFrom(const QList<QString> &rootUrls);
|
||||
};
|
||||
|
||||
} // namespace QQmlJS
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QQMLJSCONTEXTPROPERTIES_P_H
|
|
@ -0,0 +1,8 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
property var a: myContextProperty1
|
||||
property var b: myContextProperty2
|
||||
property var c: myContextProperty3
|
||||
property var d: myContextProperty4
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <QQmlContext>
|
||||
#include <QQuickView>
|
||||
|
||||
void registerContextProperties(QQuickView &view)
|
||||
{
|
||||
view.rootContext()->setContextProperty(
|
||||
|
||||
|
||||
|
||||
QLatin1StringView
|
||||
( "myContextProperty3") ,
|
||||
QDateTime::currentDateTime())
|
||||
;
|
||||
view.rootContext()->setContextProperty( QString
|
||||
( "myContextProperty4"),
|
||||
QDateTime::currentDateTime());
|
||||
|
||||
view.rootContext()->setContextProperty(QString("myContextProperty4"),
|
||||
QDateTime::currentDateTime());
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlContext>
|
||||
#include <QQuickView>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
QQuickView view;
|
||||
|
||||
view.rootContext()->setContextProperty("myContextProperty1", QDateTime::currentDateTime());
|
||||
view.rootContext()->setContextProperty(
|
||||
u"myContextProperty2"_s,
|
||||
QDateTime::currentDateTime());
|
||||
registerContentProperties(view);
|
||||
|
||||
view.loadFromModule("ContextProperty", "Main");
|
||||
view.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
#include <QString>
|
||||
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
||||
#include <QtQmlCompiler/private/qqmljslinter_p.h>
|
||||
#include <QtQmlCompiler/private/qqmljscontextproperties_p.h>
|
||||
#include <QtQmlCompiler/private/qqmlsa_p.h>
|
||||
#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
|
||||
#include <QtCore/qplugin.h>
|
||||
|
@ -85,6 +86,9 @@ private Q_SLOTS:
|
|||
void cleanJsSnippet_data();
|
||||
void cleanJsSnippet();
|
||||
|
||||
void contextPropertiesFromRootUrls_data();
|
||||
void contextPropertiesFromRootUrls();
|
||||
|
||||
void compilerWarnings_data();
|
||||
void compilerWarnings();
|
||||
|
||||
|
@ -1619,6 +1623,44 @@ void TestQmllint::cleanJsSnippet()
|
|||
checkResult(warnings, result, [] { }, [] { }, [] { });
|
||||
}
|
||||
|
||||
using ExpectedProperties = QHash<QString, int>;
|
||||
|
||||
void TestQmllint::contextPropertiesFromRootUrls_data()
|
||||
{
|
||||
QTest::addColumn<QStringList>("rootUrls");
|
||||
QTest::addColumn<ExpectedProperties>("expectedProperties");
|
||||
QTest::addColumn<bool>("disableGrep");
|
||||
|
||||
for (const bool disableGrep : { false, true }) {
|
||||
QTest::addRow("grep%s", disableGrep ? "-fallback" : "")
|
||||
<< QStringList{ testFile("ContextProperties/src"_L1) }
|
||||
<< ExpectedProperties{ { "myContextProperty1", 1 },
|
||||
{ "myContextProperty2", 1 },
|
||||
{ "myContextProperty3", 1 },
|
||||
{ "myContextProperty4", 2 } }
|
||||
<< disableGrep;
|
||||
}
|
||||
}
|
||||
|
||||
void TestQmllint::contextPropertiesFromRootUrls()
|
||||
{
|
||||
QFETCH(QStringList, rootUrls);
|
||||
QFETCH(ExpectedProperties, expectedProperties);
|
||||
QFETCH(bool, disableGrep);
|
||||
|
||||
if (disableGrep)
|
||||
qputenv("QT_QML_NO_GREP", "1");
|
||||
const auto properties = QQmlJS::ContextProperty::collectAllFrom(rootUrls);
|
||||
if (disableGrep)
|
||||
qunsetenv("QT_QML_NO_GREP");
|
||||
|
||||
QCOMPARE(properties.size(), expectedProperties.size());
|
||||
|
||||
for (auto [key, value] : properties.asKeyValueRange()) {
|
||||
QVERIFY(expectedProperties.contains(key));
|
||||
QCOMPARE(value.size(), expectedProperties[key]);
|
||||
}
|
||||
}
|
||||
|
||||
void TestQmllint::cleanQmlCode_data()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue