diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/designer/src/lib/CMakeLists.txt | 24 | ||||
| -rw-r--r-- | src/linguist/linguist/doc/src/linguist-manual.qdoc | 4 | ||||
| -rw-r--r-- | src/linguist/lprodump/main.cpp | 19 | ||||
| -rw-r--r-- | src/linguist/lupdate/cpp.cpp | 64 | ||||
| -rw-r--r-- | src/linguist/lupdate/cpp.h | 55 | ||||
| -rw-r--r-- | src/linguist/lupdate/cpp_clang.cpp | 2 | ||||
| -rw-r--r-- | src/linguist/lupdate/filesignificancecheck.cpp | 22 | ||||
| -rw-r--r-- | src/linguist/lupdate/filesignificancecheck.h | 9 | ||||
| -rw-r--r-- | src/linguist/lupdate/main.cpp | 17 | ||||
| -rw-r--r-- | src/linguist/lupdate/python.cpp | 49 | ||||
| -rw-r--r-- | src/linguist/shared/numerus.cpp | 1 | ||||
| -rw-r--r-- | src/linguist/shared/projectdescriptionreader.cpp | 24 | ||||
| -rw-r--r-- | src/linguist/shared/projectdescriptionreader.h | 4 | ||||
| -rw-r--r-- | src/linguist/shared/translator.cpp | 13 | ||||
| -rw-r--r-- | src/linguist/shared/translator.h | 5 | ||||
| -rw-r--r-- | src/qdoc/doc/qdoc-manual-topiccmds.qdoc | 26 | ||||
| -rw-r--r-- | src/qtattributionsscanner/jsongenerator.cpp | 3 | ||||
| -rw-r--r-- | src/qtattributionsscanner/package.h | 3 | ||||
| -rw-r--r-- | src/qtattributionsscanner/qdocgenerator.cpp | 3 | ||||
| -rw-r--r-- | src/qtattributionsscanner/scanner.cpp | 41 |
20 files changed, 278 insertions, 110 deletions
diff --git a/src/designer/src/lib/CMakeLists.txt b/src/designer/src/lib/CMakeLists.txt index a11a6bb0e..ae21ddb73 100644 --- a/src/designer/src/lib/CMakeLists.txt +++ b/src/designer/src/lib/CMakeLists.txt @@ -411,6 +411,30 @@ qt_internal_extend_target(Designer CONDITION NOT QT_BUILD_SHARED_LIBS ../../../shared/qtpropertybrowser ) +if(TARGET zstd::libzstd) + qt_internal_disable_find_package_global_promotion(zstd::libzstd) +endif() +if(TARGET zstd::libzstd_shared) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_shared) +endif() +if(TARGET zstd::libzstd_static) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_static) +endif() +if(NOT TARGET WrapZSTD::WrapZSTD) + qt_find_package(WrapZSTD 1.3 + PROVIDED_TARGETS + WrapZSTD::WrapZSTD + zstd::libzstd + zstd::libzstd_static + zstd::libzstd_shared + ) +endif() + +qt_internal_extend_target(Designer CONDITION QT_FEATURE_zstd + LIBRARIES + WrapZSTD::WrapZSTD +) + if(NOT QT_BUILD_SHARED_LIBS) # Resources: set(qtpropertybrowser_resource_files diff --git a/src/linguist/linguist/doc/src/linguist-manual.qdoc b/src/linguist/linguist/doc/src/linguist-manual.qdoc index 9309f5cc7..c6db433af 100644 --- a/src/linguist/linguist/doc/src/linguist-manual.qdoc +++ b/src/linguist/linguist/doc/src/linguist-manual.qdoc @@ -580,6 +580,10 @@ installed Qt, you can start \QL in the same way as any other application on the development host. + \note You can obtain an installer for \QL only (without the entire Qt SDK) + from \l {https://download.qt.io/linguist_releases/}. The installer is + available for Windows, and works with Qt 6. + To address issues that arise from the subtleties and complexities of human language, translators and developers may need to: diff --git a/src/linguist/lprodump/main.cpp b/src/linguist/lprodump/main.cpp index caa59992b..3d30e4a63 100644 --- a/src/linguist/lprodump/main.cpp +++ b/src/linguist/lprodump/main.cpp @@ -149,7 +149,7 @@ static QStringList getSources(const char *var, const char *vvar, const QStringLi } static QStringList getSources(const ProFileEvaluator &visitor, const QString &projectDir, - const QStringList &excludes, QMakeVfs *vfs) + QMakeVfs *vfs) { QStringList baseVPaths; baseVPaths += visitor.absolutePathValues(QLatin1String("VPATH"), projectDir); @@ -201,18 +201,6 @@ static QStringList getSources(const ProFileEvaluator &visitor, const QString &pr sourceFiles.removeDuplicates(); sourceFiles.sort(); - - for (const QString &ex : excludes) { - // TODO: take advantage of the file list being sorted - QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(ex)); - for (auto it = sourceFiles.begin(); it != sourceFiles.end(); ) { - if (rx.match(*it).hasMatch()) - it = sourceFiles.erase(it); - else - ++it; - } - } - return sourceFiles; } @@ -281,11 +269,10 @@ static QJsonObject processProject(const QString &proFile, const QStringList &tra if (!subResults.isEmpty()) setValue(result, "subProjects", subResults); } else { - const QStringList excludes = getExcludes(visitor, proPath); - const QStringList sourceFiles = getSources(visitor, proPath, excludes, vfs); + const QStringList sourceFiles = getSources(visitor, proPath, vfs); setValue(result, "includePaths", visitor.absolutePathValues(QLatin1String("INCLUDEPATH"), proPath)); - setValue(result, "excluded", excludes); + setValue(result, "excluded", getExcludes(visitor, proPath)); setValue(result, "sources", sourceFiles); } return result; diff --git a/src/linguist/lupdate/cpp.cpp b/src/linguist/lupdate/cpp.cpp index 96ef9625b..4c2075d21 100644 --- a/src/linguist/lupdate/cpp.cpp +++ b/src/linguist/lupdate/cpp.cpp @@ -5,7 +5,6 @@ #include <translator.h> #include <QtCore/QBitArray> -#include <QtCore/QStack> #include <QtCore/QTextStream> #include <QtCore/QRegularExpression> @@ -59,15 +58,6 @@ private: QBitArray m_ba; }; -struct CppParserState -{ - NamespaceList namespaces; - QStack<qsizetype> namespaceDepths; - NamespaceList functionContext; - QString functionContextUnresolved; - QString pendingContext; -}; - class CppParser : private CppParserState { public: @@ -1154,6 +1144,23 @@ void CppParser::truncateNamespaces(NamespaceList *namespaces, int length) Functions for processing include files. */ +size_t qHash(const CppParserState &s, size_t seed) +{ + seed = qHash(s.namespaces, seed); + seed = qHash(s.namespaceDepths, seed); + seed = qHash(s.functionContext, seed); + seed = qHash(s.functionContextUnresolved, seed); + seed = qHash(s.pendingContext, seed); + return seed; +} + +size_t qHash(const ResultsCacheKey &key, size_t seed) +{ + seed = qHash(key.cleanFile, seed); + seed = qHash(key.parserState, seed); + return seed; +} + IncludeCycleHash &CppFiles::includeCycles() { static IncludeCycleHash cycles; @@ -1175,9 +1182,9 @@ QSet<QString> &CppFiles::blacklistedFiles() return blacklisted; } -QSet<const ParseResults *> CppFiles::getResults(const QString &cleanFile) +QSet<const ParseResults *> CppFiles::getResults(const ResultsCacheKey &key) { - IncludeCycle * const cycle = includeCycles().value(cleanFile); + IncludeCycle * const cycle = includeCycles().value(key); if (cycle) return cycle->results; @@ -1185,16 +1192,16 @@ QSet<const ParseResults *> CppFiles::getResults(const QString &cleanFile) return QSet<const ParseResults *>(); } -void CppFiles::setResults(const QString &cleanFile, const ParseResults *results) +void CppFiles::setResults(const ResultsCacheKey &key, const ParseResults *results) { - IncludeCycle *cycle = includeCycles().value(cleanFile); + IncludeCycle *cycle = includeCycles().value(key); if (!cycle) { cycle = new IncludeCycle; - includeCycles().insert(cleanFile, cycle); + includeCycles().insert(key, cycle); } - cycle->fileNames.insert(cleanFile); + cycle->fileNames.insert(key.cleanFile); cycle->results.insert(results); } @@ -1218,14 +1225,15 @@ void CppFiles::setBlacklisted(const QString &cleanFile) blacklistedFiles().insert(cleanFile); } -void CppFiles::addIncludeCycle(const QSet<QString> &fileNames) +void CppFiles::addIncludeCycle(const QSet<QString> &fileNames, const CppParserState &parserState) { IncludeCycle * const cycle = new IncludeCycle; cycle->fileNames = fileNames; QSet<IncludeCycle *> intersectingCycles; for (const QString &fileName : fileNames) { - IncludeCycle *intersectingCycle = includeCycles().value(fileName); + const ResultsCacheKey key = { fileName, parserState }; + IncludeCycle *intersectingCycle = includeCycles().value(key); if (intersectingCycle && !intersectingCycles.contains(intersectingCycle)) { intersectingCycles.insert(intersectingCycle); @@ -1237,7 +1245,7 @@ void CppFiles::addIncludeCycle(const QSet<QString> &fileNames) qDeleteAll(intersectingCycles); for (const QString &fileName : std::as_const(cycle->fileNames)) - includeCycles().insert(fileName, cycle); + includeCycles().insert({ fileName, parserState }, cycle); } static bool isHeader(const QString &name) @@ -1251,29 +1259,27 @@ void CppParser::processInclude(const QString &file, ConversionData &cd, const QS { QString cleanFile = QDir::cleanPath(file); - for (const QString &ex : std::as_const(cd.m_excludes)) { - QRegularExpression rx(QRegularExpression::wildcardToRegularExpression(ex)); + for (const QRegularExpression &rx : std::as_const(cd.m_excludes)) { if (rx.match(cleanFile).hasMatch()) return; } const int index = includeStack.indexOf(cleanFile); if (index != -1) { - CppFiles::addIncludeCycle(QSet<QString>(includeStack.cbegin() + index, includeStack.cend())); + CppFiles::addIncludeCycle(QSet<QString>(includeStack.cbegin() + index, includeStack.cend()), + *this); return; } - // If the #include is in any kind of namespace, has been blacklisted previously, + // If the #include has been blacklisted previously, // or is not a header file (stdc++ extensionless or *.h*), then really include // it. Otherwise it is safe to process it stand-alone and re-use the parsed // namespace data for inclusion into other files. bool isIndirect = false; - if (namespaces.size() == 1 && functionContext.size() == 1 - && functionContextUnresolved.isEmpty() && pendingContext.isEmpty() - && !CppFiles::isBlacklisted(cleanFile) + if (!CppFiles::isBlacklisted(cleanFile) && isHeader(cleanFile)) { - QSet<const ParseResults *> res = CppFiles::getResults(cleanFile); + QSet<const ParseResults *> res = CppFiles::getResults(ResultsCacheKey(cleanFile, *this)); if (!res.isEmpty()) { results->includes.unite(res); return; @@ -2245,7 +2251,7 @@ const ParseResults *CppParser::recordResults(bool isHeader) results->fileId = nextFileId++; pr = results; } - CppFiles::setResults(yyFileName, pr); + CppFiles::setResults(ResultsCacheKey(yyFileName, *this), pr); return pr; } else { delete results; @@ -2258,7 +2264,7 @@ void loadCPP(Translator &translator, const QStringList &filenames, ConversionDat QStringConverter::Encoding e = cd.m_sourceIsUtf16 ? QStringConverter::Utf16 : QStringConverter::Utf8; for (const QString &filename : filenames) { - if (!CppFiles::getResults(filename).isEmpty() || CppFiles::isBlacklisted(filename)) + if (!CppFiles::getResults(ResultsCacheKey(filename)).isEmpty() || CppFiles::isBlacklisted(filename)) continue; QFile file(filename); diff --git a/src/linguist/lupdate/cpp.h b/src/linguist/lupdate/cpp.h index ca3cd72f9..8f6497195 100644 --- a/src/linguist/lupdate/cpp.h +++ b/src/linguist/lupdate/cpp.h @@ -7,6 +7,7 @@ #include "lupdate.h" #include <QtCore/QSet> +#include <QtCore/QStack> #include <iostream> @@ -79,19 +80,63 @@ struct IncludeCycle { QSet<const ParseResults *> results; }; -typedef QHash<QString, IncludeCycle *> IncludeCycleHash; +struct CppParserState +{ + NamespaceList namespaces; + QStack<qsizetype> namespaceDepths; + NamespaceList functionContext; + QString functionContextUnresolved; + QString pendingContext; + + bool operator==(const CppParserState &other) const + { + return namespaces == other.namespaces + && namespaceDepths == other.namespaceDepths + && functionContext == other.functionContext + && functionContextUnresolved == other.functionContextUnresolved + && pendingContext == other.pendingContext; + } +}; + +size_t qHash(const CppParserState &s, size_t seed); + +struct ResultsCacheKey +{ + const QString cleanFile; + const CppParserState parserState; + + ResultsCacheKey(const QString &filePath) + : cleanFile(filePath) + { + } + + ResultsCacheKey(const QString &filePath, const CppParserState &state) + : cleanFile(filePath), + parserState(state) + { + } + + bool operator==(const ResultsCacheKey &other) const + { + return cleanFile == other.cleanFile + && parserState == other.parserState; + } +}; + +size_t qHash(const ResultsCacheKey &key, size_t seed); + +typedef QHash<ResultsCacheKey, IncludeCycle *> IncludeCycleHash; typedef QHash<QString, const Translator *> TranslatorHash; class CppFiles { - public: - static QSet<const ParseResults *> getResults(const QString &cleanFile); - static void setResults(const QString &cleanFile, const ParseResults *results); + static QSet<const ParseResults *> getResults(const ResultsCacheKey &key); + static void setResults(const ResultsCacheKey &key, const ParseResults *results); static const Translator *getTranslator(const QString &cleanFile); static void setTranslator(const QString &cleanFile, const Translator *results); static bool isBlacklisted(const QString &cleanFile); static void setBlacklisted(const QString &cleanFile); - static void addIncludeCycle(const QSet<QString> &fileNames); + static void addIncludeCycle(const QSet<QString> &fileNames, const CppParserState &parserState); private: static IncludeCycleHash &includeCycles(); diff --git a/src/linguist/lupdate/cpp_clang.cpp b/src/linguist/lupdate/cpp_clang.cpp index 180bbb4cb..1900e284c 100644 --- a/src/linguist/lupdate/cpp_clang.cpp +++ b/src/linguist/lupdate/cpp_clang.cpp @@ -402,7 +402,7 @@ void ClangCppParser::loadCPP(Translator &translator, const QStringList &files, C { FileSignificanceCheck::create(); auto cleanup = qScopeGuard(FileSignificanceCheck::destroy); - FileSignificanceCheck::the()->setExclusionPatterns(cd.m_excludes); + FileSignificanceCheck::the()->setExclusionRegExes(cd.m_excludes); if (cd.m_rootDirs.size() > 0) FileSignificanceCheck::the()->setRootDirectories(cd.m_rootDirs); else diff --git a/src/linguist/lupdate/filesignificancecheck.cpp b/src/linguist/lupdate/filesignificancecheck.cpp index 9babe6898..65ae303f7 100644 --- a/src/linguist/lupdate/filesignificancecheck.cpp +++ b/src/linguist/lupdate/filesignificancecheck.cpp @@ -3,8 +3,6 @@ #include "filesignificancecheck.h" -#include <mutex> - QT_BEGIN_NAMESPACE FileSignificanceCheck *FileSignificanceCheck::m_instance = nullptr; @@ -17,12 +15,9 @@ void FileSignificanceCheck::setRootDirectories(const QStringList &paths) m_rootDirs[i].setPath(paths.at(i)); } -void FileSignificanceCheck::setExclusionPatterns(const QStringList &patterns) +void FileSignificanceCheck::setExclusionRegExes(const QVector<QRegularExpression> &expressions) { - const size_t patternsSize = static_cast<size_t>(patterns.size()); - m_exclusionRegExes.resize(patternsSize); - for (size_t i = 0; i < patternsSize; ++i) - m_exclusionRegExes[i] = QRegularExpression::fromWildcard(patterns.at(i)); + m_exclusionRegExes = expressions; } /* @@ -37,14 +32,15 @@ void FileSignificanceCheck::setExclusionPatterns(const QStringList &patterns) bool FileSignificanceCheck::isFileSignificant(const std::string &filePath) const { // cache lookup - std::shared_lock<std::shared_mutex> readLock(m_cacheMutex); - auto it = m_cache.find(filePath); - if (it != m_cache.end()) - return it->second; + { + QReadLocker locker(&m_cacheLock); + auto it = m_cache.find(filePath); + if (it != m_cache.end()) + return it->second; + } // cache miss - readLock.unlock(); - std::unique_lock<std::shared_mutex> writeLock(m_cacheMutex); + QWriteLocker locker(&m_cacheLock); QString file = QString::fromUtf8(filePath); QString cleanFile = QDir::cleanPath(file); for (const QRegularExpression &rx : m_exclusionRegExes) { diff --git a/src/linguist/lupdate/filesignificancecheck.h b/src/linguist/lupdate/filesignificancecheck.h index 15a947133..6f6590b52 100644 --- a/src/linguist/lupdate/filesignificancecheck.h +++ b/src/linguist/lupdate/filesignificancecheck.h @@ -5,10 +5,11 @@ #define FILESIGNIFICANCECHECK_H #include <QtCore/qdir.h> +#include <QtCore/qreadwritelock.h> #include <QtCore/qregularexpression.h> #include <QtCore/qstringlist.h> +#include <QtCore/qvector.h> -#include <shared_mutex> #include <string> #include <unordered_map> #include <vector> @@ -37,16 +38,16 @@ public: } void setRootDirectories(const QStringList &paths); - void setExclusionPatterns(const QStringList &patterns); + void setExclusionRegExes(const QVector<QRegularExpression> &expressions); bool isFileSignificant(const std::string &filePath) const; private: static FileSignificanceCheck *m_instance; std::vector<QDir> m_rootDirs; - std::vector<QRegularExpression> m_exclusionRegExes; + QVector<QRegularExpression> m_exclusionRegExes; mutable std::unordered_map<std::string, bool> m_cache; - mutable std::shared_mutex m_cacheMutex; + mutable QReadWriteLock m_cacheLock; }; namespace LupdatePrivate { diff --git a/src/linguist/lupdate/main.cpp b/src/linguist/lupdate/main.cpp index 0ebe43214..d83ed880a 100644 --- a/src/linguist/lupdate/main.cpp +++ b/src/linguist/lupdate/main.cpp @@ -18,6 +18,7 @@ #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QLibraryInfo> +#include <QtCore/QRegularExpression> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QTranslator> @@ -463,6 +464,21 @@ static bool readFileContent(const QString &filePath, QString *content, QString * return true; } +static void removeExcludedSources(Projects &projects) +{ + for (Project &project : projects) { + for (const QRegularExpression &rx : project.excluded) { + for (auto it = project.sources.begin(); it != project.sources.end(); ) { + if (rx.match(*it).hasMatch()) + it = project.sources.erase(it); + else + ++it; + } + } + removeExcludedSources(project.subProjects); + } +} + static QStringList getResources(const QString &resourceFile) { if (!QFile::exists(resourceFile)) @@ -1070,6 +1086,7 @@ int main(int argc, char **argv) .arg(projectDescriptionFile)); return 1; } + removeExcludedSources(projectDescription); for (Project &project : projectDescription) expandQrcFiles(project); } diff --git a/src/linguist/lupdate/python.cpp b/src/linguist/lupdate/python.cpp index 0bc3bf5e8..bcd37f8be 100644 --- a/src/linguist/lupdate/python.cpp +++ b/src/linguist/lupdate/python.cpp @@ -6,6 +6,7 @@ #include "lupdate.h" #include <QtCore/qhash.h> +#include <QtCore/qlist.h> #include <QtCore/qstring.h> #include <QtCore/qtextstream.h> #include <QtCore/qstack.h> @@ -53,8 +54,15 @@ static int yyParenDepth; static int yyLineNo; static int yyCurLineNo; -static QByteArray extraComment; -static QByteArray id; +struct ExtraComment +{ + QByteArray extraComment; + int lineNo; +}; + +static QList<ExtraComment> extraComments; + +static QList<ExtraComment> ids; QHash<QByteArray, Token> tokens = { {"None", Tok_None}, @@ -302,10 +310,10 @@ static Token getToken(StringType stringType = StringType::NoString) case '#': switch (getChar()) { case ':': - extraComment = readLine().trimmed(); + extraComments.append({readLine().trimmed(), yyCurLineNo}); break; case '=': - id = readLine().trimmed(); + ids.append({readLine().trimmed(), yyCurLineNo}); break; case EOF: return Tok_Eof; @@ -583,16 +591,24 @@ static bool parseTranslate(QByteArray *text, QByteArray *context, QByteArray *co return false; } -static inline void setMessageParameters(TranslatorMessage *message) +static void setMessageParameters(TranslatorMessage *message, + int lineNo) { - if (!extraComment.isEmpty()) { - message->setExtraComment(QString::fromUtf8(extraComment)); - extraComment.clear(); - } - if (!id.isEmpty()) { - message->setId(QString::fromUtf8(id)); - id.clear(); + // PYSIDE-2863: parseTranslate() can read past the message + // and capture extraComments intended for the next message. + // Use only extraComments for the current message. + QByteArray extraComment; + while (!extraComments.isEmpty() && extraComments.constFirst().lineNo <= lineNo) { + if (!extraComment.isEmpty()) + extraComment += ' '; + extraComment += extraComments.takeFirst().extraComment; } + + if (!extraComment.isEmpty()) + message->setExtraComment(QString::fromUtf8(extraComment)); + + while (!ids.isEmpty() && ids.constFirst().lineNo <= lineNo) + message->setId(QString::fromUtf8(ids.takeFirst().extraComment)); } static void parse(Translator &tor, ConversionData &cd, @@ -635,9 +651,10 @@ static void parse(Translator &tor, ConversionData &cd, yyTok = getToken(); break; case Tok_tr: - case Tok_trUtf8: + case Tok_trUtf8: { utf8 = true; yyTok = getToken(); + const int lineNo = yyCurLineNo; if (match(Tok_LeftParen) && matchString(&text)) { comment.clear(); bool plural = false; @@ -670,13 +687,15 @@ static void parse(Translator &tor, ConversionData &cd, QString::fromUtf8(comment), {}, yyFileName, yyLineNo, {}, TranslatorMessage::Unfinished, plural); - setMessageParameters(&message); + setMessageParameters(&message, lineNo); tor.extend(message, cd); } } + } break; case Tok_translate: { bool plural{}; + const int lineNo = yyCurLineNo; if (parseTranslate(&text, &context, &comment, &utf8, &plural) && !text.isEmpty()) { TranslatorMessage message(QString::fromUtf8(context), @@ -684,7 +703,7 @@ static void parse(Translator &tor, ConversionData &cd, QString::fromUtf8(comment), {}, yyFileName, yyLineNo, {}, TranslatorMessage::Unfinished, plural); - setMessageParameters(&message); + setMessageParameters(&message, lineNo); tor.extend(message, cd); } } diff --git a/src/linguist/shared/numerus.cpp b/src/linguist/shared/numerus.cpp index ef52bcdb6..b3bb86faf 100644 --- a/src/linguist/shared/numerus.cpp +++ b/src/linguist/shared/numerus.cpp @@ -151,6 +151,7 @@ static const QLocale::Language englishStyleLanguages[] = { QLocale::Friulian, QLocale::WesternFrisian, QLocale::Galician, + QLocale::Ganda, QLocale::Georgian, QLocale::German, QLocale::Greek, diff --git a/src/linguist/shared/projectdescriptionreader.cpp b/src/linguist/shared/projectdescriptionreader.cpp index eded3a650..8badd6584 100644 --- a/src/linguist/shared/projectdescriptionreader.cpp +++ b/src/linguist/shared/projectdescriptionreader.cpp @@ -15,6 +15,7 @@ #include <functional> using std::placeholders::_1; +using namespace Qt::Literals::StringLiterals; class Validator { @@ -131,7 +132,7 @@ private: result.filePath = stringValue(obj, QLatin1String("projectFile")); result.compileCommands = stringValue(obj, QLatin1String("compileCommands")); result.codec = stringValue(obj, QLatin1String("codec")); - result.excluded = stringListValue(obj, QLatin1String("excluded")); + result.excluded = wildcardsToRegExes(stringListValue(obj, QLatin1String("excluded"))); result.includePaths = stringListValue(obj, QLatin1String("includePaths")); result.sources = stringListValue(obj, QLatin1String("sources")); if (obj.contains(QLatin1String("translations"))) @@ -171,6 +172,27 @@ private: return QStringLiteral("unknown"); } + static QVector<QRegularExpression> wildcardsToRegExes(const QStringList &wildcardPatterns) + { + QVector<QRegularExpression> result; + result.reserve(wildcardPatterns.size()); + for (const QString &wildcardPattern : wildcardPatterns) + result.append(wildcardToRegEx(wildcardPattern)); + return result; + } + + // Return a QRegularExpression object for a TR_EXCLUDE / QT_EXCLUDE_SOURCES_FROM_TRANSLATION + // wildcard pattern. The regular expression is only anchored at the beginning to allow matching + // subdirectories. + static QRegularExpression wildcardToRegEx(const QString &wildcardPattern) + { + return QRegularExpression( + "\\A"_L1 + + QRegularExpression::wildcardToRegularExpression( + wildcardPattern, + QRegularExpression::UnanchoredWildcardConversion)); + } + QString stringValue(const QJsonObject &obj, const QString &key) { if (!m_errorString.isEmpty()) diff --git a/src/linguist/shared/projectdescriptionreader.h b/src/linguist/shared/projectdescriptionreader.h index 0222bdb42..b1af565a6 100644 --- a/src/linguist/shared/projectdescriptionreader.h +++ b/src/linguist/shared/projectdescriptionreader.h @@ -4,8 +4,10 @@ #ifndef PROJECTDESCRIPTIONREADER_H #define PROJECTDESCRIPTIONREADER_H +#include <QtCore/qregularexpression.h> #include <QtCore/qstring.h> #include <QtCore/qstringlist.h> +#include <QtCore/qvector.h> #include <optional> #include <vector> @@ -19,7 +21,7 @@ struct Project QString filePath; QString compileCommands; QString codec; - QStringList excluded; + QVector<QRegularExpression> excluded; QStringList includePaths; QStringList sources; Projects subProjects; diff --git a/src/linguist/shared/translator.cpp b/src/linguist/shared/translator.cpp index b52b8a253..46f4a067f 100644 --- a/src/linguist/shared/translator.cpp +++ b/src/linguist/shared/translator.cpp @@ -19,7 +19,6 @@ #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QLocale> -#include <QtCore/QRegularExpression> #include <QtCore/QTextStream> #include <private/qtranslator_p.h> @@ -339,10 +338,14 @@ void Translator::languageAndTerritory(QStringView languageCode, QLocale::Languag { QLocale::Language language = QLocale::AnyLanguage; QLocale::Territory territory = QLocale::AnyTerritory; - const auto underScore = languageCode.indexOf(u'_'); // "de_DE" - if (underScore != -1) { - language = QLocale::codeToLanguage(languageCode.left(underScore)); - territory = QLocale::codeToTerritory(languageCode.mid(underScore + 1)); + auto separator = languageCode.indexOf(u'_'); // "de_DE" + if (separator == -1) { + // compatibility with older .ts files + separator = languageCode.indexOf(u'-'); // "de-DE" + } + if (separator != -1) { + language = QLocale::codeToLanguage(languageCode.left(separator)); + territory = QLocale::codeToTerritory(languageCode.mid(separator + 1)); } else { language = QLocale::codeToLanguage(languageCode); territory = QLocale(language).territory(); diff --git a/src/linguist/shared/translator.h b/src/linguist/shared/translator.h index 4bc88c7df..1fe01cc27 100644 --- a/src/linguist/shared/translator.h +++ b/src/linguist/shared/translator.h @@ -12,9 +12,10 @@ #include <QList> #include <QLocale> #include <QMultiHash> +#include <QRegularExpression> #include <QString> #include <QSet> - +#include <QVector> QT_BEGIN_NAMESPACE @@ -53,7 +54,7 @@ public: QString m_sourceFileName; QString m_targetFileName; QString m_compilationDatabaseDir; - QStringList m_excludes; + QVector<QRegularExpression> m_excludes; QDir m_sourceDir; QDir m_targetDir; // FIXME: TS specific QSet<QString> m_projectRoots; diff --git a/src/qdoc/doc/qdoc-manual-topiccmds.qdoc b/src/qdoc/doc/qdoc-manual-topiccmds.qdoc index 3e747a7fb..e34471b13 100644 --- a/src/qdoc/doc/qdoc-manual-topiccmds.qdoc +++ b/src/qdoc/doc/qdoc-manual-topiccmds.qdoc @@ -1247,9 +1247,8 @@ The \\qmltype command is for documenting a QML type. The command has one argument, which is the name of the QML type. - If the QML type is instantiated by a C++ class, that class must be - specified using the \l{instantiates-command} {\\instantiates} - context command. + If the QML type has an equivalent C++ class, you can specify that class + with the \l{instantiates-command}{\\instantiates} context command. \badcode * /\1! @@ -1264,9 +1263,9 @@ \1/ \endcode - Here, the \e{\\qmltype} comment includes \l{instantiates-command} - {\\instantiates} to specify that a Transform is instantiated by - the C++ class QGraphicsTransform. A \\qmltype comment should + Here, the \e{\\qmltype} comment includes \e{\\instantiates} + to specify that a Transform is the QML counterpart to the + C++ class QGraphicsTransform. A \\qmltype comment should always include a \l {since-command} {\\since} command, because all QML types are new. It should also include a \l{brief-command} {\\brief} description. If a QML type is a member of a QML type group, @@ -1377,12 +1376,13 @@ \target instantiates-command \section1 \\instantiates - The \\instantiates command is used in the \l{qmltype-command} {QML - type} comment of an elemental QML type to specify the name of the - C++ class that instantiates the QML type. + The \\instantiates command must be used in conjunction with the + \l{qmltype-command}{\\qmltype} topic command. The command takes a + C++ class as its argument. If QDoc cannot find the C++ class, it + issues a warning. - If the QML type is not instantiated by a C++ class, this command - is not used. + Use the \\instantiates command to specify what the type is called in C++. + Any one QML type can only be the counterpart of one C++ class. \badcode * /\1! @@ -1398,8 +1398,8 @@ \endcode Here, the \e{\\qmltype} comment includes \l{instantiates-command} - {\\instantiates} to specify that a Transform is instantiated by - the C++ class QGraphicsTransform. + {\\instantiates} to specify that a Transform is QGraphicsTransform + in C++. \target typealias-command \section1 \\typealias diff --git a/src/qtattributionsscanner/jsongenerator.cpp b/src/qtattributionsscanner/jsongenerator.cpp index 2a194264e..b03d17e63 100644 --- a/src/qtattributionsscanner/jsongenerator.cpp +++ b/src/qtattributionsscanner/jsongenerator.cpp @@ -31,6 +31,9 @@ static QJsonObject generate(Package package) obj.insert(u"Version"_s, package.version); obj.insert(u"DownloadLocation"_s, package.downloadLocation); + obj.insert(u"CPE"_s, QJsonArray::fromStringList(package.cpeList)); + obj.insert(u"PURL"_s, QJsonArray::fromStringList(package.purlList)); + obj.insert(u"License"_s, package.license); obj.insert(u"LicenseId"_s, package.licenseId); if (package.licenseFiles.isEmpty()) diff --git a/src/qtattributionsscanner/package.h b/src/qtattributionsscanner/package.h index 082d60f1e..765335e84 100644 --- a/src/qtattributionsscanner/package.h +++ b/src/qtattributionsscanner/package.h @@ -33,6 +33,9 @@ struct Package { QString copyrightFile; // Path to file containing copyright owners. Optional. QString copyrightFileContents; + QStringList cpeList; // List of CPE values. Optional. + QStringList purlList; // List of PURL values. Optional. + QString packageComment; // Further comments about the package. Optional. }; diff --git a/src/qtattributionsscanner/qdocgenerator.cpp b/src/qtattributionsscanner/qdocgenerator.cpp index 0e57c1106..439aa5e7e 100644 --- a/src/qtattributionsscanner/qdocgenerator.cpp +++ b/src/qtattributionsscanner/qdocgenerator.cpp @@ -130,9 +130,6 @@ static void generate(QTextStream &out, const Package &package, const QDir &baseD && package.licenseId != "NONE"_L1) { out << "\\l{https://spdx.org/licenses/" << package.licenseId << ".html}" << "{" << package.license << "}.\n\n"; - } else if (package.licenseId.startsWith("urn:dje:license:"_L1)) { - out << "\\l{https://enterprise.dejacode.com/licenses/public/" << package.licenseId.mid(16) - << "/}{" << package.license << "}.\n\n"; } else { out << package.license << ".\n\n"; } diff --git a/src/qtattributionsscanner/scanner.cpp b/src/qtattributionsscanner/scanner.cpp index f936ec0d2..f4998f194 100644 --- a/src/qtattributionsscanner/scanner.cpp +++ b/src/qtattributionsscanner/scanner.cpp @@ -212,6 +212,31 @@ static bool autoDetectLicenseFiles(Package &p) return success; } +// Tries to interpret a json value either as a string or an array of strings, and assigns the +// result to outList. Returns true on success, false on failure. On failure, it also conditionally +// prints an error. +static bool handleStringOrStringArrayJsonKey(QStringList &outList, const QString &key, + QJsonValueConstRef jsonValue, const QString &filePath, + LogLevel logLevel) +{ + if (jsonValue.isArray()) { + auto maybeStringList = toStringList(jsonValue); + if (maybeStringList) + outList = maybeStringList.value(); + } else if (jsonValue.isString()) { + outList.append(jsonValue.toString()); + } else { + if (logLevel != SilentLog) { + std::cerr << qPrintable(tr("File %1: Expected JSON array of strings or " + "string as value of %2.").arg( + QDir::toNativeSeparators(filePath), key)) + << std::endl; + } + return false; + } + return true; +} + // Transforms a JSON object into a Package object static std::optional<Package> readPackage(const QJsonObject &object, const QString &filePath, Checks checks, LogLevel logLevel) @@ -226,7 +251,7 @@ static std::optional<Package> readPackage(const QJsonObject &object, const QStri if (!iter.value().isString() && key != "QtParts"_L1 && key != "SecurityCritical"_L1 && key != "Files"_L1 && key != "LicenseFiles"_L1 && key != "Comment"_L1 - && key != "Copyright"_L1) { + && key != "Copyright"_L1 && key != "CPE"_L1 && key != "PURL"_L1) { if (logLevel != SilentLog) std::cerr << qPrintable(tr("File %1: Expected JSON string as value of %2.").arg( QDir::toNativeSeparators(filePath), key)) << std::endl; @@ -299,13 +324,25 @@ static std::optional<Package> readPackage(const QJsonObject &object, const QStri p.copyright = value; } else { if (logLevel != SilentLog) { - std::cerr << qPrintable(tr("File %1: Expected JSON array of string or" + std::cerr << qPrintable(tr("File %1: Expected JSON array of strings or " "string as value of %2.").arg( QDir::toNativeSeparators(filePath), key)) << std::endl; validPackage = false; continue; } } + } else if (key == "CPE"_L1) { + const QJsonValueConstRef jsonValue = iter.value(); + if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) { + validPackage = false; + continue; + } + } else if (key == "PURL"_L1) { + const QJsonValueConstRef jsonValue = iter.value(); + if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) { + validPackage = false; + continue; + } } else if (key == "CopyrightFile"_L1) { p.copyrightFile = QDir(directory).absoluteFilePath(value); } else if (key == "PackageComment"_L1) { |
