9#include <QCoreApplication>
13#include <QMutableMapIterator>
14#include <QRegularExpression>
18#include <QXmlStreamReader>
22#include <xercesc/framework/XMLGrammarPoolImpl.hpp>
24#include <xercesc/parsers/SAX2XMLReaderImpl.hpp>
26#include <xercesc/sax/ErrorHandler.hpp>
27#include <xercesc/sax/SAXParseException.hpp>
29#include <xercesc/util/PlatformUtils.hpp>
30#include <xercesc/util/XMLString.hpp>
31#include <xercesc/util/XMLUni.hpp>
33#include <xercesc/framework/XMLGrammarPoolImpl.hpp>
34#include <xercesc/validators/common/Grammar.hpp>
36using namespace xercesc;
55class CustomErrorHandler :
public ErrorHandler
62 CustomErrorHandler(
QString *messages)
63 : m_messages(messages)
80 enum severity { s_warning, s_error, s_fatal };
86 void warning(
const SAXParseException &e)
override
96 void error(
const SAXParseException &e)
override
106 void fatalError(
const SAXParseException &e)
override
115 void resetErrors()
override
125 void handle(
const SAXParseException &e, severity s)
128 const XMLCh *xid(e.getPublicId());
130 xid = e.getSystemId();
132 m_messages <<
QString::fromUtf16(xid) <<
":" << e.getLineNumber() <<
":" << e.getColumnNumber() <<
" " << (s == s_warning ?
"warning: " :
"error: ")
145 bool m_failed =
false;
148void init_parser(SAX2XMLReaderImpl &parser)
152 parser.setFeature(XMLUni::fgSAX2CoreNameSpaces,
true);
153 parser.setFeature(XMLUni::fgSAX2CoreNameSpacePrefixes,
true);
154 parser.setFeature(XMLUni::fgSAX2CoreValidation,
true);
158 parser.setFeature(XMLUni::fgXercesSchema,
true);
159 parser.setFeature(XMLUni::fgXercesSchemaFullChecking,
true);
160 parser.setFeature(XMLUni::fgXercesValidationErrorAsFatal,
true);
164 parser.setFeature(XMLUni::fgXercesUseCachedGrammarInParse,
true);
169 parser.setFeature(XMLUni::fgXercesLoadSchema,
false);
174 parser.setFeature(XMLUni::fgXercesHandleMultipleImports,
true);
179#include "../lib/worddelimiters_p.h"
180#include "../lib/xml_p.h"
184using KSyntaxHighlighting::WordDelimiters;
185using KSyntaxHighlighting::Xml::attrToBool;
186using Qt::operator
""_s;
193 m_currentDefinition = &*m_definitions.insert(name, Definition{});
194 m_currentDefinition->languageName =
name;
195 m_currentDefinition->filename = filename;
196 m_currentDefinition->kateVersionStr = verStr.
toString();
197 m_currentKeywords =
nullptr;
198 m_currentContext =
nullptr;
202 qWarning() << filename <<
"invalid kateversion" << verStr;
208 auto checkName = [
this, &filename](
char const *nameType,
const QString &
name) {
209 auto it = m_names.find(name);
210 if (it != m_names.end()) {
211 qWarning() << filename <<
"duplicate" << nameType <<
"with" << it.value();
214 m_names.insert(name, filename);
217 checkName(
"name", name);
218 for (
auto alternativeName : alternativeNames) {
219 checkName(
"alternative name", alternativeName);
226 if (m_currentContext) {
227 m_currentContext->rules.push_back(Context::Rule{});
228 auto &rule = m_currentContext->rules.back();
229 m_success = rule.parseElement(m_currentDefinition->filename, xml) && m_success;
230 m_currentContext->hasDynamicRule = m_currentContext->hasDynamicRule || rule.dynamic == XmlBool::True;
231 }
else if (m_currentKeywords) {
232 m_success = m_currentKeywords->items.parseElement(m_currentDefinition->filename, xml) && m_success;
233 }
else if (xml.
name() == QStringLiteral(
"context")) {
234 processContextElement(xml);
235 }
else if (xml.
name() == QStringLiteral(
"list")) {
236 processListElement(xml);
237 }
else if (xml.
name() == QStringLiteral(
"keywords")) {
238 m_success = m_currentDefinition->parseKeywords(xml) && m_success;
239 }
else if (xml.
name() == QStringLiteral(
"emptyLine")) {
240 m_success = parseEmptyLine(m_currentDefinition->filename, xml) && m_success;
241 }
else if (xml.
name() == QStringLiteral(
"itemData")) {
242 m_success = m_currentDefinition->itemDatas.parseElement(m_currentDefinition->filename, xml) && m_success;
245 if (m_currentContext && xml.
name() == QStringLiteral(
"context")) {
246 m_currentContext =
nullptr;
247 }
else if (m_currentKeywords && xml.
name() == QStringLiteral(
"list")) {
248 m_currentKeywords =
nullptr;
254 void resolveContexts()
257 while (def.hasNext()) {
259 auto &definition = def.value();
260 auto &contexts = definition.contexts;
262 if (contexts.isEmpty()) {
263 qWarning() << definition.filename <<
"has no context";
268 auto markAsUsedContext = [](ContextName &contextName) {
269 if (!contextName.stay && contextName.context) {
270 contextName.context->isOnlyIncluded =
false;
275 while (contextIt.hasNext()) {
277 auto &context = contextIt.value();
278 resolveContextName(definition, context, context.lineEndContext, context.line);
279 resolveContextName(definition, context, context.lineEmptyContext, context.line);
280 resolveContextName(definition, context, context.fallthroughContext, context.line);
281 markAsUsedContext(context.lineEndContext);
282 markAsUsedContext(context.lineEmptyContext);
283 markAsUsedContext(context.fallthroughContext);
284 for (
auto &rule : context.rules) {
285 rule.parentContext = &context;
286 resolveContextName(definition, context, rule.context, rule.line);
287 if (rule.type != Context::Rule::Type::IncludeRules) {
288 markAsUsedContext(rule.context);
289 }
else if (rule.includeAttrib == XmlBool::True && rule.context.context) {
290 rule.context.context->referencedWithIncludeAttrib =
true;
295 auto *firstContext = &*definition.contexts.find(definition.firstContextName);
296 firstContext->isOnlyIncluded =
false;
297 definition.firstContext = firstContext;
300 resolveIncludeRules();
305 bool success = m_success;
307 const auto usedContexts = extractUsedContexts();
313 while (def.hasNext()) {
315 const auto &definition = def.value();
316 const auto &filename = definition.filename;
318 auto *maxDef = maxKateVersionDefinition(definition, maxVersionByDefinitions);
319 if (maxDef != &definition) {
320 qWarning() << definition.filename <<
"depends on a language" << maxDef->languageName <<
"in version" << maxDef->kateVersionStr
321 <<
". Please, increase kateversion.";
327 success = checkKeywordsList(definition) && success;
328 success = checkContexts(definition, usedAttributeNames, ignoredAttributeNames, usedContexts, unreachableIncludedRules) && success;
331 const auto invalidNames = usedAttributeNames - definition.itemDatas.styleNames;
332 for (
const auto &styleName : invalidNames) {
333 qWarning() << filename <<
"line" << styleName.line <<
"reference of non-existing itemData attributes:" << styleName.name;
338 const auto ignoredNames = ignoredAttributeNames - usedAttributeNames;
339 for (
const auto &styleName : ignoredNames) {
340 qWarning() << filename <<
"line" << styleName.line <<
"attribute" << styleName.name
341 <<
"is never used. All uses are with lookAhead=true or <IncludeRules/>";
346 auto unusedNames = definition.itemDatas.styleNames - usedAttributeNames;
347 unusedNames -= ignoredNames;
348 for (
const auto &styleName : std::as_const(unusedNames)) {
349 qWarning() << filename <<
"line" << styleName.line <<
"unused itemData:" << styleName.name;
355 while (unreachableIncludedRuleIt.hasNext()) {
356 unreachableIncludedRuleIt.next();
357 IncludedRuleUnreachableBy &unreachableRulesBy = unreachableIncludedRuleIt.value();
358 if (unreachableRulesBy.alwaysUnreachable) {
359 auto *rule = unreachableIncludedRuleIt.key();
361 if (!rule->parentContext->isOnlyIncluded) {
367 auto &unreachableBy = unreachableRulesBy.unreachableBy;
368 unreachableBy.
erase(std::remove_if(unreachableBy.begin(),
370 [&](
const RuleAndInclude &ruleAndInclude) {
371 if (rules.contains(ruleAndInclude.rule)) {
374 rules.
insert(ruleAndInclude.rule);
377 unreachableBy.end());
381 for (
auto &ruleAndInclude : std::as_const(unreachableBy)) {
382 message += QStringLiteral(
"line ");
384 message += QStringLiteral(
" [");
385 message += ruleAndInclude.rule->parentContext->name;
386 if (rule->filename != ruleAndInclude.rule->filename) {
387 message += QStringLiteral(
" (");
388 message += ruleAndInclude.rule->filename;
391 if (ruleAndInclude.includeRules) {
392 message += QStringLiteral(
" via line ");
395 message += QStringLiteral(
"], ");
399 qWarning() << rule->filename <<
"line" << rule->line <<
"no IncludeRule can reach this rule, hidden by" << message;
421 Context *context =
nullptr;
434 if (attr.
name() != attrName) {
440 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"attribute is empty";
449 bool extractXmlBool(XmlBool &xmlBool,
const QString &attrName)
451 if (attr.
name() != attrName) {
455 xmlBool = attr.
value().
isNull() ? XmlBool::Unspecified : attrToBool(attr.
value()) ? XmlBool::True : XmlBool::False;
462 bool extractPositive(
int &positive,
const QString &attrName)
464 if (attr.
name() != attrName) {
471 if (!ok || positive < 0) {
472 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"should be a positive integer:" << attr.
value();
481 bool checkColor(
const QString &attrName)
483 if (attr.
name() != attrName) {
487 const auto value = attr.
value();
488 if (value.isEmpty() ) {
489 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"should be a color:" << value;
500 if (attr.
name() != attrName) {
508 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"must contain exactly one char:" << attr.
value();
516 bool checkIfExtracted(
bool isExtracted)
522 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"unknown attribute:" << attr.
name();
533 friend size_t qHash(
const Item &item,
size_t seed = 0)
535 return qHash(item.content, seed);
540 return item0.content == item1.content;
555 qWarning() << filename <<
"line" << line <<
"is empty:" << xml.
name();
559 if (xml.
name() == QStringLiteral(
"include")) {
560 includes.
insert({content, line});
561 }
else if (xml.
name() == QStringLiteral(
"item")) {
562 keywords.
append({content, line});
564 qWarning() << filename <<
"line" << line <<
"invalid element:" << xml.
name();
582 Parser parser{filename, xml, attr, success};
584 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name"));
586 success = parser.checkIfExtracted(isExtracted);
618 bool isDotRegex =
false;
628 XmlBool firstNonSpace{};
631 XmlBool insensitive{};
640 XmlBool includeAttrib{};
662 Context
const *parentContext =
nullptr;
668 this->filename = filename;
671 using Pair = QPair<QString, Type>;
672 static const auto pairs = {
673 Pair{QStringLiteral(
"AnyChar"), Type::AnyChar},
674 Pair{QStringLiteral(
"Detect2Chars"), Type::Detect2Chars},
675 Pair{QStringLiteral(
"DetectChar"), Type::DetectChar},
676 Pair{QStringLiteral(
"DetectIdentifier"), Type::DetectIdentifier},
677 Pair{QStringLiteral(
"DetectSpaces"), Type::DetectSpaces},
678 Pair{QStringLiteral(
"Float"), Type::Float},
679 Pair{QStringLiteral(
"HlCChar"), Type::HlCChar},
680 Pair{QStringLiteral(
"HlCHex"), Type::HlCHex},
681 Pair{QStringLiteral(
"HlCOct"), Type::HlCOct},
682 Pair{QStringLiteral(
"HlCStringChar"), Type::HlCStringChar},
683 Pair{QStringLiteral(
"IncludeRules"), Type::IncludeRules},
684 Pair{QStringLiteral(
"Int"), Type::Int},
685 Pair{QStringLiteral(
"LineContinue"), Type::LineContinue},
686 Pair{QStringLiteral(
"RangeDetect"), Type::RangeDetect},
687 Pair{QStringLiteral(
"RegExpr"), Type::RegExpr},
688 Pair{QStringLiteral(
"StringDetect"), Type::StringDetect},
689 Pair{QStringLiteral(
"WordDetect"), Type::WordDetect},
690 Pair{QStringLiteral(
"keyword"), Type::keyword},
693 for (
auto pair : pairs) {
696 bool success = parseAttributes(filename, xml);
697 success = checkMandoryAttributes(filename, xml) && success;
698 if (success && type == Type::RegExpr) {
700 static const QRegularExpression isDot(QStringLiteral(R
"(^\(?\.(?:[*+][*+?]?|[*+]|\{1\})?\$?$)"));
702 static const QRegularExpression removeParentheses(QStringLiteral(R
"(\((?:\?:)?|\))"));
705 isDotRegex = reg.contains(isDot);
708 static const QRegularExpression allSuffix(QStringLiteral(
"(?<!\\\\)[.][*][?+]?[$]?$"));
709 sanitizedString = string;
712 if (sanitizedString.
isEmpty() || sanitizedString == QStringLiteral(
"^")) {
713 sanitizedString = string;
720 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"unknown element:" << xml.
name();
730 Parser parser{filename, xml, attr, success};
733 const bool isExtracted
734 = parser.extractString(attribute, QStringLiteral(
"attribute"))
735 || parser.extractString(context.name, QStringLiteral(
"context"))
736 || parser.extractXmlBool(lookAhead, QStringLiteral(
"lookAhead"))
737 || parser.extractXmlBool(firstNonSpace, QStringLiteral(
"firstNonSpace"))
738 || parser.extractString(beginRegion, QStringLiteral(
"beginRegion"))
739 || parser.extractString(endRegion, QStringLiteral(
"endRegion"))
740 || parser.extractPositive(column, QStringLiteral(
"column"))
741 || ((
type == Type::RegExpr
742 ||
type == Type::StringDetect
743 ||
type == Type::WordDetect
744 ||
type == Type::keyword
745 ) && parser.extractXmlBool(insensitive, QStringLiteral(
"insensitive")))
746 || ((
type == Type::DetectChar
747 ||
type == Type::RegExpr
748 ||
type == Type::StringDetect
749 ||
type == Type::keyword
750 ) && parser.extractXmlBool(dynamic, QStringLiteral(
"dynamic")))
751 || ((
type == Type::RegExpr)
752 && parser.extractXmlBool(minimal, QStringLiteral(
"minimal")))
753 || ((
type == Type::DetectChar
754 ||
type == Type::Detect2Chars
755 ||
type == Type::LineContinue
756 ||
type == Type::RangeDetect
757 ) && parser.extractChar(char0, QStringLiteral(
"char")))
758 || ((
type == Type::Detect2Chars
759 ||
type == Type::RangeDetect
760 ) && parser.extractChar(char1, QStringLiteral(
"char1")))
761 || ((
type == Type::AnyChar
762 ||
type == Type::RegExpr
763 ||
type == Type::StringDetect
764 ||
type == Type::WordDetect
765 ||
type == Type::keyword
766 ) && parser.extractString(
string, QStringLiteral(
"String")))
767 || ((
type == Type::IncludeRules)
768 && parser.extractXmlBool(includeAttrib, QStringLiteral(
"includeAttrib")))
769 || ((
type == Type::Float
770 ||
type == Type::HlCHex
771 ||
type == Type::HlCOct
773 ||
type == Type::keyword
774 ||
type == Type::WordDetect
775 ) && (parser.extractString(additionalDeliminator, QStringLiteral(
"additionalDeliminator"))
776 || parser.extractString(weakDeliminator, QStringLiteral(
"weakDeliminator"))))
780 success = parser.checkIfExtracted(isExtracted);
782 if (type == Type::LineContinue && char0 ==
QLatin1Char(
'\0')) {
800 case Type::StringDetect:
801 case Type::WordDetect:
803 missingAttr =
string.
isEmpty() ? QStringLiteral(
"String") :
QString();
806 case Type::DetectChar:
807 missingAttr = !char0.
unicode() ? QStringLiteral(
"char") :
QString();
810 case Type::Detect2Chars:
811 case Type::RangeDetect:
812 missingAttr = !char0.
unicode() && !char1.
unicode() ? QStringLiteral(
"char and char1")
813 : !char0.
unicode() ? QStringLiteral(
"char")
814 : !char1.
unicode() ? QStringLiteral(
"char1")
818 case Type::IncludeRules:
819 missingAttr = context.name.isEmpty() ? QStringLiteral(
"context") :
QString();
822 case Type::DetectIdentifier:
823 case Type::DetectSpaces:
828 case Type::HlCStringChar:
830 case Type::LineContinue:
835 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute:" << missingAttr;
845 bool isOnlyIncluded =
true;
847 bool referencedWithIncludeAttrib =
false;
848 bool hasDynamicRule =
false;
851 ContextName lineEndContext;
852 ContextName lineEmptyContext;
853 ContextName fallthroughContext;
856 XmlBool fallthrough{};
857 XmlBool stopEmptyLineContextSwitchLoop{};
866 Parser parser{filename, xml, attr, success};
867 XmlBool noIndentationBasedFolding{};
870 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name"))
871 || parser.extractString(attribute, QStringLiteral(
"attribute"))
872 || parser.extractString(lineEndContext.name, QStringLiteral(
"lineEndContext"))
873 || parser.extractString(lineEmptyContext.name, QStringLiteral(
"lineEmptyContext"))
874 || parser.extractString(fallthroughContext.name, QStringLiteral(
"fallthroughContext"))
875 || parser.extractXmlBool(dynamic, QStringLiteral(
"dynamic"))
876 || parser.extractXmlBool(fallthrough, QStringLiteral(
"fallthrough"))
877 || parser.extractXmlBool(stopEmptyLineContextSwitchLoop, QStringLiteral(
"stopEmptyLineContextSwitchLoop"))
878 || parser.extractXmlBool(noIndentationBasedFolding, QStringLiteral(
"noIndentationBasedFolding"));
881 success = parser.checkIfExtracted(isExtracted);
885 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: name";
890 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: attribute";
902 Version(
int majorRevision = 0,
int minorRevision = 0)
903 : majorRevision(majorRevision)
904 , minorRevision(minorRevision)
908 bool operator<(
const Version &version)
const
910 return majorRevision <
version.majorRevision || (majorRevision ==
version.majorRevision && minorRevision <
version.minorRevision);
919 friend size_t qHash(
const Style &style,
size_t seed = 0)
921 return qHash(style.name, seed);
926 return style0.name == style1.name;
941 Parser parser{filename, xml, attr, success};
943 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name")) || parser.extractString(defStyleNum, QStringLiteral(
"defStyleNum"))
944 || parser.extractXmlBool(
boolean, QStringLiteral(
"bold")) || parser.extractXmlBool(
boolean, QStringLiteral(
"italic"))
945 || parser.extractXmlBool(
boolean, QStringLiteral(
"underline")) || parser.extractXmlBool(
boolean, QStringLiteral(
"strikeOut"))
946 || parser.extractXmlBool(
boolean, QStringLiteral(
"spellChecking")) || parser.checkColor(QStringLiteral(
"color"))
947 || parser.checkColor(QStringLiteral(
"selColor")) || parser.checkColor(QStringLiteral(
"backgroundColor"))
948 || parser.checkColor(QStringLiteral(
"selBackgroundColor"));
950 success = parser.checkIfExtracted(isExtracted);
954 const auto len = styleNames.
size();
956 if (len == styleNames.
size()) {
957 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"itemData duplicate:" <<
name;
971 const Context *firstContext =
nullptr;
973 WordDelimiters wordDelimiters;
974 Version kateVersion{};
982 wordDelimiters.append(xml.
attributes().
value(QStringLiteral(
"additionalDeliminator")));
983 wordDelimiters.remove(xml.
attributes().
value(QStringLiteral(
"weakDeliminator")));
992 m_success = context.parseElement(m_currentDefinition->filename, xml) && m_success;
993 if (m_currentDefinition->firstContextName.isEmpty()) {
994 m_currentDefinition->firstContextName = context.name;
996 if (m_currentDefinition->contexts.contains(context.name)) {
997 qWarning() << m_currentDefinition->filename <<
"line" << xml.
lineNumber() <<
"duplicate context:" << context.name;
1000 m_currentContext = &*m_currentDefinition->contexts.insert(context.name, context);
1007 m_success = keywords.parseElement(m_currentDefinition->filename, xml) && m_success;
1008 if (m_currentDefinition->keywordsList.contains(keywords.name)) {
1009 qWarning() << m_currentDefinition->filename <<
"line" << xml.
lineNumber() <<
"duplicate list:" << keywords.name;
1012 m_currentKeywords = &*m_currentDefinition->keywordsList.insert(keywords.name, keywords);
1017 auto it = maxVersionByDefinitions.
find(&definition);
1018 if (it != maxVersionByDefinitions.
end()) {
1021 auto it = maxVersionByDefinitions.
insert(&definition, &definition);
1022 for (
const auto &referencedDef : definition.referencedDefinitions) {
1023 auto *maxDef = maxKateVersionDefinition(*referencedDef, maxVersionByDefinitions);
1024 if (it.value()->kateVersion < maxDef->kateVersion) {
1025 it.value() = maxDef;
1033 void resolveIncludeRules()
1039 while (def.hasNext()) {
1041 auto &definition = def.value();
1043 while (contextIt.hasNext()) {
1045 auto ¤tContext = contextIt.value();
1046 for (
auto &rule : currentContext.rules) {
1047 if (rule.type != Context::Rule::Type::IncludeRules) {
1051 if (rule.context.stay) {
1052 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules refers to himself";
1057 if (rule.context.popCount) {
1058 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules with #pop prefix";
1062 if (!rule.context.context) {
1069 usedContexts.
clear();
1070 usedContexts.
insert(rule.context.context);
1072 contexts.
append(rule.context.context);
1074 for (
int i = 0; i < contexts.
size(); ++i) {
1075 currentContext.hasDynamicRule = contexts[i]->hasDynamicRule;
1076 for (
const auto &includedRule : contexts[i]->rules) {
1077 if (includedRule.type != Context::Rule::Type::IncludeRules) {
1078 rule.includedRules.append(&includedRule);
1079 }
else if (&rule == &includedRule) {
1080 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules refers to himself by recursivity";
1083 rule.includedIncludeRules.insert(&includedRule);
1085 if (includedRule.includedRules.isEmpty()) {
1086 const auto *context = includedRule.context.context;
1087 if (context && !usedContexts.
contains(context)) {
1088 contexts.
append(context);
1089 usedContexts.
insert(context);
1092 rule.includedRules.append(includedRule.includedRules);
1110 while (def.hasNext()) {
1112 const auto &definition = def.value();
1114 if (definition.firstContext) {
1115 usedContexts.
insert(definition.firstContext);
1117 contexts.
append(definition.firstContext);
1119 for (
int i = 0; i < contexts.
size(); ++i) {
1120 auto appendContext = [&](
const Context *context) {
1121 if (context && !usedContexts.
contains(context)) {
1122 contexts.
append(context);
1123 usedContexts.
insert(context);
1127 const auto *context = contexts[i];
1128 appendContext(context->lineEndContext.context);
1129 appendContext(context->lineEmptyContext.context);
1130 appendContext(context->fallthroughContext.context);
1132 for (
auto &rule : context->rules) {
1133 appendContext(rule.context.context);
1139 return usedContexts;
1142 struct RuleAndInclude {
1143 const Context::Rule *rule;
1144 const Context::Rule *includeRules;
1146 explicit operator bool()
const
1152 struct IncludedRuleUnreachableBy {
1154 bool alwaysUnreachable =
true;
1158 bool checkContexts(
const Definition &definition,
1164 bool success =
true;
1167 while (contextIt.hasNext()) {
1170 const auto &context = contextIt.value();
1171 const auto &filename = definition.filename;
1173 if (!usedContexts.
contains(&context)) {
1174 qWarning() << filename <<
"line" << context.line <<
"unused context:" << context.name;
1179 if (context.name.startsWith(QStringLiteral(
"#pop"))) {
1180 qWarning() << filename <<
"line" << context.line <<
"the context name must not start with '#pop':" << context.name;
1184 if (!context.attribute.isEmpty() && (!context.isOnlyIncluded || context.referencedWithIncludeAttrib)) {
1185 usedAttributeNames.
insert({context.attribute, context.line});
1188 success = checkContextAttribute(definition, context) && success;
1189 success = checkUreachableRules(definition.filename, context, unreachableIncludedRules) && success;
1190 success = suggestRuleMerger(definition.filename, context) && success;
1192 for (
const auto &rule : context.rules) {
1193 if (!rule.attribute.isEmpty()) {
1194 if (rule.lookAhead != XmlBool::True) {
1195 usedAttributeNames.
insert({rule.attribute, rule.line});
1197 ignoredAttributeNames.
insert({rule.attribute, rule.line});
1200 success = checkLookAhead(rule) && success;
1201 success = checkStringDetect(rule) && success;
1202 success = checkKeyword(definition, rule) && success;
1203 success = checkRegExpr(filename, rule, context) && success;
1204 success = checkDelimiters(definition, rule) && success;
1220 bool checkRegExpr(
const QString &filename,
const Context::Rule &rule,
const Context &context)
const
1223 if (rule.type == Context::Rule::Type::RegExpr && !rule.string.isEmpty()) {
1225 if (!checkRegularExpression(rule.filename, regexp, rule.line)) {
1230 if (rule.dynamic == XmlBool::True) {
1232 if (!rule.string.contains(placeHolder)) {
1233 qWarning() << rule.filename <<
"line" << rule.line <<
"broken regex:" << rule.string <<
"problem: dynamic=true but no %\\d+ placeholder";
1238 if (rule.lookAhead == XmlBool::True && (rule.string.endsWith(QStringLiteral(
".*$")) || rule.string.endsWith(QStringLiteral(
".*")))
1239 && -1 == rule.string.indexOf(u
'|')) {
1240 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr with lookAhead=1 doesn't need to end with '.*' or '.*$':" << rule.string;
1244 auto reg = (rule.lookAhead == XmlBool::True) ? rule.sanitizedString : rule.string;
1245 if (rule.lookAhead == XmlBool::True) {
1247 R
"(((?<!\\)\\(?:[DSWdsw]|x[0-9a-fA-F]{2}|x\{[0-9a-fA-F]+\}|0\d\d|o\{[0-7]+\}|u[0-9a-fA-F]{4})|(?<!\\)[^])}\\]|(?=\\)\\\\)[*][?+]?$)"));
1248 reg.replace(removeAllSuffix, QString());
1257 QStringLiteral(R
"(^\^?(?:\((?:\?:)?)?\^?(?:\\s|\[(?:\\s| (?:\t|\\t)|(?:\t|\\t) )\])\)?(?:[*+][*+?]?|[*+])?\)?\)?$)"));
1258 if (rule.string.contains(isDetectSpaces)) {
1259 char const *extraMsg = rule.string.contains(
QLatin1Char(
'^')) ?
"+ column=\"0\" or firstNonSpace=\"1\"" :
"";
1260 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by DetectSpaces / DetectChar / AnyChar" << extraMsg <<
":"
1265#define REG_ESCAPE_CHAR R"(\\(?:[^0BDPSWbdpswoux]|x[0-9a-fA-F]{2}|x\{[0-9a-fA-F]+\}|0\d\d|o\{[0-7]+\}|u[0-9a-fA-F]{4}))"
1266#define REG_CHAR "(?:" REG_ESCAPE_CHAR "|\\[(?:" REG_ESCAPE_CHAR "|.)\\]|[^[.^])"
1270 "\\.\\*[?+]?" REG_CHAR
"|"
1271 "\\[\\^(" REG_ESCAPE_CHAR
"|.)\\]\\*[?+]?\\1"
1273 if ((rule.lookAhead == XmlBool::True || rule.minimal == XmlBool::True || rule.string.contains(QStringLiteral(
".*?"))
1274 || rule.string.contains(QStringLiteral(
"[^")))
1275 && reg.contains(isRange)) {
1276 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by RangeDetect:" << rule.string;
1281 static const QRegularExpression isAnyChar(QStringLiteral(R
"(^(\^|\((\?:)?)*\[(?!\^)[-\]]?(\\[^0BDPSWbdpswoux]|[^-\]\\])*\]\)*$)"));
1282 if (rule.string.contains(isAnyChar)) {
1284 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by AnyChar:" << rule.string << extra;
1289 static const QRegularExpression isLineContinue(QStringLiteral(
"^\\^?" REG_CHAR
"\\$$"));
1290 if (reg.contains(isLineContinue)) {
1291 auto extra = (reg[0] ==
QLatin1Char(
'^')) ?
"with column=\"0\"" :
"";
1292 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by LineContinue:" << rule.string << extra;
1296#define REG_DIGIT uR"((\[(0-9|\\d)\]|\\d))"
1297#define REG_DIGITS REG_DIGIT u"([+]|" REG_DIGIT u"[*])"
1298#define REG_DOT uR"((\\[.]|\[.\]))"
1300 static const QRegularExpression isInt(uR
"(^(\((\?:)?)*\\b(\((\?:)?)*)" REG_DIGITS uR"(\)*$)"_s);
1301 if (reg.contains(isInt)) {
1302 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by Int:" << rule.string;
1308 uR
"(^(\\b|\((\?:)?)*)" REG_DIGITS REG_DOT
1309 REG_DIGIT u"[*][|]" REG_DOT REG_DIGITS uR
"(\)+\((\?:)?\[[eE]+\]\[(\\?-\\?\+|\\?\+\\?-)\]\?)" REG_DIGITS uR"(\)\?\)*$)"_s);
1310 if (reg.contains(isFloat)) {
1311 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by Float:" << rule.string;
1320 reg.replace(sanitize1, QStringLiteral(
"_"));
1323#undef REG_ESCAPE_CHAR
1326 static const QRegularExpression isMinimal(QStringLiteral(
"(?![.][*+?][$]?[)]*$)[.][*+?][^?+]"));
1329 if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(isMinimal) && !reg.contains(hasNotGreedy)
1330 && (!rule.context.context || !rule.context.context->hasDynamicRule || regexp.captureCount() == 0)
1332 qWarning() << rule.filename <<
"line" << rule.line
1333 <<
"RegExpr should be have minimal=\"1\" or use lazy operator (i.g, '.*' -> '.*?'):" << rule.string;
1339 reg.replace(sanitize2, QStringLiteral("___"));
1342 static const QRegularExpression sanitize3(QStringLiteral(R
"(\[(?:\^\]?[^]]*|\]?[^]\\]*?\\.[^]]*|\][^]]{2,}|[^]]{3,})\]|(\[\]?[^]]*\]))"));
1343 reg.replace(sanitize3, QStringLiteral("...\\1"));
1347 reg.replace(sanitize4, QStringLiteral("_"));
1349 const int len = reg.size();
1351 static const QRegularExpression toInsensitive(QStringLiteral(R
"(\[(?:([^]])\1)\])"));
1352 reg = reg.toUpper();
1353 reg.replace(toInsensitive, QString());
1357 static const QRegularExpression isStringDetect(QStringLiteral(R
"(^\^?(?:[^|\\?*+$^[{(.]|{(?!\d+,\d*}|,\d+})|\(\?:)+$)"));
1358 if (reg.contains(isStringDetect)) {
1359 char const *extraMsg = rule.string.contains(
QLatin1Char(
'^')) ?
"+ column=\"0\" or firstNonSpace=\"1\"" :
"";
1360 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by StringDetect / Detect2Chars / DetectChar" << extraMsg
1361 <<
":" << rule.string;
1362 if (len != reg.size()) {
1363 qWarning() << rule.filename <<
"line" << rule.line <<
"insensitive=\"1\" missing:" << rule.string;
1369 if (rule.column == -1) {
1374 auto first = std::as_const(reg).begin();
1375 auto last = std::as_const(reg).end();
1387 const int bolDepth = depth;
1390 while (++first != last) {
1395 if (depth < bolDepth) {
1397 if (first + 1 != last && QStringLiteral(
"*?").contains(first[1])) {
1404 if (depth <= bolDepth) {
1412 qWarning() << rule.filename <<
"line" << rule.line <<
"column=\"0\" missing with RegExpr:" << rule.string;
1419 if (rule.column == 0 && !rule.isDotRegex) {
1420 bool hasStartOfLine =
false;
1421 auto first = std::as_const(reg).begin();
1422 auto last = std::as_const(reg).end();
1423 for (; first != last; ++first) {
1425 hasStartOfLine =
true;
1436 if (!hasStartOfLine) {
1437 qWarning() << rule.filename <<
"line" << rule.line
1438 <<
"start of line missing in the pattern with column=\"0\" (i.e. abc -> ^abc):" << rule.string;
1443 bool useCapture =
false;
1446 if (regexp.captureCount()) {
1447 auto maximalCapture = [](
const QString(&referenceNames)[9],
const QString &s) {
1449 while (maxCapture && !s.contains(referenceNames[maxCapture - 1])) {
1455 int maxCaptureUsed = 0;
1457 if (rule.context.context && !rule.context.stay) {
1458 for (
const auto &nextRule : rule.context.context->rules) {
1459 if (nextRule.dynamic == XmlBool::True) {
1461 QStringLiteral(
"%1"),
1462 QStringLiteral(
"%2"),
1463 QStringLiteral(
"%3"),
1464 QStringLiteral(
"%4"),
1465 QStringLiteral(
"%5"),
1466 QStringLiteral(
"%6"),
1467 QStringLiteral(
"%7"),
1468 QStringLiteral(
"%8"),
1469 QStringLiteral(
"%9"),
1471 int maxDynamicCapture = maximalCapture(cap, nextRule.string);
1472 maxCaptureUsed = std::max(maxCaptureUsed, maxDynamicCapture);
1478 QStringLiteral(
"\\1"),
1479 QStringLiteral(
"\\2"),
1480 QStringLiteral(
"\\3"),
1481 QStringLiteral(
"\\4"),
1482 QStringLiteral(
"\\5"),
1483 QStringLiteral(
"\\6"),
1484 QStringLiteral(
"\\7"),
1485 QStringLiteral(
"\\8"),
1486 QStringLiteral(
"\\9"),
1489 QStringLiteral(
"\\g1"),
1490 QStringLiteral(
"\\g2"),
1491 QStringLiteral(
"\\g3"),
1492 QStringLiteral(
"\\g4"),
1493 QStringLiteral(
"\\g5"),
1494 QStringLiteral(
"\\g6"),
1495 QStringLiteral(
"\\g7"),
1496 QStringLiteral(
"\\g8"),
1497 QStringLiteral(
"\\g9"),
1499 const int maxBackReference = std::max(maximalCapture(num1, rule.string), maximalCapture(num1, rule.string));
1501 const int maxCapture = std::max(maxCaptureUsed, maxBackReference);
1503 if (maxCapture && regexp.captureCount() > maxCapture) {
1504 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr with" << regexp.captureCount() <<
"captures but only" << maxCapture
1505 <<
"are used. Please, replace '(...)' with '(?:...)':" << rule.string;
1509 useCapture = maxCapture;
1515 QStringLiteral(R
"(^(\((\?:)?|\^)*\[(\\p\{L\}|_){2}\]([+][?+]?)?\[(\\p\{N\}|\\p\{L\}|_){3}\][*][?+]?\)*$)"));
1516 if (rule.string.contains(isDetectIdentifier)) {
1517 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by DetectIdentifier:" << rule.string;
1522 if (rule.isDotRegex) {
1524 int i = &rule - context.rules.data() + 1;
1525 const bool hasColumn = (rule.column != -1);
1526 const bool hasFirstNonSpace = (rule.firstNonSpace == XmlBool::True);
1527 const bool isSpecial = (hasColumn || hasFirstNonSpace);
1528 for (; i < context.rules.size(); ++i) {
1529 auto &rule2 = context.rules[i];
1530 if (rule2.type == Context::Rule::Type::IncludeRules && isSpecial) {
1531 i = context.rules.size();
1535 const bool hasColumn2 = (rule2.column != -1);
1536 const bool hasFirstNonSpace2 = (rule2.firstNonSpace == XmlBool::True);
1537 if ((!isSpecial && !hasColumn2 && !hasFirstNonSpace2) || (hasColumn && rule.column == rule2.column)
1538 || (hasFirstNonSpace && hasFirstNonSpace2)) {
1543 auto ruleFilename = (filename == rule.filename) ?
QString() : QStringLiteral(
"in ") + rule.filename;
1544 if (i == context.rules.size()) {
1545 if (rule.lookAhead == XmlBool::True && rule.firstNonSpace != XmlBool::True && rule.column == -1 && rule.beginRegion.isEmpty()
1546 && rule.endRegion.isEmpty() && !useCapture) {
1547 qWarning() << filename <<
"context line" << context.line <<
": RegExpr line" << rule.line << ruleFilename
1548 <<
"should be replaced by fallthroughContext:" << rule.string;
1551 auto &nextRule = context.rules[i];
1552 auto nextRuleFilename = (filename == nextRule.filename) ?
QString() : QStringLiteral(
"in ") + nextRule.filename;
1553 qWarning() << filename <<
"context line" << context.line <<
"contains unreachable element line" << nextRule.line << nextRuleFilename
1554 <<
"because a dot RegExpr is used line" << rule.line << ruleFilename;
1558 static const QRegularExpression unnecessaryQuantifier1(QStringLiteral(R
"([*+?]([.][*+?]{0,2})?$)"));
1559 static const QRegularExpression unnecessaryQuantifier2(QStringLiteral(R
"([*+?]([.][*+?]{0,2})?[)]*$)"));
1560 auto &unnecessaryQuantifier = useCapture ? unnecessaryQuantifier1 : unnecessaryQuantifier2;
1561 if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(unnecessaryQuantifier)) {
1562 qWarning() << rule.filename <<
"line" << rule.line
1563 <<
"Last quantifier is not necessary (i.g., 'xyz*' -> 'xy', 'xyz+.' -> 'xyz.'):" << rule.string;
1575 bool success =
true;
1578 XmlBool casesensitive{};
1581 Parser parser{filename, xml, attr, success};
1583 const bool isExtracted =
1584 parser.extractString(pattern, QStringLiteral(
"regexpr")) || parser.extractXmlBool(casesensitive, QStringLiteral(
"casesensitive"));
1586 success = parser.checkIfExtracted(isExtracted);
1590 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: regexpr";
1604 const auto pattern = regexp.
pattern();
1608 qWarning() << filename <<
"line" << line <<
"broken regex:" << pattern <<
"problem:" << regexp.
errorString() <<
"at offset"
1614 const int azOffset = std::max(pattern.
indexOf(QStringLiteral(
"A-z")), pattern.
indexOf(QStringLiteral(
"a-Z")));
1615 if (azOffset >= 0) {
1616 qWarning() << filename <<
"line" << line <<
"broken regex:" << pattern <<
"problem: [a-Z] or [A-z] at offset" << azOffset;
1625 bool checkContextAttribute(
const Definition &definition,
const Context &context)
const
1627 bool success =
true;
1629 if (!context.fallthroughContext.name.isEmpty()) {
1630 const bool mandatoryFallthroughAttribute = definition.kateVersion < Version{5, 62};
1631 if (context.fallthrough == XmlBool::True && !mandatoryFallthroughAttribute) {
1632 qWarning() << definition.filename <<
"line" << context.line <<
"fallthrough attribute is unnecessary with kateversion >= 5.62 in context"
1635 }
else if (context.fallthrough != XmlBool::True && mandatoryFallthroughAttribute) {
1636 qWarning() << definition.filename <<
"line" << context.line
1637 <<
"fallthroughContext attribute without fallthrough=\"1\" attribute is only valid with kateversion >= 5.62 in context"
1643 if (context.stopEmptyLineContextSwitchLoop != XmlBool::Unspecified && definition.kateVersion < Version{5, 103}) {
1644 qWarning() << definition.filename <<
"line" << context.line
1645 <<
"stopEmptyLineContextSwitchLoop attribute is only valid with kateversion >= 5.103 in context" << context.name;
1653 bool checkDelimiters(
const Definition &definition,
const Context::Rule &rule)
const
1655 if (rule.additionalDeliminator.isEmpty() && rule.weakDeliminator.isEmpty()) {
1659 bool success =
true;
1661 if (definition.kateVersion < Version{5, 79}) {
1662 qWarning() << definition.filename <<
"line" << rule.line
1663 <<
"additionalDeliminator and weakDeliminator are only available since version \"5.79\". Please, increase kateversion.";
1667 for (
QChar c : rule.additionalDeliminator) {
1668 if (!definition.wordDelimiters.contains(c)) {
1673 for (
QChar c : rule.weakDeliminator) {
1674 if (definition.wordDelimiters.contains(c)) {
1679 qWarning() << rule.filename <<
"line" << rule.line <<
"unnecessary use of additionalDeliminator and/or weakDeliminator" << rule.string;
1684 bool checkKeyword(
const Definition &definition,
const Context::Rule &rule)
const
1686 if (rule.type == Context::Rule::Type::keyword) {
1687 auto it = definition.keywordsList.find(rule.string);
1688 if (it == definition.keywordsList.end()) {
1689 qWarning() << rule.filename <<
"line" << rule.line <<
"reference of non-existing keyword list:" << rule.string;
1698 bool checkLookAhead(
const Context::Rule &rule)
const
1700 if (rule.lookAhead == XmlBool::True && rule.context.stay) {
1701 qWarning() << rule.filename <<
"line" << rule.line <<
"infinite loop: lookAhead with context #stay";
1707 bool checkStringDetect(
const Context::Rule &rule)
const
1709 if (rule.type == Context::Rule::Type::StringDetect) {
1711 if (rule.dynamic == XmlBool::True) {
1713 if (!rule.string.contains(placeHolder)) {
1714 qWarning() << rule.filename <<
"line" << rule.line <<
"broken regex:" << rule.string <<
"problem: dynamic=true but no %\\d+ placeholder";
1723 bool checkKeywordsList(
const Definition &definition)
const
1725 bool success =
true;
1727 bool includeNotSupport = (definition.kateVersion < Version{5, 53});
1729 while (keywordsIt.hasNext()) {
1732 for (
const auto &include : keywordsIt.value().items.includes) {
1733 if (includeNotSupport) {
1734 qWarning() << definition.filename <<
"line" << include.line
1735 <<
"<include> is only available since version \"5.53\". Please, increase kateversion.";
1738 success = checkKeywordInclude(definition, include) && success;
1743 for (
const auto& keyword : keywordsIt.value().items.keywords) {
1744 for (
QChar c : keyword.content) {
1745 if (definition.wordDelimiters.contains(c)) {
1746 qWarning() << definition.filename <<
"line" << keyword.line <<
"keyword with delimiter:" << c <<
"in" << keyword.content;
1758 bool checkKeywordInclude(
const Definition &definition,
const Keywords::Items::Item &include)
const
1760 bool containsKeywordName =
true;
1761 int const idx = include.content.indexOf(QStringLiteral(
"##"));
1763 auto it = definition.keywordsList.find(include.content);
1764 containsKeywordName = (it != definition.keywordsList.end());
1766 auto defName = include.content.sliced(idx + 2);
1767 auto listName = include.content.sliced(0, idx);
1768 auto it = m_definitions.find(defName);
1769 if (it == m_definitions.end()) {
1770 qWarning() << definition.filename <<
"line" << include.line <<
"unknown definition in" << include.content;
1773 containsKeywordName = it->keywordsList.contains(listName);
1776 if (!containsKeywordName) {
1777 qWarning() << definition.filename <<
"line" << include.line <<
"unknown keyword name in" << include.content;
1780 return containsKeywordName;
1789 bool checkUreachableRules(
const QString &filename,
1790 const Context &context,
1793 if (context.isOnlyIncluded) {
1798 RuleAndInclude setRule(
const Context::Rule &rule,
const Context::Rule *includeRules =
nullptr)
1800 auto set = [&](RuleAndInclude &ruleAndInclude) {
1801 auto old = ruleAndInclude;
1802 ruleAndInclude = {&rule, includeRules};
1806 if (rule.firstNonSpace == XmlBool::True) {
1807 return set(firstNonSpace);
1808 }
else if (rule.column == 0) {
1809 return set(column0);
1810 }
else if (rule.column > 0) {
1811 return set(columnGreaterThan0[rule.column]);
1818 RuleAndInclude normal;
1819 RuleAndInclude column0;
1821 RuleAndInclude firstNonSpace;
1830 return m_asciiMap[c.
unicode()];
1832 auto it = m_utf8Map.find(c);
1833 return it == m_utf8Map.end() ? RuleAndInclude{
nullptr,
nullptr} : it.value();
1856 void append(
QChar c,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1859 m_asciiMap[c.
unicode()] = {&rule, includeRule};
1861 m_utf8Map[c] = {&rule, includeRule};
1866 void append(
QStringView s,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1869 append(c, rule, includeRule);
1874 RuleAndInclude m_asciiMap[127]{};
1878 struct Char4Tables {
1880 CharTable charsColumn0;
1882 CharTable charsFirstNonSpace;
1886 struct CharTableArray {
1889 CharTableArray(Char4Tables &tables,
const Context::Rule &rule)
1891 if (rule.firstNonSpace == XmlBool::True) {
1892 appendTable(tables.charsFirstNonSpace);
1895 if (rule.column == 0) {
1896 appendTable(tables.charsColumn0);
1897 }
else if (rule.column > 0) {
1898 appendTable(tables.charsColumnGreaterThan0[rule.column]);
1901 appendTable(tables.chars);
1905 void removeNonSpecialWhenSpecial()
1915 for (
int i = 0; i < m_size; ++i) {
1916 if (
auto ruleAndInclude = m_charTables[i]->
find(c)) {
1917 return ruleAndInclude;
1920 return RuleAndInclude{
nullptr,
nullptr};
1927 for (
int i = 0; i < m_size; ++i) {
1928 auto result = m_charTables[i]->find(s);
1929 if (result.
size()) {
1930 while (++i < m_size) {
1940 void append(
QChar c,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1942 for (
int i = 0; i < m_size; ++i) {
1943 m_charTables[i]->append(c, rule, includeRule);
1948 void append(
QStringView s,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1950 for (
int i = 0; i < m_size; ++i) {
1951 m_charTables[i]->append(s, rule, includeRule);
1956 void appendTable(CharTable &t)
1958 m_charTables[m_size] = &t;
1962 CharTable *m_charTables[3];
1966 struct ObservableRule {
1967 const Context::Rule *rule;
1968 const Context::Rule *includeRules;
1970 bool hasResolvedIncludeRules()
const
1972 return rule == includeRules;
1977 struct RuleIterator {
1979 : m_end(&endRule - rules.data())
1985 const Context::Rule *
next()
1988 if (m_includedRules) {
1990 if (m_i2 != m_includedRules->size()) {
1991 return (*m_includedRules)[m_i2];
1994 m_includedRules =
nullptr;
1998 while (m_i < m_end && m_rules[m_i].rule->type == Context::Rule::Type::IncludeRules) {
1999 if (!m_rules[m_i].includeRules && m_rules[m_i].rule->includedRules.size()) {
2001 m_includedRules = &m_rules[m_i].rule->includedRules;
2002 return (*m_includedRules)[m_i2];
2009 return m_rules[m_i - 1].rule;
2016 const Context::Rule *currentIncludeRules()
const
2018 return m_includedRules ? m_rules[m_i].rule : m_rules[m_i].includeRules;
2032 void append(
const Context::Rule &rule,
const Context::Rule *includedRule)
2034 auto array = extractDotRegexes(rule);
2036 *array[0] = {&rule, includedRule};
2039 *array[1] = {&rule, includedRule};
2044 RuleAndInclude
find(
const Context::Rule &rule)
2046 auto array = extractDotRegexes(rule);
2053 return RuleAndInclude{};
2057 using Array = std::array<RuleAndInclude *, 2>;
2059 Array extractDotRegexes(
const Context::Rule &rule)
2063 if (rule.firstNonSpace != XmlBool::True && rule.column == -1) {
2066 if (rule.firstNonSpace == XmlBool::True) {
2067 ret[0] = &dotRegexFirstNonSpace;
2070 if (rule.column == 0) {
2071 ret[1] = &dotRegexColumn0;
2072 }
else if (rule.column > 0) {
2073 ret[1] = &dotRegexColumnGreaterThan0[rule.column];
2080 RuleAndInclude dotRegex{};
2081 RuleAndInclude dotRegexColumn0{};
2083 RuleAndInclude dotRegexFirstNonSpace{};
2086 bool success =
true;
2089 Char4Tables detectChars;
2091 Char4Tables dynamicDetectChars;
2093 Char4Tables lineContinueChars;
2097 Rule4 hlCCharRule{};
2100 Rule4 hlCStringCharRule{};
2101 Rule4 detectIdentifierRule{};
2109 observedRules.
reserve(context.rules.size());
2110 for (
const Context::Rule &rule : context.rules) {
2111 const Context::Rule *includeRule =
nullptr;
2112 if (rule.type == Context::Rule::Type::IncludeRules) {
2113 auto *context = rule.context.context;
2114 if (context && context->isOnlyIncluded) {
2115 includeRule = &rule;
2119 observedRules.
push_back({&rule, includeRule});
2121 for (
const Context::Rule *rule2 : rule.includedRules) {
2122 observedRules.
push_back({rule2, includeRule});
2127 for (
auto &observedRule : observedRules) {
2128 const Context::Rule &rule = *observedRule.rule;
2129 bool isUnreachable =
false;
2133 auto updateUnreachable1 = [&](RuleAndInclude ruleAndInclude) {
2134 if (ruleAndInclude) {
2135 isUnreachable =
true;
2136 unreachableBy.
append(ruleAndInclude);
2142 if (!ruleAndIncludes.isEmpty()) {
2143 isUnreachable =
true;
2144 unreachableBy.
append(ruleAndIncludes);
2149 auto isCompatible = [&rule](Context::Rule
const &rule2) {
2150 return (rule2.firstNonSpace != XmlBool::True && rule2.column == -1) || (rule.column == rule2.column && rule.column != -1)
2151 || (rule.firstNonSpace == rule2.firstNonSpace && rule.firstNonSpace == XmlBool::True);
2154 updateUnreachable1(dotRegex.find(rule));
2156 switch (rule.type) {
2159 case Context::Rule::Type::AnyChar: {
2160 auto tables = CharTableArray(detectChars, rule);
2161 updateUnreachable2(tables.find(rule.string));
2162 tables.removeNonSpecialWhenSpecial();
2163 tables.append(rule.string, rule);
2169 case Context::Rule::Type::DetectChar: {
2170 auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
2171 auto tables = CharTableArray(chars4, rule);
2172 updateUnreachable1(tables.find(rule.char0));
2173 tables.removeNonSpecialWhenSpecial();
2174 tables.append(rule.char0, rule);
2180 case Context::Rule::Type::DetectSpaces: {
2181 auto tables = CharTableArray(detectChars, rule);
2182 updateUnreachable2(tables.find(QStringLiteral(
" \t")));
2183 tables.removeNonSpecialWhenSpecial();
2190 case Context::Rule::Type::HlCChar:
2191 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'\'')));
2192 updateUnreachable1(hlCCharRule.setRule(rule));
2196 case Context::Rule::Type::HlCHex:
2197 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'0')));
2198 updateUnreachable1(hlCHexRule.setRule(rule));
2202 case Context::Rule::Type::HlCOct:
2203 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'0')));
2204 updateUnreachable1(hlCOctRule.setRule(rule));
2208 case Context::Rule::Type::HlCStringChar:
2209 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'\\')));
2210 updateUnreachable1(hlCStringCharRule.setRule(rule));
2214 case Context::Rule::Type::Int:
2215 updateUnreachable2(CharTableArray(detectChars, rule).
find(QStringLiteral(
"0123456789")));
2216 updateUnreachable1(intRule.setRule(rule));
2220 case Context::Rule::Type::Float:
2221 updateUnreachable2(CharTableArray(detectChars, rule).
find(QStringLiteral(
"0123456789.")));
2222 updateUnreachable1(floatRule.setRule(rule));
2224 updateUnreachable1(Rule4(intRule).setRule(rule));
2228 case Context::Rule::Type::DetectIdentifier:
2229 updateUnreachable1(detectIdentifierRule.setRule(rule));
2233 case Context::Rule::Type::LineContinue: {
2234 updateUnreachable1(CharTableArray(detectChars, rule).
find(rule.char0));
2236 auto tables = CharTableArray(lineContinueChars, rule);
2237 updateUnreachable1(tables.find(rule.char0));
2238 tables.removeNonSpecialWhenSpecial();
2239 tables.append(rule.char0, rule);
2244 case Context::Rule::Type::Detect2Chars:
2245 case Context::Rule::Type::RangeDetect:
2246 updateUnreachable1(CharTableArray(detectChars, rule).
find(rule.char0));
2247 if (!isUnreachable) {
2248 RuleIterator ruleIterator(observedRules, observedRule);
2249 while (
const auto *rulePtr = ruleIterator.next()) {
2250 if (isUnreachable) {
2253 const auto &rule2 = *rulePtr;
2254 if (rule2.type == rule.type && isCompatible(rule2) && rule.char0 == rule2.char0 && rule.char1 == rule2.char1) {
2255 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2261 case Context::Rule::Type::RegExpr: {
2262 if (rule.isDotRegex) {
2263 dotRegex.append(rule,
nullptr);
2268 RuleIterator ruleIterator(observedRules, observedRule);
2269 while (
const auto *rulePtr = ruleIterator.next()) {
2270 if (isUnreachable) {
2273 const auto &rule2 = *rulePtr;
2274 if (rule2.type == Context::Rule::Type::RegExpr && isCompatible(rule2) && rule.insensitive == rule2.insensitive
2275 && rule.dynamic == rule2.dynamic && rule.sanitizedString.startsWith(rule2.sanitizedString)) {
2276 bool add = (rule.sanitizedString.startsWith(rule2.string) || rule.sanitizedString.size() < rule2.sanitizedString.size() + 2);
2280 auto c1 = rule.sanitizedString[rule2.sanitizedString.size()].unicode();
2281 auto c2 = rule.sanitizedString[rule2.sanitizedString.size() + 1].unicode();
2282 auto c3 = rule2.sanitizedString.back().unicode();
2283 if (c3 ==
'*' || c3 ==
'?' || c3 ==
'+') {
2285 }
else if (c1 ==
'*' || c1 ==
'?') {
2286 add = !((c2 ==
'?' || c2 ==
'+') || (rule.sanitizedString.size() >= rule2.sanitizedString.size() + 3));
2292 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2300 case Context::Rule::Type::WordDetect:
2301 case Context::Rule::Type::StringDetect: {
2303 if (rule.type == Context::Rule::Type::StringDetect && rule.dynamic == XmlBool::True) {
2304 RuleIterator ruleIterator(observedRules, observedRule);
2305 while (
const auto *rulePtr = ruleIterator.next()) {
2306 if (isUnreachable) {
2310 const auto &rule2 = *rulePtr;
2311 if (rule2.type != Context::Rule::Type::StringDetect || rule2.dynamic != XmlBool::True || !isCompatible(rule2)) {
2315 const bool isSensitive = (rule2.insensitive == XmlBool::True);
2317 if ((isSensitive || rule.insensitive != XmlBool::True) && rule.string.startsWith(rule2.string, caseSensitivity)) {
2318 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2327 if (rule.dynamic == XmlBool::True) {
2328 static const QRegularExpression dynamicPosition(QStringLiteral(R
"(^(?:[^%]*|%(?![1-9]))*)"));
2329 auto result = dynamicPosition.match(rule.string);
2330 s = s.
sliced(0, result.capturedLength());
2336 if (rule.type == Context::Rule::Type::RegExpr) {
2337 static const QRegularExpression regularChars(QStringLiteral(R
"(^(?:[^.?*+^$[{(\\|]+|\\[-.?*+^$[\]{}()\\|]+|\[[^^\\]\])+)"));
2338 static const QRegularExpression sanitizeChars(QStringLiteral(R
"(\\([-.?*+^$[\]{}()\\|])|\[([^^\\])\])"));
2339 const qsizetype result = regularChars.match(rule.string).capturedLength();
2340 const qsizetype pos = qMin(result, s.
size());
2341 if (rule.string.indexOf(
QLatin1Char(
'|'), pos) < pos) {
2342 sanitizedRegex = rule.string.
sliced(0, qMin(result, s.
size()));
2343 sanitizedRegex.
replace(sanitizeChars, QStringLiteral(
"\\1"));
2352 auto t = CharTableArray(detectChars, rule);
2353 if (rule.insensitive != XmlBool::True) {
2354 updateUnreachable1(t.find(s[0]));
2356 QChar c2[]{s[0].toLower(), s[0].toUpper()};
2362 if (s.
size() > 0 && !isUnreachable) {
2364 RuleAndInclude detect2CharsInsensitives[]{{}, {}, {}, {}};
2366 RuleIterator ruleIterator(observedRules, observedRule);
2367 while (
const auto *rulePtr = ruleIterator.next()) {
2368 if (isUnreachable) {
2371 const auto &rule2 = *rulePtr;
2372 const bool isSensitive = (rule2.insensitive == XmlBool::True);
2375 switch (rule2.type) {
2377 case Context::Rule::Type::Detect2Chars:
2378 if (isCompatible(rule2) && s.
size() >= 2) {
2379 if (rule.insensitive != XmlBool::True) {
2380 if (rule2.char0 == s[0] && rule2.char1 == s[1]) {
2381 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2386 auto set = [&](RuleAndInclude &x,
QChar c1,
QChar c2) {
2387 if (!x && rule2.char0 == c1 && rule2.char0 == c2) {
2388 x = {&rule2, ruleIterator.currentIncludeRules()};
2391 set(detect2CharsInsensitives[0], s[0].toLower(), s[1].toLower());
2392 set(detect2CharsInsensitives[1], s[0].toLower(), s[1].toUpper());
2393 set(detect2CharsInsensitives[2], s[0].toUpper(), s[1].toUpper());
2394 set(detect2CharsInsensitives[3], s[0].toUpper(), s[1].toLower());
2396 if (detect2CharsInsensitives[0] && detect2CharsInsensitives[1] && detect2CharsInsensitives[2]
2397 && detect2CharsInsensitives[3]) {
2398 isUnreachable =
true;
2399 unreachableBy.
append(detect2CharsInsensitives[0]);
2400 unreachableBy.
append(detect2CharsInsensitives[1]);
2401 unreachableBy.
append(detect2CharsInsensitives[2]);
2402 unreachableBy.
append(detect2CharsInsensitives[3]);
2409 case Context::Rule::Type::StringDetect:
2410 if (isCompatible(rule2) && rule2.dynamic != XmlBool::True && (isSensitive || rule.insensitive != XmlBool::True)
2411 && s.
startsWith(rule2.string, caseSensitivity)) {
2412 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2417 case Context::Rule::Type::WordDetect:
2418 if (rule.type == Context::Rule::Type::WordDetect && isCompatible(rule2) && (isSensitive || rule.insensitive != XmlBool::True)
2419 && 0 == rule.string.compare(rule2.string, caseSensitivity)) {
2420 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2433 case Context::Rule::Type::keyword: {
2434 RuleIterator ruleIterator(observedRules, observedRule);
2435 while (
const auto *rulePtr = ruleIterator.next()) {
2436 if (isUnreachable) {
2439 const auto &rule2 = *rulePtr;
2440 if (rule2.type == Context::Rule::Type::keyword && isCompatible(rule2) && rule.string == rule2.string) {
2441 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2453 case Context::Rule::Type::IncludeRules:
2454 if (observedRule.includeRules && !observedRule.hasResolvedIncludeRules()) {
2458 if (
auto &ruleAndInclude = includeContexts[rule.context.context]) {
2459 updateUnreachable1(ruleAndInclude);
2461 ruleAndInclude.rule = &rule;
2464 for (
const auto *rulePtr : rule.includedIncludeRules) {
2465 includeContexts.
insert(rulePtr->context.context, RuleAndInclude{rulePtr, &rule});
2468 if (observedRule.includeRules) {
2472 for (
const auto *rulePtr : rule.includedRules) {
2473 const auto &rule2 = *rulePtr;
2474 switch (rule2.type) {
2475 case Context::Rule::Type::AnyChar: {
2476 auto tables = CharTableArray(detectChars, rule2);
2477 tables.removeNonSpecialWhenSpecial();
2478 tables.append(rule2.string, rule2, &rule);
2482 case Context::Rule::Type::DetectChar: {
2483 auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
2484 auto tables = CharTableArray(chars4, rule2);
2485 tables.removeNonSpecialWhenSpecial();
2486 tables.append(rule2.char0, rule2, &rule);
2490 case Context::Rule::Type::DetectSpaces: {
2491 auto tables = CharTableArray(detectChars, rule2);
2492 tables.removeNonSpecialWhenSpecial();
2498 case Context::Rule::Type::HlCChar:
2499 hlCCharRule.setRule(rule2, &rule);
2502 case Context::Rule::Type::HlCHex:
2503 hlCHexRule.setRule(rule2, &rule);
2506 case Context::Rule::Type::HlCOct:
2507 hlCOctRule.setRule(rule2, &rule);
2510 case Context::Rule::Type::HlCStringChar:
2511 hlCStringCharRule.setRule(rule2, &rule);
2514 case Context::Rule::Type::Int:
2515 intRule.setRule(rule2, &rule);
2518 case Context::Rule::Type::Float:
2519 floatRule.setRule(rule2, &rule);
2522 case Context::Rule::Type::LineContinue: {
2523 auto tables = CharTableArray(lineContinueChars, rule2);
2524 tables.removeNonSpecialWhenSpecial();
2525 tables.append(rule2.char0, rule2, &rule);
2529 case Context::Rule::Type::RegExpr:
2530 if (rule2.isDotRegex) {
2531 dotRegex.append(rule2, &rule);
2535 case Context::Rule::Type::WordDetect:
2536 case Context::Rule::Type::StringDetect:
2537 case Context::Rule::Type::Detect2Chars:
2538 case Context::Rule::Type::IncludeRules:
2539 case Context::Rule::Type::DetectIdentifier:
2540 case Context::Rule::Type::keyword:
2541 case Context::Rule::Type::Unknown:
2542 case Context::Rule::Type::RangeDetect:
2548 case Context::Rule::Type::Unknown:
2552 if (observedRule.includeRules && !observedRule.hasResolvedIncludeRules()) {
2553 auto &unreachableIncludedRule = unreachableIncludedRules[&rule];
2554 if (isUnreachable && unreachableIncludedRule.alwaysUnreachable) {
2555 unreachableIncludedRule.unreachableBy.append(unreachableBy);
2557 unreachableIncludedRule.alwaysUnreachable =
false;
2559 }
else if (isUnreachable) {
2563 for (
auto &ruleAndInclude : unreachableBy) {
2564 message += QStringLiteral(
"line ");
2565 if (ruleAndInclude.includeRules) {
2567 message += QStringLiteral(
" [by '");
2568 message += ruleAndInclude.includeRules->context.name;
2569 message += QStringLiteral(
"' line ");
2571 if (ruleAndInclude.includeRules->filename != ruleAndInclude.rule->filename) {
2572 message += QStringLiteral(
" (");
2573 message += ruleAndInclude.rule->filename;
2580 message += QStringLiteral(
", ");
2583 qWarning() << filename <<
"line" << rule.line <<
"unreachable rule by" << message;
2593 bool suggestRuleMerger(
const QString &filename,
const Context &context)
const
2595 bool success =
true;
2597 if (context.rules.isEmpty()) {
2601 auto it = context.rules.begin();
2602 const auto end = context.rules.end() - 1;
2604 for (; it <
end; ++it) {
2606 auto &rule2 = it[1];
2608 auto isCommonCompatible = [&] {
2609 if (rule1.lookAhead != rule2.lookAhead) {
2613 if (rule1.lookAhead != XmlBool::True && rule1.attribute != rule2.attribute) {
2617 return rule1.beginRegion == rule2.beginRegion
2618 && rule1.endRegion == rule2.endRegion
2619 && rule1.firstNonSpace == rule2.firstNonSpace
2620 && rule1.context.context == rule2.context.context
2621 && rule1.context.popCount == rule2.context.popCount;
2625 switch (rule1.type) {
2627 case Context::Rule::Type::AnyChar:
2628 case Context::Rule::Type::DetectChar:
2629 if ((rule2.type == Context::Rule::Type::AnyChar || rule2.type == Context::Rule::Type::DetectChar) && isCommonCompatible()
2630 && rule1.column == rule2.column) {
2631 qWarning() << filename <<
"line" << rule2.line <<
"can be merged as AnyChar with the previous rule";
2637 case Context::Rule::Type::RegExpr:
2638 if (rule2.type == Context::Rule::Type::RegExpr && isCommonCompatible() && rule1.dynamic == rule2.dynamic
2639 && (rule1.column == rule2.column || (rule1.column <= 0 && rule2.column <= 0))) {
2640 qWarning() << filename <<
"line" << rule2.line <<
"can be merged with the previous rule";
2645 case Context::Rule::Type::DetectSpaces:
2646 case Context::Rule::Type::HlCChar:
2647 case Context::Rule::Type::HlCHex:
2648 case Context::Rule::Type::HlCOct:
2649 case Context::Rule::Type::HlCStringChar:
2650 case Context::Rule::Type::Int:
2651 case Context::Rule::Type::Float:
2652 case Context::Rule::Type::LineContinue:
2653 case Context::Rule::Type::WordDetect:
2654 case Context::Rule::Type::StringDetect:
2655 case Context::Rule::Type::Detect2Chars:
2656 case Context::Rule::Type::IncludeRules:
2657 case Context::Rule::Type::DetectIdentifier:
2658 case Context::Rule::Type::keyword:
2659 case Context::Rule::Type::Unknown:
2660 case Context::Rule::Type::RangeDetect:
2676 void resolveContextName(Definition &definition, Context &context, ContextName &contextName,
int line)
2680 contextName.stay =
true;
2683 contextName.stay =
true;
2684 contextName.context = &context;
2686 qWarning() << definition.filename <<
"line" << line <<
"invalid context in" << context.name;
2692 ++contextName.popCount;
2699 qWarning() << definition.filename <<
"line" << line <<
"'!' missing between '#pop' and context name" << context.name;
2705 const int idx =
name.
indexOf(QStringLiteral(
"##"));
2707 auto it = definition.contexts.find(
name.toString());
2708 if (it != definition.contexts.end()) {
2709 contextName.context = &*it;
2713 auto it = m_definitions.find(defName.toString());
2714 if (it != m_definitions.end()) {
2715 auto listName =
name.
sliced(0, idx).toString();
2716 definition.referencedDefinitions.insert(&*it);
2717 auto ctxIt = it->contexts.find(listName.isEmpty() ? it->firstContextName : listName);
2718 if (ctxIt != it->contexts.end()) {
2719 contextName.context = &*ctxIt;
2722 qWarning() << definition.filename <<
"line" << line <<
"unknown definition in" << context.name;
2727 if (!contextName.context) {
2728 qWarning() << definition.filename <<
"line" << line <<
"unknown context" <<
name <<
"in" << context.name;
2737 Definition *m_currentDefinition =
nullptr;
2738 Keywords *m_currentKeywords =
nullptr;
2739 Context *m_currentContext =
nullptr;
2740 bool m_success =
true;
2747 QFile file(fileName);
2754 while (!xml.
atEnd()) {
2764 qWarning() <<
"XML error while reading" << fileName <<
" - " << qPrintable(xml.
errorString()) <<
"@ offset" << xml.
characterOffset();
2782 if (extensionParts.
isEmpty()) {
2787 for (
const auto &extension : extensionParts) {
2788 for (
const auto c : extension) {
2804 qWarning() <<
"invalid character" << c <<
"seen in extensions wildcard";
2815int main(
int argc,
char *argv[])
2821 if (app.arguments().size() < 3) {
2827 XMLPlatformUtils::Initialize();
2828 auto cleanup = qScopeGuard(XMLPlatformUtils::Terminate);
2833 XMLGrammarPoolImpl xsd(XMLPlatformUtils::fgMemoryManager);
2836 SAX2XMLReaderImpl parser(XMLPlatformUtils::fgMemoryManager, &xsd);
2837 init_parser(parser);
2839 CustomErrorHandler eh(&messages);
2840 parser.setErrorHandler(&eh);
2843 const auto xsdFile = app.arguments().at(2);
2844 if (!parser.loadGrammar((
const char16_t *)xsdFile.utf16(), Grammar::SchemaGrammarType,
true) || eh.failed()) {
2845 qWarning(
"Failed to parse XSD %s: %s", qPrintable(xsdFile), qPrintable(messages));
2853 const QString hlFilenamesListing = app.arguments().value(3);
2854 if (hlFilenamesListing.
isEmpty()) {
2858 QStringList hlFilenames = readListing(hlFilenamesListing);
2860 qWarning(
"Failed to read %s", qPrintable(hlFilenamesListing));
2865 const QStringList textAttributes =
QStringList() << QStringLiteral(
"name") << QStringLiteral(
"alternativeNames") << QStringLiteral(
"section")
2866 << QStringLiteral(
"mimetype") << QStringLiteral(
"extensions") << QStringLiteral(
"style")
2867 << QStringLiteral(
"author") << QStringLiteral(
"license") << QStringLiteral(
"indenter");
2870 HlFilesChecker filesChecker;
2873 for (
const QString &hlFilename : std::as_const(hlFilenames)) {
2874 QFile hlFile(hlFilename);
2876 qWarning(
"Failed to open %s", qPrintable(hlFilename));
2883 SAX2XMLReaderImpl parser(XMLPlatformUtils::fgMemoryManager, &xsd);
2884 init_parser(parser);
2886 CustomErrorHandler eh(&messages);
2887 parser.setErrorHandler(&eh);
2890 parser.parse((
const char16_t *)hlFile.fileName().utf16());
2894 qWarning(
"Failed to validate XML %s: %s", qPrintable(hlFile.fileName()), qPrintable(messages));
2917 for (
const QString &attribute : std::as_const(textAttributes)) {
2922 if (!checkExtensions(hl[QStringLiteral(
"extensions")].
toString())) {
2923 qWarning() << hlFilename <<
"'extensions' wildcards invalid:" << hl[QStringLiteral(
"extensions")].toString();
2935 hl[QStringLiteral(
"nameUtf8")] = hl[QStringLiteral(
"name")].toString().toUtf8();
2936 hl[QStringLiteral(
"sectionUtf8")] = hl[QStringLiteral(
"section")].toString().toUtf8();
2941 const QString hlName = hl[QStringLiteral(
"name")].toString();
2942 const QString hlAlternativeNames = hl[QStringLiteral(
"alternativeNames")].toString();
2944 filesChecker.setDefinition(xml.
attributes().
value(QStringLiteral(
"kateversion")),
2950 while (!xml.
atEnd()) {
2952 filesChecker.processElement(xml);
2961 filesChecker.resolveContexts();
2963 if (!filesChecker.check()) {
2973 QFile outFile(app.arguments().at(1));
Type type(const QSqlDatabase &db)
char * toString(const EngineQuery &query)
KDB_EXPORT KDbVersionInfo version()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT void add(const QString &fileClass, const QString &directory)
QAction * replace(const QObject *recvr, const char *slot, QObject *parent)
QString name(StandardAction id)
QAction * next(const QObject *recvr, const char *slot, QObject *parent)
QAction * find(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
QCborValue fromVariant(const QVariant &variant)
bool isDigit(char32_t ucs4)
bool isLetter(char32_t ucs4)
QString fileName() const const
void append(QList< T > &&value)
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
QString errorString() const const
bool isValid() const const
QString pattern() const const
qsizetype patternErrorOffset() const const
bool contains(const QSet< T > &other) const const
iterator erase(const_iterator pos)
iterator insert(const T &value)
qsizetype size() const const
QString fromUtf16(const char16_t *unicode, qsizetype size)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
qsizetype size() const const
QString sliced(qsizetype pos) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QChar c, Qt::CaseSensitivity cs) const const
QChar first() const const
qsizetype indexOf(QChar c, qsizetype from, Qt::CaseSensitivity cs) const const
bool isNull() const const
qsizetype size() const const
QStringView sliced(qsizetype pos) const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar ch) const const
int toInt(bool *ok, int base) const const
QString toString() const const
bool operator==(const QGraphicsApiFilter &reference, const QGraphicsApiFilter &sample)
QTextStream & endl(QTextStream &stream)
QStringView name() const const
QStringView value() const const
QStringView value(QAnyStringView namespaceUri, QAnyStringView name) const const
QXmlStreamAttributes attributes() const const
qint64 characterOffset() const const
QString errorString() const const
bool hasError() const const
bool isCharacters() const const
bool isEndElement() const const
bool isStartElement() const const
qint64 lineNumber() const const
QStringView name() const const
QString readElementText(ReadElementTextBehaviour behaviour)
bool readNextStartElement()
QStringView text() const const