| 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(ch: 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 | |