diff --git a/src/indexer/katehighlightingindexer.cpp b/src/indexer/katehighlightingindexer.cpp --- a/src/indexer/katehighlightingindexer.cpp +++ b/src/indexer/katehighlightingindexer.cpp @@ -153,46 +153,97 @@ class KeywordChecker { public: - KeywordChecker(const QString &filename) - : m_filename(filename) - {} - - void processElement(QXmlStreamReader &xml) + void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml) { if (xml.name() == QLatin1String("list")) { const QString name = xml.attributes().value(QLatin1String("name")).toString(); - if (m_existingNames.contains(name)) { - qWarning() << m_filename << "list duplicate:" << name; + auto & keyword = m_keywordMap[hlName]; + keyword.filename = hlFilename; + if (name.indexOf(QStringLiteral("##")) != -1) { + qWarning() << hlFilename << "line" << xml.lineNumber() << "'##' is reserved inside a list name:" << name; + m_success = false; } - m_existingNames.insert(name); + if (keyword.existingNames.contains(name)) { + qWarning() << hlFilename << "line" << xml.lineNumber() << "list duplicate:" << name; + m_success = false; + } + keyword.existingNames.insert(name); } else if (xml.name() == QLatin1String("keyword")) { - const QString context = xml.attributes().value(QLatin1String("String")).toString(); - if (!context.isEmpty()) - m_usedNames.insert(context); + QString context = xml.attributes().value(QLatin1String("String")).toString(); + QString language; + + const int idx = context.indexOf(QStringLiteral("##")); + if (idx >= 0) { + language = context.mid(idx+2); + context = context.left(idx); + if (language.isEmpty()) { + qWarning() << hlFilename << "line" << xml.lineNumber() << "empty language name"; + m_success = false; + } + } + + auto & keyword = m_keywordMap[hlName]; + keyword.filename = hlFilename; + + if (context.isEmpty()) { + qWarning() << hlFilename << "line" << xml.lineNumber() << "empty keyword name"; + m_success = false; + } else if (language.isEmpty()) { + keyword.usedNames.insert(context); + } else { + keyword.languageMap[language].insert(context); + } } } bool check() const { - bool success = true; - const auto invalidNames = m_usedNames - m_existingNames; - if (!invalidNames.isEmpty()) { - qWarning() << m_filename << "Reference of non-existing keyword list:" << invalidNames; - success = false; - } + bool success = m_success; - const auto unusedNames = m_existingNames - m_usedNames; - if (!unusedNames.isEmpty()) { - qWarning() << m_filename << "Unused keyword lists:" << unusedNames; + for (auto &keyword : m_keywordMap) { + const auto invalidNames = keyword.usedNames - keyword.existingNames; + if (!invalidNames.isEmpty()) { + qWarning() << keyword.filename << "Reference of non-existing keyword list:" << invalidNames; + success = false; + } + + const auto unusedNames = keyword.existingNames - keyword.usedNames; + if (!unusedNames.isEmpty()) { + qWarning() << keyword.filename << "Unused keyword lists:" << unusedNames; + success = false; + } + + QMapIterator> language(keyword.languageMap); + while (language.hasNext()) { + language.next(); + auto &languageName = language.key(); + auto referencedKeyword = m_keywordMap.find(languageName); + if (referencedKeyword == m_keywordMap.end()) { + qWarning() << keyword.filename << "Unknown language name in the keyword rule:" << languageName; + success = false; + } else { + const auto invalidNames = language.value() - referencedKeyword->existingNames; + if (!invalidNames.isEmpty()) { + qWarning() << keyword.filename << "Reference of non-existing keyword list:" << invalidNames << "with" << languageName << "language"; + success = false; + } + } + } } return success; } private: - QString m_filename; - QSet m_usedNames; - QSet m_existingNames; + struct Keyword + { + QString filename; + QSet usedNames; + QSet existingNames; + QMap> languageMap; + }; + QHash m_keywordMap; + bool m_success = true; }; /** @@ -411,6 +462,7 @@ // index all given highlightings ContextChecker contextChecker; + KeywordChecker keywordChecker; QVariantMap hls; int anyError = 0; foreach (const QString &hlFilename, hlFilenames) { @@ -463,7 +515,6 @@ hls[QFileInfo(hlFile).fileName()] = hl; AttributeChecker attributeChecker(hlFilename); - KeywordChecker keywordChecker(hlFilename); const QString hlName = hl[QStringLiteral("name")].toString(); // scan for broken regex or keywords with spaces @@ -476,12 +527,12 @@ // search for used/existing contexts if applicable contextChecker.processElement(hlFilename, hlName, xml); + // search for used/existing keyword lists if applicable + keywordChecker.processElement(hlFilename, hlName, xml); + // search for used/existing attributes if applicable attributeChecker.processElement(xml); - // search for used/existing keyword lists if applicable - keywordChecker.processElement(xml); - // scan for bad regex if (!checkRegularExpression(hlFilename, xml)) { anyError = 7; @@ -510,10 +561,10 @@ if (!attributeChecker.check()) { anyError = 7; } + } - if (!keywordChecker.check()) { - anyError = 7; - } + if (!keywordChecker.check()) { + anyError = 7; } if (!contextChecker.check()) diff --git a/src/lib/rule.cpp b/src/lib/rule.cpp --- a/src/lib/rule.cpp +++ b/src/lib/rule.cpp @@ -17,6 +17,7 @@ #include "rule_p.h" #include "definition_p.h" +#include "repository.h" #include "ksyntaxhighlighting_logging.h" #include "xml_p.h" @@ -544,10 +545,21 @@ return offset; if (m_keywordList.isEmpty()) { - const auto def = definition(); + auto def = definition(); Q_ASSERT(def.isValid()); - auto defData = DefinitionData::get(def); - m_keywordList = defData->keywordList(m_listName); + const auto idx = m_listName.indexOf(QLatin1String("##")); + if (idx >= 0) { + auto listName = m_listName.left(idx); + auto defName = m_listName.mid(idx + 2); + def = DefinitionData::get(def)->repo->definitionForName(defName); + auto defData = DefinitionData::get(def); + if (!defData->load()) + qCWarning(Log) << "Unresolved include rule for" << m_listName; + m_keywordList = defData->keywordList(listName); + } else { + auto defData = DefinitionData::get(def); + m_keywordList = defData->keywordList(m_listName); + } } auto newOffset = offset;