1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #ifndef CLANG_CPP_H |
5 | #define CLANG_CPP_H |
6 | |
7 | #include "lupdate.h" |
8 | #include "synchronized.h" |
9 | |
10 | #include <QtCore/qloggingcategory.h> |
11 | #include <QtCore/qregularexpression.h> |
12 | #include <QtCore/qstring.h> |
13 | |
14 | QT_WARNING_PUSH |
15 | QT_WARNING_DISABLE_MSVC(4100) |
16 | QT_WARNING_DISABLE_MSVC(4146) |
17 | QT_WARNING_DISABLE_MSVC(4267) |
18 | QT_WARNING_DISABLE_MSVC(4624) |
19 | QT_WARNING_DISABLE_GCC("-Wnonnull" ) |
20 | |
21 | #include <llvm/ADT/StringRef.h> |
22 | #include <clang/Basic/SourceLocation.h> |
23 | #include <clang/Basic/SourceManager.h> |
24 | #include <clang/Basic/FileManager.h> |
25 | |
26 | QT_WARNING_POP |
27 | |
28 | #include <vector> |
29 | #include <iostream> |
30 | #include <sstream> |
31 | |
32 | QT_BEGIN_NAMESPACE |
33 | |
34 | Q_DECLARE_LOGGING_CATEGORY(lcClang) |
35 | |
36 | inline QString toQt(llvm::StringRef str) |
37 | { |
38 | return QString::fromUtf8(utf8: str.data(), size: str.size()); |
39 | } |
40 | |
41 | #define LUPDATE_CLANG_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch)) |
42 | #define LUPDATE_CLANG_VERSION LUPDATE_CLANG_VERSION_CHECK(LUPDATE_CLANG_VERSION_MAJOR, \ |
43 | LUPDATE_CLANG_VERSION_MINOR, LUPDATE_CLANG_VERSION_PATCH) |
44 | |
45 | // Local storage of translation information (information from the AST and linguist side) |
46 | struct TranslationRelatedStore |
47 | { |
48 | QString callType; |
49 | QString rawCode; |
50 | QString funcName; |
51 | qint64 locationCol = -1; |
52 | QString contextArg; |
53 | QString contextRetrieved; |
54 | QString lupdateSource; |
55 | QString lupdateLocationFile; |
56 | QString lupdateInputFile; // file associated to the running of the tool |
57 | qint64 lupdateLocationLine = -1; |
58 | QString lupdateId; |
59 | QString lupdateSourceWhenId; |
60 | QString lupdateIdMetaData; |
61 | QString lupdateMagicMetaData; |
62 | QHash<QString, QString> lupdateAllMagicMetaData; |
63 | QString ; |
64 | QString ; |
65 | QString lupdatePlural; |
66 | QString lupdateWarning; |
67 | clang::SourceLocation sourceLocation; |
68 | |
69 | bool isValid(bool printwarning = false) |
70 | { |
71 | switch (trFunctionAliasManager.trFunctionByName(trFunctionName: funcName)) { |
72 | // only one argument: the source |
73 | case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: |
74 | if (contextArg.isEmpty()) { |
75 | if (printwarning) { |
76 | std::stringstream warning; |
77 | warning << qPrintable(lupdateLocationFile) << ":" |
78 | << lupdateLocationLine << ":" |
79 | << locationCol << ": " |
80 | << " \'" << qPrintable(funcName) |
81 | << "\' cannot be called without context." |
82 | << " The call is ignored." <<std::endl; |
83 | lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
84 | } |
85 | return false; |
86 | } |
87 | break; |
88 | case TrFunctionAliasManager::Function_tr: |
89 | case TrFunctionAliasManager::Function_trUtf8: |
90 | if (lupdateSource.isEmpty()) { |
91 | if (printwarning) { |
92 | std::stringstream warning; |
93 | warning << qPrintable(lupdateLocationFile) << ":" |
94 | << lupdateLocationLine << ":" |
95 | << locationCol << ": " |
96 | << " \'" << qPrintable(funcName) |
97 | << "\' cannot be called without source." |
98 | << " The call is ignored." << std::endl; |
99 | lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
100 | } |
101 | return false; |
102 | } |
103 | break; |
104 | // two arguments: the context and the source |
105 | case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: |
106 | case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: |
107 | case TrFunctionAliasManager::Function_translate: |
108 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: |
109 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: |
110 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: |
111 | case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: |
112 | if (contextArg.isEmpty() || lupdateSource.isEmpty()) { |
113 | if (printwarning) { |
114 | std::stringstream warning; |
115 | warning << qPrintable(lupdateLocationFile) << ":" |
116 | << lupdateLocationLine << ":" |
117 | << locationCol << ": " |
118 | << " \'" << qPrintable(funcName) |
119 | << "\' cannot be called without context or source." |
120 | << " The call is ignored." << std::endl; |
121 | lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
122 | } |
123 | return false; |
124 | } |
125 | // not sure if the third argument is compulsory |
126 | break; |
127 | // only one argument (?) the message Id |
128 | case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: |
129 | case TrFunctionAliasManager::Function_qtTrId: |
130 | case TrFunctionAliasManager::Function_QT_TRID_NOOP: |
131 | if (lupdateId.isEmpty()) { |
132 | if (printwarning) { |
133 | std::stringstream warning; |
134 | warning << qPrintable(lupdateLocationFile) << ":" |
135 | << lupdateLocationLine << ":" |
136 | << locationCol << ": " |
137 | << " \'" << qPrintable(funcName) |
138 | << "\' cannot be called without Id." |
139 | << " The call is ignored." << std::endl; |
140 | lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
141 | } |
142 | return false; |
143 | } |
144 | break; |
145 | default: |
146 | if (funcName == QStringLiteral("TRANSLATOR" ) && lupdateComment.isEmpty()) { |
147 | if (printwarning) { |
148 | std::stringstream warning; |
149 | warning << qPrintable(lupdateLocationFile) << ":" |
150 | << lupdateLocationLine << ":" |
151 | << locationCol << ": " |
152 | << qPrintable(funcName) |
153 | << " cannot be called without comment." |
154 | << " The call is ignored." << std::endl; |
155 | lupdateWarning.append(s: QString::fromStdString(s: warning.str())); |
156 | } |
157 | return false; |
158 | } |
159 | } |
160 | return !lupdateLocationFile.isEmpty() && (lupdateLocationLine > -1) && (locationCol > -1); |
161 | } |
162 | |
163 | clang::SourceLocation callLocation(const clang::SourceManager &sourceManager) |
164 | { |
165 | if (sourceLocation.isInvalid()) { |
166 | auto sourceFile = sourceManager.getFileManager() |
167 | .getFile(Filename: lupdateLocationFile.toStdString()); |
168 | #if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0)) |
169 | sourceLocation = sourceManager.translateFileLineCol(SourceFile: sourceFile.get(), |
170 | Line: lupdateLocationLine, Col: locationCol); |
171 | #else |
172 | sourceLocation = sourceManager.translateFileLineCol(sourceFile, lupdateLocationLine, |
173 | locationCol); |
174 | #endif |
175 | } |
176 | return sourceLocation; |
177 | } |
178 | |
179 | void printStore() const |
180 | { |
181 | qCDebug(lcClang) << "------------------ Printing Store----------------------------------\n" ; |
182 | qCDebug(lcClang) |
183 | << "callType : " << callType << "\n" |
184 | << "rawCode : \n" << rawCode << "\n" |
185 | << "funcName : " << funcName << "\n" |
186 | << "LocationCol : " << locationCol << "\n" |
187 | << "contextArg : " << contextArg << "\n" |
188 | << "contextRetrieved : " << contextRetrieved << "\n" |
189 | << "lupdateSource : " << lupdateSource << "\n" |
190 | << "lupdateLocationFile : " << lupdateLocationFile << "\n" |
191 | << "lupdateLocationLine : " << lupdateLocationLine << "\n" |
192 | << "lupdateId : " << lupdateId << "\n" |
193 | << "lupdateIdMetaData : " << lupdateIdMetaData << "\n" |
194 | << "lupdateMagicMetaData: " << lupdateMagicMetaData << "\n" |
195 | << "lupdateComment : " << lupdateComment << "\n" |
196 | << "lupdateExtraComment : " << lupdateExtraComment << "\n" |
197 | << "lupdatePlural : " << lupdatePlural; |
198 | qCDebug(lcClang) << "-------------------------------------------------------------------\n" ; |
199 | } |
200 | }; |
201 | using TranslationStores = std::vector<TranslationRelatedStore>; |
202 | |
203 | struct Stores |
204 | { |
205 | Stores(TranslationStores &a, TranslationStores &qd, TranslationStores &qn) |
206 | : AST(a) |
207 | , QDeclareTrWithContext(qd) |
208 | , QNoopTranlsationWithContext(qn) |
209 | {} |
210 | |
211 | TranslationStores Preprocessor; |
212 | WriteSynchronizedRef<TranslationRelatedStore> AST; |
213 | WriteSynchronizedRef<TranslationRelatedStore> QDeclareTrWithContext; |
214 | WriteSynchronizedRef<TranslationRelatedStore> QNoopTranlsationWithContext; // or with warnings that need to be |
215 | //displayed in the same order, always |
216 | }; |
217 | |
218 | namespace LupdatePrivate |
219 | { |
220 | inline QString fixedLineEndings(const QString &s) |
221 | { |
222 | #ifdef Q_OS_WIN |
223 | QString result = s; |
224 | result.replace(QLatin1String("\r\n" ), QLatin1String("\n" )); |
225 | return result; |
226 | #else |
227 | return s; |
228 | #endif |
229 | } |
230 | |
231 | enum QuoteCompulsary |
232 | { |
233 | None = 0x01, |
234 | Left = 0x02, // Left quote is mandatory |
235 | Right = 0x04, // Right quote is mandatory |
236 | LeftAndRight = Left | Right // Both quotes are mandatory |
237 | }; |
238 | |
239 | /* |
240 | Removes the quotes around the lupdate extra, ID meta data, magic and |
241 | ID prefix comments and source string literals. |
242 | Depending on the given compulsory option, quotes can be unbalanced and |
243 | still some text is returned. This is to mimic the old lupdate behavior. |
244 | */ |
245 | inline QString cleanQuote(llvm::StringRef s, QuoteCompulsary quote) |
246 | { |
247 | if (s.empty()) |
248 | return {}; |
249 | s = s.trim(); |
250 | if (!s.consume_front(Prefix: "\"" ) && ((quote & Left) != 0)) |
251 | return {}; |
252 | if (!s.consume_back(Suffix: "\"" ) && ((quote & Right) != 0)) |
253 | return {}; |
254 | return fixedLineEndings(s: toQt(str: s)); |
255 | } |
256 | |
257 | /* |
258 | Removes the quotes and a possible existing string literal prefix |
259 | for a given string literal coming from the source code. Do not use |
260 | to clean the quotes around the lupdate translator specific comments. |
261 | */ |
262 | inline QString cleanQuote(const std::string &token) |
263 | { |
264 | if (token.empty()) |
265 | return {}; |
266 | |
267 | const QString string = fixedLineEndings(s: QString::fromStdString(s: token).trimmed()); |
268 | const int index = string.indexOf(c: QLatin1Char('"')); |
269 | if (index <= 0) |
270 | return LupdatePrivate::cleanQuote(s: token, quote: QuoteCompulsary::LeftAndRight); |
271 | |
272 | QRegularExpressionMatch result; |
273 | if (string.at(i: index - 1) == QLatin1Char('R')) { |
274 | static const QRegularExpression rawStringLiteral { |
275 | QStringLiteral( |
276 | "(?:\\bu8|\\b[LuU])??R\\\"([^\\(\\)\\\\ ]{0,16})\\((?<characters>.*)\\)\\1\\\"" |
277 | ), QRegularExpression::DotMatchesEverythingOption }; |
278 | result = rawStringLiteral.match(subject: string); |
279 | } else { |
280 | static const QRegularExpression stringLiteral { |
281 | QStringLiteral( |
282 | "(?:\\bu8|\\b[LuU])+?\\\"(?<characters>[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*)\\\"" |
283 | ) |
284 | }; |
285 | result = stringLiteral.match(subject: string); |
286 | } |
287 | if (result.hasMatch()) |
288 | return result.captured(QStringLiteral("characters" )); |
289 | return string; |
290 | } |
291 | } |
292 | |
293 | namespace ClangCppParser |
294 | { |
295 | void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd, |
296 | bool *fail); |
297 | |
298 | using TranslatorMessageVector = std::vector<TranslatorMessage>; |
299 | void collectMessages(TranslatorMessageVector &result, TranslationRelatedStore &store); |
300 | TranslatorMessage translatorMessage(const TranslationRelatedStore &store, |
301 | const QString &id, bool plural, bool isID, bool isWarningOnly = false); |
302 | |
303 | void correctAstTranslationContext(ReadSynchronizedRef<TranslationRelatedStore> &ast, |
304 | WriteSynchronizedRef<TranslationRelatedStore> &newAst, const TranslationStores &qDecl); |
305 | void finalize(ReadSynchronizedRef<TranslationRelatedStore> &ast, |
306 | WriteSynchronizedRef<TranslationRelatedStore> &newAst); |
307 | |
308 | bool stringContainsTranslationInformation(llvm::StringRef ba); |
309 | bool hasAliases(); |
310 | std::vector<std::string> getAliasFunctionDefinition(); |
311 | |
312 | } |
313 | |
314 | QT_END_NAMESPACE |
315 | |
316 | #endif |
317 | |