Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
projectdescriptionreader.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "fmt.h"
6
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qjsonarray.h>
10#include <QtCore/qjsondocument.h>
11#include <QtCore/qjsonobject.h>
12#include <QtCore/qset.h>
13
14#include <algorithm>
15#include <functional>
16
17using std::placeholders::_1;
18using namespace Qt::Literals::StringLiterals;
19
20class Validator
21{
22public:
23 Validator(QString *errorString)
25 {
26 }
27
28 bool isValidProjectDescription(const QJsonArray &projects)
29 {
30 return std::all_of(projects.begin(), projects.end(),
31 std::bind(&Validator::isValidProjectObject, this, _1));
32 }
33
34private:
35 bool isValidProject(const QJsonObject &project)
36 {
37 static const QSet<QString> requiredKeys = {
38 QStringLiteral("projectFile"),
39 };
40 static const QSet<QString> allowedKeys
41 = QSet<QString>(requiredKeys)
42 << QStringLiteral("codec")
43 << QStringLiteral("excluded")
44 << QStringLiteral("includePaths")
45 << QStringLiteral("sources")
46 << QStringLiteral("compileCommands")
47 << QStringLiteral("subProjects")
48 << QStringLiteral("translations");
49 QSet<QString> actualKeys;
50 for (auto it = project.constBegin(), end = project.constEnd(); it != end; ++it)
51 actualKeys.insert(it.key());
52 const QSet<QString> missingKeys = requiredKeys - actualKeys;
53 if (!missingKeys.isEmpty()) {
54 *m_errorString = FMT::tr("Missing keys in project description: %1.").arg(
55 missingKeys.values().join(QLatin1String(", ")));
56 return false;
57 }
58 const QSet<QString> unexpected = actualKeys - allowedKeys;
59 if (!unexpected.isEmpty()) {
60 *m_errorString = FMT::tr("Unexpected keys in project %1: %2").arg(
61 project.value(QStringLiteral("projectFile")).toString(),
62 unexpected.values().join(QLatin1String(", ")));
63 return false;
64 }
65 return isValidProjectDescription(project.value(QStringLiteral("subProjects")).toArray());
66 }
67
68 bool isValidProjectObject(const QJsonValue &v)
69 {
70 if (!v.isObject()) {
71 *m_errorString = FMT::tr("JSON object expected.");
72 return false;
73 }
74 return isValidProject(v.toObject());
75 }
76
77 QString *m_errorString;
78};
79
80static QJsonArray readRawProjectDescription(const QString &filePath, QString *errorString)
81{
82 errorString->clear();
83 QFile file(filePath);
84 if (!file.open(QIODevice::ReadOnly)) {
85 *errorString = FMT::tr("Cannot open project description file '%1'.\n")
86 .arg(filePath);
87 return {};
88 }
89 QJsonParseError parseError;
90 QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseError);
91 if (doc.isNull()) {
92 *errorString = FMT::tr("%1 in %2 at offset %3.\n")
93 .arg(parseError.errorString(), filePath)
94 .arg(parseError.offset);
95 return {};
96 }
97 QJsonArray result = doc.isArray() ? doc.array() : QJsonArray{doc.object()};
98 Validator validator(errorString);
99 if (!validator.isValidProjectDescription(result))
100 return {};
101 return result;
102}
103
105{
106public:
107 ProjectConverter(QString *errorString)
109 {
110 }
111
112 Projects convertProjects(const QJsonArray &rawProjects)
113 {
114 Projects result;
115 result.reserve(rawProjects.size());
116 for (const QJsonValue rawProject : rawProjects) {
117 Project project = convertProject(rawProject);
118 if (!m_errorString.isEmpty())
119 break;
120 result.push_back(std::move(project));
121 }
122 return result;
123 }
124
125private:
126 Project convertProject(const QJsonValue &v)
127 {
128 if (!v.isObject())
129 return {};
130 Project result;
131 QJsonObject obj = v.toObject();
132 result.filePath = stringValue(obj, QLatin1String("projectFile"));
133 result.compileCommands = stringValue(obj, QLatin1String("compileCommands"));
134 result.codec = stringValue(obj, QLatin1String("codec"));
135 result.excluded = wildcardsToRegExes(stringListValue(obj, QLatin1String("excluded")));
136 result.includePaths = stringListValue(obj, QLatin1String("includePaths"));
137 result.sources = stringListValue(obj, QLatin1String("sources"));
138 if (obj.contains(QLatin1String("translations")))
139 result.translations = stringListValue(obj, QLatin1String("translations"));
140 result.subProjects = convertProjects(obj.value(QLatin1String("subProjects")).toArray());
141 return result;
142 }
143
144 bool checkType(const QJsonValue &v, QJsonValue::Type t, const QString &key)
145 {
146 if (v.type() == t)
147 return true;
148 m_errorString = FMT::tr("Key %1 should be %2 but is %3.").arg(key, jsonTypeName(t),
149 jsonTypeName(v.type()));
150 return false;
151 }
152
153 static QString jsonTypeName(QJsonValue::Type t)
154 {
155 // ### If QJsonValue::Type was declared with Q_ENUM we could just query QMetaEnum.
156 switch (t) {
157 case QJsonValue::Null:
158 return QStringLiteral("null");
159 case QJsonValue::Bool:
160 return QStringLiteral("bool");
161 case QJsonValue::Double:
162 return QStringLiteral("double");
163 case QJsonValue::String:
164 return QStringLiteral("string");
165 case QJsonValue::Array:
166 return QStringLiteral("array");
167 case QJsonValue::Object:
168 return QStringLiteral("object");
169 case QJsonValue::Undefined:
170 return QStringLiteral("undefined");
171 }
172 return QStringLiteral("unknown");
173 }
174
175 static QVector<QRegularExpression> wildcardsToRegExes(const QStringList &wildcardPatterns)
176 {
177 QVector<QRegularExpression> result;
178 result.reserve(wildcardPatterns.size());
179 for (const QString &wildcardPattern : wildcardPatterns)
180 result.append(wildcardToRegEx(wildcardPattern));
181 return result;
182 }
183
184 // Return a QRegularExpression object for a TR_EXCLUDE / QT_EXCLUDE_SOURCES_FROM_TRANSLATION
185 // wildcard pattern. The regular expression is only anchored at the beginning to allow matching
186 // subdirectories.
187 static QRegularExpression wildcardToRegEx(const QString &wildcardPattern)
188 {
189 return QRegularExpression(
190 "\\A"_L1
191 + QRegularExpression::wildcardToRegularExpression(
192 wildcardPattern,
193 QRegularExpression::UnanchoredWildcardConversion));
194 }
195
196 QString stringValue(const QJsonObject &obj, const QString &key)
197 {
198 if (!m_errorString.isEmpty())
199 return {};
200 QJsonValue v = obj.value(key);
201 if (v.isUndefined())
202 return {};
203 if (!checkType(v, QJsonValue::String, key))
204 return {};
205 return v.toString();
206 }
207
208 QStringList stringListValue(const QJsonObject &obj, const QString &key)
209 {
210 if (!m_errorString.isEmpty())
211 return {};
212 QJsonValue v = obj.value(key);
213 if (v.isUndefined())
214 return {};
215 if (!checkType(v, QJsonValue::Array, key))
216 return {};
217 return toStringList(v, key);
218 }
219
220 QStringList toStringList(const QJsonValue &v, const QString &key)
221 {
222 QStringList result;
223 const QJsonArray a = v.toArray();
224 result.reserve(a.count());
225 for (const QJsonValue v : a) {
226 if (!v.isString()) {
227 m_errorString = FMT::tr("Unexpected type %1 in string array in key %2.")
228 .arg(jsonTypeName(v.type()), key);
229 return {};
230 }
231 result.append(v.toString());
232 }
233 return result;
234 }
235
236 QString &m_errorString;
237};
238
239Projects readProjectDescription(const QString &filePath, QString *errorString)
240{
241 const QJsonArray rawProjects = readRawProjectDescription(filePath, errorString);
242 if (!errorString->isEmpty())
243 return {};
244 ProjectConverter converter(errorString);
245 Projects result = converter.convertProjects(rawProjects);
246 if (!errorString->isEmpty())
247 return {};
248 return result;
249}
ProjectConverter(QString *errorString)
Projects convertProjects(const QJsonArray &rawProjects)
static QJsonArray readRawProjectDescription(const QString &filePath, QString *errorString)
std::vector< Project > Projects
Projects readProjectDescription(const QString &filePath, QString *errorString)
Validator(QString *errorString)
bool isValidProjectDescription(const QJsonArray &projects)