1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljscontextproperties_p.h"
5#include <QtCore/qtconfigmacros.h>
6#include <QtCore/qregularexpression.h>
7#include <QtCore/qdirlisting.h>
8#include <QtCore/qfile.h>
9
10#if QT_CONFIG(process)
11# include <QtCore/qprocess.h>
12#endif
13
14QT_BEGIN_NAMESPACE
15
16namespace QQmlJS {
17
18using namespace Qt::StringLiterals;
19
20// There are many ways to set context properties without triggering the regexp in s_pattern,
21// but its supposed to catch most context properties set via "setContextProperty".
22static constexpr QLatin1StringView s_pattern =
23 R"x((\.|->)setContextProperty\s*\(\s*(QStringLiteral\s*\(|QString\s*\(|QLatin1String(View)?\s*\(|u)?\s*"([^"]*)")x"_L1;
24static constexpr int s_contextPropertyNameIdxInPattern = 4;
25
26// TODO: use a central list of file extensions that can also be used by qmetatypesjsonprocessor.cpp
27// (that needs header file extensions) and Qt6QmlMacros.cmake.
28static constexpr std::array s_fileFilters = {
29 "*.cpp"_L1, "*.cxx"_L1, "*.cc"_L1, "*.c"_L1, "*.c++"_L1,
30 "*.hpp"_L1, "*.hxx"_L1, "*.hh"_L1, "*.h"_L1, "*.h++"_L1,
31};
32
33static const QRegularExpression s_matchSetContextProperty{ s_pattern,
34 QRegularExpression::MultilineOption };
35
36static void collectAllFromFile(const QString &filePath, ContextProperties *output)
37{
38 Q_ASSERT(output);
39
40 QFile file(filePath);
41 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text))
42 return;
43
44 const QString fileContent = QString::fromUtf8(ba: file.readAll());
45 for (const auto &match : s_matchSetContextProperty.globalMatch(subject: fileContent)) {
46 const quint32 offset = match.capturedStart(nth: s_contextPropertyNameIdxInPattern);
47 const quint32 length = match.capturedLength(nth: s_contextPropertyNameIdxInPattern);
48 const auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(text: fileContent, offset);
49 const QQmlJS::SourceLocation sourceLocation{ offset, length, row, column };
50
51 (*output)[match.captured(nth: s_contextPropertyNameIdxInPattern)].append(
52 t: ContextProperty{ .filename: filePath, .location: sourceLocation });
53 }
54}
55
56static ContextProperties grepFallback(const QList<QString> &rootUrls)
57{
58 ContextProperties result;
59
60 const QStringList fileFilters{ s_fileFilters.begin(), s_fileFilters.end() };
61
62 for (const QString &url : rootUrls) {
63 for (const auto &dirEntry : QDirListing{ url, fileFilters,
64 QDirListing::IteratorFlag::Recursive
65 | QDirListing::IteratorFlag::FilesOnly }) {
66
67 const QString filePath = dirEntry.filePath();
68 collectAllFromFile(filePath, output: &result);
69 }
70 }
71
72 return result;
73}
74
75#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
76static ContextProperties parseGrepOutput(const QString &output)
77{
78 ContextProperties result;
79
80 for (const auto line : QStringTokenizer{ output, "\n"_L1, Qt::SkipEmptyParts })
81 collectAllFromFile(filePath: line.toString(), output: &result);
82
83 return result;
84}
85#endif
86
87/*!
88 \internal
89 Uses grep to find files that have setContextProperty()-calls, and then search matching files
90 with QRegularExpression to extract the location and name of the found context properties.
91*/
92ContextProperties ContextProperty::collectAllFrom(const QList<QString> &rootUrls)
93{
94#if QT_CONFIG(process) && !defined(Q_OS_WINDOWS)
95 if (qEnvironmentVariableIsSet(varName: "QT_QML_NO_GREP"))
96 return grepFallback(rootUrls);
97
98 QProcess grep;
99 QStringList arguments{ "--recursive"_L1,
100 "--null-data"_L1, // match multiline patterns
101 "--files-with-matches"_L1,
102 "--extended-regexp"_L1, // the pattern is "extended"
103 "-e"_L1,
104 s_pattern };
105
106 // don't search non-cpp files
107 for (const auto fileFilter : s_fileFilters)
108 arguments << "--include"_L1 << fileFilter;
109
110 arguments.append(l: rootUrls);
111 grep.start(program: "grep"_L1, arguments);
112 grep.waitForFinished();
113 if (grep.exitStatus() == QProcess::NormalExit && grep.exitCode() == 0) {
114 const QString output = QString::fromUtf8(ba: grep.readAllStandardOutput());
115 return parseGrepOutput(output);
116 }
117#endif
118 return grepFallback(rootUrls);
119}
120
121bool ContextProperty::isWarningEnabled(const QList<QQmlJS::LoggerCategory> &categories)
122{
123 auto it = std::find_if(
124 first: categories.cbegin(), last: categories.cend(),
125 pred: [](const QQmlJS::LoggerCategory &c) { return c.id() == qmlContextProperties.name(); });
126
127 return it != categories.cend() && !it->isIgnored();
128}
129
130} // namespace QQmlJS
131
132QT_END_NAMESPACE
133

source code of qtdeclarative/src/qmlcompiler/qqmljscontextproperties.cpp