9#include <QCoreApplication>
13#include <QMutableMapIterator>
14#include <QRegularExpression>
17#include <QXmlStreamReader>
21#include <xercesc/framework/XMLGrammarPoolImpl.hpp>
23#include <xercesc/parsers/SAX2XMLReaderImpl.hpp>
25#include <xercesc/sax/ErrorHandler.hpp>
26#include <xercesc/sax/SAXParseException.hpp>
28#include <xercesc/util/PlatformUtils.hpp>
29#include <xercesc/util/XMLString.hpp>
30#include <xercesc/util/XMLUni.hpp>
32#include <xercesc/framework/XMLGrammarPoolImpl.hpp>
33#include <xercesc/validators/common/Grammar.hpp>
35using namespace xercesc;
54class CustomErrorHandler :
public ErrorHandler
61 CustomErrorHandler(
QString *messages)
62 : m_messages(messages)
79 enum severity { s_warning, s_error, s_fatal };
85 void warning(
const SAXParseException &e)
override
95 void error(
const SAXParseException &e)
override
105 void fatalError(
const SAXParseException &e)
override
114 void resetErrors()
override
124 void handle(
const SAXParseException &e, severity s)
127 const XMLCh *xid(e.getPublicId());
129 xid = e.getSystemId();
131 m_messages <<
QString::fromUtf16(xid) <<
":" << e.getLineNumber() <<
":" << e.getColumnNumber() <<
" " << (s == s_warning ?
"warning: " :
"error: ")
144 bool m_failed =
false;
147void init_parser(SAX2XMLReaderImpl &parser)
151 parser.setFeature(XMLUni::fgSAX2CoreNameSpaces,
true);
152 parser.setFeature(XMLUni::fgSAX2CoreNameSpacePrefixes,
true);
153 parser.setFeature(XMLUni::fgSAX2CoreValidation,
true);
157 parser.setFeature(XMLUni::fgXercesSchema,
true);
158 parser.setFeature(XMLUni::fgXercesSchemaFullChecking,
true);
159 parser.setFeature(XMLUni::fgXercesValidationErrorAsFatal,
true);
163 parser.setFeature(XMLUni::fgXercesUseCachedGrammarInParse,
true);
168 parser.setFeature(XMLUni::fgXercesLoadSchema,
false);
173 parser.setFeature(XMLUni::fgXercesHandleMultipleImports,
true);
178#include "../lib/worddelimiters_p.h"
179#include "../lib/xml_p.h"
183using KSyntaxHighlighting::WordDelimiters;
184using KSyntaxHighlighting::Xml::attrToBool;
190 void setDefinition(
const T &verStr,
const QString &filename,
const QString &name)
192 m_currentDefinition = &*m_definitions.
insert(name, Definition{});
193 m_currentDefinition->languageName =
name;
194 m_currentDefinition->filename = filename;
195 m_currentDefinition->kateVersionStr = verStr.toString();
196 m_currentKeywords =
nullptr;
197 m_currentContext =
nullptr;
201 qWarning() << filename <<
"invalid kateversion" << verStr;
204 m_currentDefinition->kateVersion = {verStr.left(idx).toInt(), verStr.mid(idx + 1).toInt()};
211 if (m_currentContext) {
212 m_currentContext->rules.
push_back(Context::Rule{});
213 auto &rule = m_currentContext->rules.
back();
214 m_success = rule.parseElement(m_currentDefinition->filename, xml) && m_success;
215 m_currentContext->hasDynamicRule = m_currentContext->hasDynamicRule || rule.dynamic == XmlBool::True;
216 }
else if (m_currentKeywords) {
217 m_success = m_currentKeywords->items.parseElement(m_currentDefinition->filename, xml) && m_success;
218 }
else if (xml.
name() == QStringLiteral(
"context")) {
219 processContextElement(xml);
220 }
else if (xml.
name() == QStringLiteral(
"list")) {
221 processListElement(xml);
222 }
else if (xml.
name() == QStringLiteral(
"keywords")) {
223 m_success = m_currentDefinition->parseKeywords(xml) && m_success;
224 }
else if (xml.
name() == QStringLiteral(
"emptyLine")) {
225 m_success = parseEmptyLine(m_currentDefinition->filename, xml) && m_success;
226 }
else if (xml.
name() == QStringLiteral(
"itemData")) {
227 m_success = m_currentDefinition->itemDatas.parseElement(m_currentDefinition->filename, xml) && m_success;
230 if (m_currentContext && xml.
name() == QStringLiteral(
"context")) {
231 m_currentContext =
nullptr;
232 }
else if (m_currentKeywords && xml.
name() == QStringLiteral(
"list")) {
233 m_currentKeywords =
nullptr;
239 void resolveContexts()
242 while (def.hasNext()) {
244 auto &definition = def.value();
245 auto &contexts = definition.contexts;
247 if (contexts.isEmpty()) {
248 qWarning() << definition.filename <<
"has no context";
253 auto markAsUsedContext = [](ContextName &contextName) {
254 if (!contextName.stay && contextName.context) {
255 contextName.context->isOnlyIncluded =
false;
260 while (contextIt.hasNext()) {
262 auto &context = contextIt.value();
263 resolveContextName(definition, context, context.lineEndContext, context.line);
264 resolveContextName(definition, context, context.lineEmptyContext, context.line);
265 resolveContextName(definition, context, context.fallthroughContext, context.line);
266 markAsUsedContext(context.lineEndContext);
267 markAsUsedContext(context.lineEmptyContext);
268 markAsUsedContext(context.fallthroughContext);
269 for (
auto &rule : context.rules) {
270 rule.parentContext = &context;
271 resolveContextName(definition, context, rule.context, rule.line);
272 if (rule.type != Context::Rule::Type::IncludeRules) {
273 markAsUsedContext(rule.context);
274 }
else if (rule.includeAttrib == XmlBool::True && rule.context.context) {
275 rule.context.context->referencedWithIncludeAttrib =
true;
280 auto *firstContext = &*definition.contexts.find(definition.firstContextName);
281 firstContext->isOnlyIncluded =
false;
282 definition.firstContext = firstContext;
285 resolveIncludeRules();
290 bool success = m_success;
292 const auto usedContexts = extractUsedContexts();
298 while (def.hasNext()) {
300 const auto &definition = def.value();
301 const auto &filename = definition.filename;
303 auto *maxDef = maxKateVersionDefinition(definition, maxVersionByDefinitions);
304 if (maxDef != &definition) {
305 qWarning() << definition.filename <<
"depends on a language" << maxDef->languageName <<
"in version" << maxDef->kateVersionStr
306 <<
". Please, increase kateversion.";
312 success = checkKeywordsList(definition) && success;
313 success = checkContexts(definition, usedAttributeNames, ignoredAttributeNames, usedContexts, unreachableIncludedRules) && success;
316 const auto invalidNames = usedAttributeNames - definition.itemDatas.styleNames;
317 for (
const auto &styleName : invalidNames) {
318 qWarning() << filename <<
"line" << styleName.line <<
"reference of non-existing itemData attributes:" << styleName.name;
323 const auto ignoredNames = ignoredAttributeNames - usedAttributeNames;
324 for (
const auto &styleName : ignoredNames) {
325 qWarning() << filename <<
"line" << styleName.line <<
"attribute" << styleName.name
326 <<
"is never used. All uses are with lookAhead=true or <IncludeRules/>";
331 auto unusedNames = definition.itemDatas.styleNames - usedAttributeNames;
332 unusedNames -= ignoredNames;
333 for (
const auto &styleName :
std::as_const(unusedNames)) {
334 qWarning() << filename <<
"line" << styleName.line <<
"unused itemData:" << styleName.name;
340 while (unreachableIncludedRuleIt.hasNext()) {
341 unreachableIncludedRuleIt.next();
342 IncludedRuleUnreachableBy &unreachableRulesBy = unreachableIncludedRuleIt.value();
343 if (unreachableRulesBy.alwaysUnreachable) {
344 auto *rule = unreachableIncludedRuleIt.key();
346 if (!rule->parentContext->isOnlyIncluded) {
352 auto &unreachableBy = unreachableRulesBy.unreachableBy;
353 unreachableBy.
erase(std::remove_if(unreachableBy.begin(),
355 [&](
const RuleAndInclude &ruleAndInclude) {
356 if (rules.contains(ruleAndInclude.rule)) {
359 rules.
insert(ruleAndInclude.rule);
362 unreachableBy.end());
366 for (
auto &ruleAndInclude :
std::as_const(unreachableBy)) {
367 message += QStringLiteral(
"line ");
369 message += QStringLiteral(
" [");
370 message += ruleAndInclude.rule->parentContext->name;
371 if (rule->filename != ruleAndInclude.rule->filename) {
372 message += QStringLiteral(
" (");
373 message += ruleAndInclude.rule->filename;
376 if (ruleAndInclude.includeRules) {
377 message += QStringLiteral(
" via line ");
380 message += QStringLiteral(
"], ");
384 qWarning() << rule->filename <<
"line" << rule->line <<
"no IncludeRule can reach this rule, hidden by" << message;
406 Context *context =
nullptr;
419 if (attr.
name() != attrName) {
425 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"attribute is empty";
434 bool extractXmlBool(XmlBool &xmlBool,
const QString &attrName)
436 if (attr.
name() != attrName) {
440 xmlBool = attr.
value().
isNull() ? XmlBool::Unspecified : attrToBool(attr.
value()) ? XmlBool::True : XmlBool::False;
447 bool extractPositive(
int &positive,
const QString &attrName)
449 if (attr.
name() != attrName) {
456 if (!ok || positive < 0) {
457 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"should be a positive integer:" << attr.
value();
466 bool checkColor(
const QString &attrName)
468 if (attr.
name() != attrName) {
472 const auto value = attr.
value();
473 if (value.isEmpty() ) {
474 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"should be a color:" << value;
485 if (attr.
name() != attrName) {
493 qWarning() << filename <<
"line" << xml.
lineNumber() << attrName <<
"must contain exactly one char:" << attr.
value();
501 bool checkIfExtracted(
bool isExtracted)
507 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"unknown attribute:" << attr.
name();
518 friend size_t qHash(
const Item &item,
size_t seed = 0)
520 return qHash(item.content, seed);
525 return item0.content == item1.content;
540 qWarning() << filename <<
"line" << line <<
"is empty:" << xml.
name();
544 if (xml.
name() == QStringLiteral(
"include")) {
545 includes.
insert({content, line});
546 }
else if (xml.
name() == QStringLiteral(
"item")) {
547 keywords.
append({content, line});
549 qWarning() << filename <<
"line" << line <<
"invalid element:" << xml.
name();
566 for (
auto &attr : xml.attributes()) {
567 Parser parser{filename, xml, attr, success};
569 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name"));
571 success = parser.checkIfExtracted(isExtracted);
603 bool isDotRegex =
false;
613 XmlBool firstNonSpace{};
616 XmlBool insensitive{};
625 XmlBool includeAttrib{};
647 Context
const *parentContext =
nullptr;
653 this->filename = filename;
656 using Pair = QPair<QString, Type>;
657 static const auto pairs = {
658 Pair{QStringLiteral(
"AnyChar"), Type::AnyChar},
659 Pair{QStringLiteral(
"Detect2Chars"), Type::Detect2Chars},
660 Pair{QStringLiteral(
"DetectChar"), Type::DetectChar},
661 Pair{QStringLiteral(
"DetectIdentifier"), Type::DetectIdentifier},
662 Pair{QStringLiteral(
"DetectSpaces"), Type::DetectSpaces},
663 Pair{QStringLiteral(
"Float"), Type::Float},
664 Pair{QStringLiteral(
"HlCChar"), Type::HlCChar},
665 Pair{QStringLiteral(
"HlCHex"), Type::HlCHex},
666 Pair{QStringLiteral(
"HlCOct"), Type::HlCOct},
667 Pair{QStringLiteral(
"HlCStringChar"), Type::HlCStringChar},
668 Pair{QStringLiteral(
"IncludeRules"), Type::IncludeRules},
669 Pair{QStringLiteral(
"Int"), Type::Int},
670 Pair{QStringLiteral(
"LineContinue"), Type::LineContinue},
671 Pair{QStringLiteral(
"RangeDetect"), Type::RangeDetect},
672 Pair{QStringLiteral(
"RegExpr"), Type::RegExpr},
673 Pair{QStringLiteral(
"StringDetect"), Type::StringDetect},
674 Pair{QStringLiteral(
"WordDetect"), Type::WordDetect},
675 Pair{QStringLiteral(
"keyword"), Type::keyword},
678 for (
auto pair : pairs) {
681 bool success = parseAttributes(filename, xml);
682 success = checkMandoryAttributes(filename, xml) && success;
683 if (success && type == Type::RegExpr) {
685 static const QRegularExpression isDot(QStringLiteral(R
"(^\(?\.(?:[*+][*+?]?|[*+]|\{1\})?\$?$)"));
687 static const QRegularExpression removeParentheses(QStringLiteral(R
"(\((?:\?:)?|\))"));
690 isDotRegex = reg.contains(isDot);
693 static const QRegularExpression allSuffix(QStringLiteral(
"(?<!\\\\)[.][*][?+]?[$]?$"));
694 sanitizedString = string;
697 if (sanitizedString.
isEmpty() || sanitizedString == QStringLiteral(
"^")) {
698 sanitizedString = string;
705 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"unknown element:" << xml.
name();
714 for (
auto &attr : xml.attributes()) {
715 Parser parser{filename, xml, attr, success};
718 const bool isExtracted
719 = parser.extractString(attribute, QStringLiteral(
"attribute"))
720 || parser.extractString(context.name, QStringLiteral(
"context"))
721 || parser.extractXmlBool(lookAhead, QStringLiteral(
"lookAhead"))
722 || parser.extractXmlBool(firstNonSpace, QStringLiteral(
"firstNonSpace"))
723 || parser.extractString(beginRegion, QStringLiteral(
"beginRegion"))
724 || parser.extractString(endRegion, QStringLiteral(
"endRegion"))
725 || parser.extractPositive(column, QStringLiteral(
"column"))
726 || ((
type == Type::RegExpr
727 ||
type == Type::StringDetect
728 ||
type == Type::WordDetect
729 ||
type == Type::keyword
730 ) && parser.extractXmlBool(insensitive, QStringLiteral(
"insensitive")))
731 || ((
type == Type::DetectChar
732 ||
type == Type::RegExpr
733 ||
type == Type::StringDetect
734 ||
type == Type::keyword
735 ) && parser.extractXmlBool(dynamic, QStringLiteral(
"dynamic")))
736 || ((
type == Type::RegExpr)
737 && parser.extractXmlBool(minimal, QStringLiteral(
"minimal")))
738 || ((
type == Type::DetectChar
739 ||
type == Type::Detect2Chars
740 ||
type == Type::LineContinue
741 ||
type == Type::RangeDetect
742 ) && parser.extractChar(char0, QStringLiteral(
"char")))
743 || ((
type == Type::Detect2Chars
744 ||
type == Type::RangeDetect
745 ) && parser.extractChar(char1, QStringLiteral(
"char1")))
746 || ((
type == Type::AnyChar
747 ||
type == Type::RegExpr
748 ||
type == Type::StringDetect
749 ||
type == Type::WordDetect
750 ||
type == Type::keyword
751 ) && parser.extractString(
string, QStringLiteral(
"String")))
752 || ((
type == Type::IncludeRules)
753 && parser.extractXmlBool(includeAttrib, QStringLiteral(
"includeAttrib")))
754 || ((
type == Type::Float
755 ||
type == Type::HlCHex
756 ||
type == Type::HlCOct
758 ||
type == Type::keyword
759 ||
type == Type::WordDetect
760 ) && (parser.extractString(additionalDeliminator, QStringLiteral(
"additionalDeliminator"))
761 || parser.extractString(weakDeliminator, QStringLiteral(
"weakDeliminator"))))
765 success = parser.checkIfExtracted(isExtracted);
767 if (type == Type::LineContinue && char0 ==
QLatin1Char(
'\0')) {
785 case Type::StringDetect:
786 case Type::WordDetect:
788 missingAttr =
string.
isEmpty() ? QStringLiteral(
"String") :
QString();
791 case Type::DetectChar:
792 missingAttr = !char0.
unicode() ? QStringLiteral(
"char") :
QString();
795 case Type::Detect2Chars:
796 case Type::RangeDetect:
797 missingAttr = !char0.
unicode() && !char1.
unicode() ? QStringLiteral(
"char and char1")
798 : !char0.unicode() ? QStringLiteral(
"char")
799 : !char1.unicode() ? QStringLiteral(
"char1")
803 case Type::IncludeRules:
804 missingAttr = context.name.isEmpty() ? QStringLiteral(
"context") :
QString();
807 case Type::DetectIdentifier:
808 case Type::DetectSpaces:
813 case Type::HlCStringChar:
815 case Type::LineContinue:
820 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute:" << missingAttr;
830 bool isOnlyIncluded =
true;
832 bool referencedWithIncludeAttrib =
false;
833 bool hasDynamicRule =
false;
836 ContextName lineEndContext;
837 ContextName lineEmptyContext;
838 ContextName fallthroughContext;
841 XmlBool fallthrough{};
842 XmlBool stopEmptyLineContextSwitchLoop{};
850 for (
auto &attr : xml.attributes()) {
851 Parser parser{filename, xml, attr, success};
852 XmlBool noIndentationBasedFolding{};
855 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name"))
856 || parser.extractString(attribute, QStringLiteral(
"attribute"))
857 || parser.extractString(lineEndContext.name, QStringLiteral(
"lineEndContext"))
858 || parser.extractString(lineEmptyContext.name, QStringLiteral(
"lineEmptyContext"))
859 || parser.extractString(fallthroughContext.name, QStringLiteral(
"fallthroughContext"))
860 || parser.extractXmlBool(dynamic, QStringLiteral(
"dynamic"))
861 || parser.extractXmlBool(fallthrough, QStringLiteral(
"fallthrough"))
862 || parser.extractXmlBool(stopEmptyLineContextSwitchLoop, QStringLiteral(
"stopEmptyLineContextSwitchLoop"))
863 || parser.extractXmlBool(noIndentationBasedFolding, QStringLiteral(
"noIndentationBasedFolding"));
866 success = parser.checkIfExtracted(isExtracted);
870 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: name";
875 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: attribute";
887 Version(
int majorRevision = 0,
int minorRevision = 0)
888 : majorRevision(majorRevision)
889 , minorRevision(minorRevision)
893 bool operator<(
const Version &version)
const
895 return majorRevision <
version.majorRevision || (majorRevision ==
version.majorRevision && minorRevision <
version.minorRevision);
904 friend size_t qHash(
const Style &style,
size_t seed = 0)
906 return qHash(style.name, seed);
911 return style0.name == style1.name;
925 for (
auto &attr : xml.attributes()) {
926 Parser parser{filename, xml, attr, success};
928 const bool isExtracted = parser.extractString(name, QStringLiteral(
"name")) || parser.extractString(defStyleNum, QStringLiteral(
"defStyleNum"))
929 || parser.extractXmlBool(
boolean, QStringLiteral(
"bold")) || parser.extractXmlBool(
boolean, QStringLiteral(
"italic"))
930 || parser.extractXmlBool(
boolean, QStringLiteral(
"underline")) || parser.extractXmlBool(
boolean, QStringLiteral(
"strikeOut"))
931 || parser.extractXmlBool(
boolean, QStringLiteral(
"spellChecking")) || parser.checkColor(QStringLiteral(
"color"))
932 || parser.checkColor(QStringLiteral(
"selColor")) || parser.checkColor(QStringLiteral(
"backgroundColor"))
933 || parser.checkColor(QStringLiteral(
"selBackgroundColor"));
935 success = parser.checkIfExtracted(isExtracted);
939 const auto len = styleNames.
size();
941 if (len == styleNames.
size()) {
942 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"itemData duplicate:" <<
name;
956 const Context *firstContext =
nullptr;
958 WordDelimiters wordDelimiters;
959 Version kateVersion{};
967 wordDelimiters.append(xml.
attributes().
value(QStringLiteral(
"additionalDeliminator")));
968 wordDelimiters.remove(xml.
attributes().
value(QStringLiteral(
"weakDeliminator")));
977 m_success = context.parseElement(m_currentDefinition->filename, xml) && m_success;
978 if (m_currentDefinition->firstContextName.isEmpty()) {
979 m_currentDefinition->firstContextName = context.name;
981 if (m_currentDefinition->contexts.contains(context.name)) {
982 qWarning() << m_currentDefinition->filename <<
"line" << xml.
lineNumber() <<
"duplicate context:" << context.name;
985 m_currentContext = &*m_currentDefinition->contexts.insert(context.name, context);
992 m_success = keywords.parseElement(m_currentDefinition->filename, xml) && m_success;
993 if (m_currentDefinition->keywordsList.contains(keywords.name)) {
994 qWarning() << m_currentDefinition->filename <<
"line" << xml.
lineNumber() <<
"duplicate list:" << keywords.name;
997 m_currentKeywords = &*m_currentDefinition->keywordsList.insert(keywords.name, keywords);
1002 auto it = maxVersionByDefinitions.
find(&definition);
1003 if (it != maxVersionByDefinitions.
end()) {
1006 auto it = maxVersionByDefinitions.
insert(&definition, &definition);
1007 for (
const auto &referencedDef : definition.referencedDefinitions) {
1008 auto *maxDef = maxKateVersionDefinition(*referencedDef, maxVersionByDefinitions);
1009 if (it.value()->kateVersion < maxDef->kateVersion) {
1010 it.value() = maxDef;
1018 void resolveIncludeRules()
1024 while (def.hasNext()) {
1026 auto &definition = def.value();
1028 while (contextIt.hasNext()) {
1030 auto ¤tContext = contextIt.value();
1031 for (
auto &rule : currentContext.rules) {
1032 if (rule.type != Context::Rule::Type::IncludeRules) {
1036 if (rule.context.stay) {
1037 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules refers to himself";
1042 if (rule.context.popCount) {
1043 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules with #pop prefix";
1047 if (!rule.context.context) {
1054 usedContexts.
clear();
1055 usedContexts.
insert(rule.context.context);
1057 contexts.
append(rule.context.context);
1059 for (
int i = 0; i < contexts.
size(); ++i) {
1060 currentContext.hasDynamicRule = contexts[i]->hasDynamicRule;
1061 for (
const auto &includedRule : contexts[i]->rules) {
1062 if (includedRule.type != Context::Rule::Type::IncludeRules) {
1063 rule.includedRules.
append(&includedRule);
1064 }
else if (&rule == &includedRule) {
1065 qWarning() << definition.filename <<
"line" << rule.line <<
"IncludeRules refers to himself by recursivity";
1068 rule.includedIncludeRules.insert(&includedRule);
1070 if (includedRule.includedRules.isEmpty()) {
1071 const auto *context = includedRule.context.context;
1072 if (context && !usedContexts.
contains(context)) {
1073 contexts.
append(context);
1074 usedContexts.
insert(context);
1077 rule.includedRules.append(includedRule.includedRules);
1095 while (def.hasNext()) {
1097 const auto &definition = def.value();
1099 if (definition.firstContext) {
1100 usedContexts.
insert(definition.firstContext);
1102 contexts.
append(definition.firstContext);
1104 for (
int i = 0; i < contexts.
size(); ++i) {
1105 auto appendContext = [&](
const Context *context) {
1106 if (context && !usedContexts.
contains(context)) {
1107 contexts.
append(context);
1108 usedContexts.
insert(context);
1112 const auto *context = contexts[i];
1113 appendContext(context->lineEndContext.context);
1114 appendContext(context->lineEmptyContext.context);
1115 appendContext(context->fallthroughContext.context);
1117 for (
auto &rule : context->rules) {
1118 appendContext(rule.context.context);
1124 return usedContexts;
1127 struct RuleAndInclude {
1128 const Context::Rule *rule;
1129 const Context::Rule *includeRules;
1131 explicit operator bool()
const
1137 struct IncludedRuleUnreachableBy {
1139 bool alwaysUnreachable =
true;
1143 bool checkContexts(
const Definition &definition,
1149 bool success =
true;
1152 while (contextIt.hasNext()) {
1155 const auto &context = contextIt.value();
1156 const auto &filename = definition.filename;
1158 if (!usedContexts.
contains(&context)) {
1159 qWarning() << filename <<
"line" << context.line <<
"unused context:" << context.name;
1164 if (context.name.startsWith(QStringLiteral(
"#pop"))) {
1165 qWarning() << filename <<
"line" << context.line <<
"the context name must not start with '#pop':" << context.name;
1169 if (!context.attribute.isEmpty() && (!context.isOnlyIncluded || context.referencedWithIncludeAttrib)) {
1170 usedAttributeNames.
insert({context.attribute, context.line});
1173 success = checkContextAttribute(definition, context) && success;
1174 success = checkUreachableRules(definition.filename, context, unreachableIncludedRules) && success;
1175 success = suggestRuleMerger(definition.filename, context) && success;
1177 for (
const auto &rule : context.rules) {
1178 if (!rule.attribute.isEmpty()) {
1179 if (rule.lookAhead != XmlBool::True) {
1180 usedAttributeNames.
insert({rule.attribute, rule.line});
1182 ignoredAttributeNames.
insert({rule.attribute, rule.line});
1185 success = checkLookAhead(rule) && success;
1186 success = checkStringDetect(rule) && success;
1187 success = checkKeyword(definition, rule) && success;
1188 success = checkRegExpr(filename, rule, context) && success;
1189 success = checkDelimiters(definition, rule) && success;
1205 bool checkRegExpr(
const QString &filename,
const Context::Rule &rule,
const Context &context)
const
1208 if (rule.type == Context::Rule::Type::RegExpr && !rule.string.isEmpty()) {
1210 if (!checkRegularExpression(rule.filename, regexp, rule.line)) {
1215 if (rule.dynamic == XmlBool::True) {
1217 if (!rule.string.contains(placeHolder)) {
1218 qWarning() << rule.filename <<
"line" << rule.line <<
"broken regex:" << rule.string <<
"problem: dynamic=true but no %\\d+ placeholder";
1223 auto reg = (rule.lookAhead == XmlBool::True) ? rule.sanitizedString : rule.string;
1224 if (rule.lookAhead == XmlBool::True) {
1226 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})|(?<!\\)[^])}\\]|(?=\\)\\\\)[*][?+]?$)"));
1227 reg.replace(removeAllSuffix, QString());
1230 reg.replace(QStringLiteral("{1}"),
QString());
1235 QStringLiteral(R
"(^\^?(?:\((?:\?:)?)?\^?(?:\\s|\[(?:\\s| (?:\t|\\t)|(?:\t|\\t) )\])\)?(?:[*+][*+?]?|[*+])?\)?\)?$)"));
1236 if (rule.string.contains(isDetectSpaces)) {
1237 char const *extraMsg = rule.string.contains(
QLatin1Char(
'^')) ?
"+ column=\"0\" or firstNonSpace=\"1\"" :
"";
1238 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by DetectSpaces / DetectChar / AnyChar" << extraMsg <<
":"
1243#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}))"
1244#define REG_CHAR "(?:" REG_ESCAPE_CHAR "|\\[(?:" REG_ESCAPE_CHAR "|.)\\]|[^[.^])"
1248 "\\.\\*[?+]?" REG_CHAR
"|"
1249 "\\[\\^(" REG_ESCAPE_CHAR
"|.)\\]\\*[?+]?\\1"
1251 if ((rule.lookAhead == XmlBool::True || rule.minimal == XmlBool::True || rule.string.contains(QStringLiteral(
".*?"))
1252 || rule.string.contains(QStringLiteral(
"[^")))
1253 && reg.contains(isRange)) {
1254 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by RangeDetect:" << rule.string;
1259 static const QRegularExpression isAnyChar(QStringLiteral(R
"(^(\^|\((\?:)?)*\[(?!\^)[-\]]?(\\[^0BDPSWbdpswoux]|[^-\]\\])*\]\)*$)"));
1260 if (rule.string.contains(isAnyChar)) {
1262 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by AnyChar:" << rule.string << extra;
1267 static const QRegularExpression isLineContinue(QStringLiteral(
"^\\^?" REG_CHAR
"\\$$"));
1268 if (reg.contains(isLineContinue)) {
1269 auto extra = (reg[0] ==
QLatin1Char(
'^')) ?
"with column=\"0\"" :
"";
1270 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by LineContinue:" << rule.string << extra;
1276 reg.replace(sanitize1, QStringLiteral(
"_"));
1279#undef REG_ESCAPE_CHAR
1282 static const QRegularExpression isMinimal(QStringLiteral(
"(?![.][*+?][$]?[)]*$)[.][*+?][^?+]"));
1285 if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(isMinimal) && !reg.contains(hasNotGreedy)
1286 && (!rule.context.context || !rule.context.context->hasDynamicRule || regexp.captureCount() == 0)
1288 qWarning() << rule.filename <<
"line" << rule.line
1289 <<
"RegExpr should be have minimal=\"1\" or use lazy operator (i.g, '.*' -> '.*?'):" << rule.string;
1295 reg.replace(sanitize2, QStringLiteral("___"));
1298 static const QRegularExpression sanitize3(QStringLiteral(R
"(\[(?:\^\]?[^]]*|\]?[^]\\]*?\\.[^]]*|\][^]]{2,}|[^]]{3,})\]|(\[\]?[^]]*\]))"));
1299 reg.replace(sanitize3, QStringLiteral("...\\1"));
1303 reg.replace(sanitize4, QStringLiteral("_"));
1305 const int len = reg.size();
1307 static const QRegularExpression toInsensitive(QStringLiteral(R
"(\[(?:([^]])\1)\])"));
1308 reg = reg.toUpper();
1309 reg.replace(toInsensitive, QString());
1313 static const QRegularExpression isStringDetect(QStringLiteral(R
"(^\^?(?:[^|\\?*+$^[{(.]|{(?!\d+,\d*}|,\d+})|\(\?:)+$)"));
1314 if (reg.contains(isStringDetect)) {
1315 char const *extraMsg = rule.string.contains(
QLatin1Char(
'^')) ?
"+ column=\"0\" or firstNonSpace=\"1\"" :
"";
1316 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by StringDetect / Detect2Chars / DetectChar" << extraMsg
1317 <<
":" << rule.string;
1318 if (len != reg.size()) {
1319 qWarning() << rule.filename <<
"line" << rule.line <<
"insensitive=\"1\" missing:" << rule.string;
1325 if (rule.column == -1) {
1330 auto first = std::as_const(reg).begin();
1331 auto last = std::as_const(reg).end();
1343 const int bolDepth = depth;
1346 while (++first != last) {
1351 if (depth < bolDepth) {
1353 if (first + 1 != last && QStringLiteral(
"*?").contains(first[1])) {
1360 if (depth <= bolDepth) {
1368 qWarning() << rule.filename <<
"line" << rule.line <<
"column=\"0\" missing with RegExpr:" << rule.string;
1375 if (rule.column == 0 && !rule.isDotRegex) {
1376 bool hasStartOfLine =
false;
1377 auto first = std::as_const(reg).begin();
1378 auto last = std::as_const(reg).end();
1379 for (; first != last; ++first) {
1381 hasStartOfLine =
true;
1392 if (!hasStartOfLine) {
1393 qWarning() << rule.filename <<
"line" << rule.line
1394 <<
"start of line missing in the pattern with column=\"0\" (i.e. abc -> ^abc):" << rule.string;
1399 bool useCapture =
false;
1402 if (regexp.captureCount()) {
1403 auto maximalCapture = [](
const QString(&referenceNames)[9],
const QString &s) {
1405 while (maxCapture && !s.contains(referenceNames[maxCapture - 1])) {
1411 int maxCaptureUsed = 0;
1413 if (rule.context.context && !rule.context.stay) {
1414 for (
const auto &nextRule : rule.context.context->rules) {
1415 if (nextRule.dynamic == XmlBool::True) {
1417 QStringLiteral(
"%1"),
1418 QStringLiteral(
"%2"),
1419 QStringLiteral(
"%3"),
1420 QStringLiteral(
"%4"),
1421 QStringLiteral(
"%5"),
1422 QStringLiteral(
"%6"),
1423 QStringLiteral(
"%7"),
1424 QStringLiteral(
"%8"),
1425 QStringLiteral(
"%9"),
1427 int maxDynamicCapture = maximalCapture(cap, nextRule.string);
1428 maxCaptureUsed = std::max(maxCaptureUsed, maxDynamicCapture);
1434 QStringLiteral(
"\\1"),
1435 QStringLiteral(
"\\2"),
1436 QStringLiteral(
"\\3"),
1437 QStringLiteral(
"\\4"),
1438 QStringLiteral(
"\\5"),
1439 QStringLiteral(
"\\6"),
1440 QStringLiteral(
"\\7"),
1441 QStringLiteral(
"\\8"),
1442 QStringLiteral(
"\\9"),
1445 QStringLiteral(
"\\g1"),
1446 QStringLiteral(
"\\g2"),
1447 QStringLiteral(
"\\g3"),
1448 QStringLiteral(
"\\g4"),
1449 QStringLiteral(
"\\g5"),
1450 QStringLiteral(
"\\g6"),
1451 QStringLiteral(
"\\g7"),
1452 QStringLiteral(
"\\g8"),
1453 QStringLiteral(
"\\g9"),
1455 const int maxBackReference = std::max(maximalCapture(num1, rule.string), maximalCapture(num1, rule.string));
1457 const int maxCapture = std::max(maxCaptureUsed, maxBackReference);
1459 if (maxCapture && regexp.captureCount() > maxCapture) {
1460 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr with" << regexp.captureCount() <<
"captures but only" << maxCapture
1461 <<
"are used. Please, replace '(...)' with '(?:...)':" << rule.string;
1465 useCapture = maxCapture;
1471 QStringLiteral(R
"(^(\((\?:)?|\^)*\[(\\p\{L\}|_){2}\]([+][?+]?)?\[(\\p\{N\}|\\p\{L\}|_){3}\][*][?+]?\)*$)"));
1472 if (rule.string.contains(isDetectIdentifier)) {
1473 qWarning() << rule.filename <<
"line" << rule.line <<
"RegExpr should be replaced by DetectIdentifier:" << rule.string;
1478 if (rule.isDotRegex) {
1480 int i = &rule - context.rules.data() + 1;
1481 const bool hasColumn = (rule.column != -1);
1482 const bool hasFirstNonSpace = (rule.firstNonSpace == XmlBool::True);
1483 const bool isSpecial = (hasColumn || hasFirstNonSpace);
1484 for (; i < context.rules.size(); ++i) {
1485 auto &rule2 = context.rules[i];
1486 if (rule2.type == Context::Rule::Type::IncludeRules && isSpecial) {
1487 i = context.rules.size();
1491 const bool hasColumn2 = (rule2.column != -1);
1492 const bool hasFirstNonSpace2 = (rule2.firstNonSpace == XmlBool::True);
1493 if ((!isSpecial && !hasColumn2 && !hasFirstNonSpace2) || (hasColumn && rule.column == rule2.column)
1494 || (hasFirstNonSpace && hasFirstNonSpace2)) {
1499 auto ruleFilename = (filename == rule.filename) ?
QString() : QStringLiteral(
"in ") + rule.filename;
1500 if (i == context.rules.size()) {
1501 if (rule.lookAhead == XmlBool::True && rule.firstNonSpace != XmlBool::True && rule.column == -1 && rule.beginRegion.isEmpty()
1502 && rule.endRegion.isEmpty() && !useCapture) {
1503 qWarning() << filename <<
"context line" << context.line <<
": RegExpr line" << rule.line << ruleFilename
1504 <<
"should be replaced by fallthroughContext:" << rule.string;
1507 auto &nextRule = context.rules[i];
1508 auto nextRuleFilename = (filename == nextRule.filename) ?
QString() : QStringLiteral(
"in ") + nextRule.filename;
1509 qWarning() << filename <<
"context line" << context.line <<
"contains unreachable element line" << nextRule.line << nextRuleFilename
1510 <<
"because a dot RegExpr is used line" << rule.line << ruleFilename;
1514 static const QRegularExpression unnecessaryQuantifier1(QStringLiteral(R
"([*+?]([.][*+?]{0,2})?$)"));
1515 static const QRegularExpression unnecessaryQuantifier2(QStringLiteral(R
"([*+?]([.][*+?]{0,2})?[)]*$)"));
1516 auto &unnecessaryQuantifier = useCapture ? unnecessaryQuantifier1 : unnecessaryQuantifier2;
1517 if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(unnecessaryQuantifier)) {
1518 qWarning() << rule.filename <<
"line" << rule.line
1519 <<
"Last quantifier is not necessary (i.g., 'xyz*' -> 'xy', 'xyz+.' -> 'xyz.'):" << rule.string;
1531 bool success =
true;
1534 XmlBool casesensitive{};
1536 for (
auto &attr : xml.attributes()) {
1537 Parser parser{filename, xml, attr, success};
1539 const bool isExtracted =
1540 parser.extractString(pattern, QStringLiteral(
"regexpr")) || parser.extractXmlBool(casesensitive, QStringLiteral(
"casesensitive"));
1542 success = parser.checkIfExtracted(isExtracted);
1546 qWarning() << filename <<
"line" << xml.
lineNumber() <<
"missing attribute: regexpr";
1560 const auto pattern = regexp.
pattern();
1564 qWarning() << filename <<
"line" << line <<
"broken regex:" << pattern <<
"problem:" << regexp.
errorString() <<
"at offset"
1570 const int azOffset = std::max(pattern.
indexOf(QStringLiteral(
"A-z")), pattern.
indexOf(QStringLiteral(
"a-Z")));
1571 if (azOffset >= 0) {
1572 qWarning() << filename <<
"line" << line <<
"broken regex:" << pattern <<
"problem: [a-Z] or [A-z] at offset" << azOffset;
1581 bool checkContextAttribute(
const Definition &definition,
const Context &context)
const
1583 bool success =
true;
1585 if (!context.fallthroughContext.name.isEmpty()) {
1586 const bool mandatoryFallthroughAttribute = definition.kateVersion < Version{5, 62};
1587 if (context.fallthrough == XmlBool::True && !mandatoryFallthroughAttribute) {
1588 qWarning() << definition.filename <<
"line" << context.line <<
"fallthrough attribute is unnecessary with kateversion >= 5.62 in context"
1591 }
else if (context.fallthrough != XmlBool::True && mandatoryFallthroughAttribute) {
1592 qWarning() << definition.filename <<
"line" << context.line
1593 <<
"fallthroughContext attribute without fallthrough=\"1\" attribute is only valid with kateversion >= 5.62 in context"
1599 if (context.stopEmptyLineContextSwitchLoop != XmlBool::Unspecified && definition.kateVersion < Version{5, 103}) {
1600 qWarning() << definition.filename <<
"line" << context.line
1601 <<
"stopEmptyLineContextSwitchLoop attribute is only valid with kateversion >= 5.103 in context" << context.name;
1609 bool checkDelimiters(
const Definition &definition,
const Context::Rule &rule)
const
1611 if (rule.additionalDeliminator.isEmpty() && rule.weakDeliminator.isEmpty()) {
1615 bool success =
true;
1617 if (definition.kateVersion < Version{5, 79}) {
1618 qWarning() << definition.filename <<
"line" << rule.line
1619 <<
"additionalDeliminator and weakDeliminator are only available since version \"5.79\". Please, increase kateversion.";
1623 for (
QChar c : rule.additionalDeliminator) {
1624 if (!definition.wordDelimiters.contains(c)) {
1629 for (
QChar c : rule.weakDeliminator) {
1630 if (definition.wordDelimiters.contains(c)) {
1635 qWarning() << rule.filename <<
"line" << rule.line <<
"unnecessary use of additionalDeliminator and/or weakDeliminator" << rule.string;
1640 bool checkKeyword(
const Definition &definition,
const Context::Rule &rule)
const
1642 if (rule.type == Context::Rule::Type::keyword) {
1643 auto it = definition.keywordsList.find(rule.string);
1644 if (it == definition.keywordsList.end()) {
1645 qWarning() << rule.filename <<
"line" << rule.line <<
"reference of non-existing keyword list:" << rule.string;
1654 bool checkLookAhead(
const Context::Rule &rule)
const
1656 if (rule.lookAhead == XmlBool::True && rule.context.stay) {
1657 qWarning() << rule.filename <<
"line" << rule.line <<
"infinite loop: lookAhead with context #stay";
1663 bool checkStringDetect(
const Context::Rule &rule)
const
1665 if (rule.type == Context::Rule::Type::StringDetect) {
1667 if (rule.dynamic == XmlBool::True) {
1669 if (!rule.string.contains(placeHolder)) {
1670 qWarning() << rule.filename <<
"line" << rule.line <<
"broken regex:" << rule.string <<
"problem: dynamic=true but no %\\d+ placeholder";
1679 bool checkKeywordsList(
const Definition &definition)
const
1681 bool success =
true;
1683 bool includeNotSupport = (definition.kateVersion < Version{5, 53});
1685 while (keywordsIt.hasNext()) {
1688 for (
const auto &include : keywordsIt.value().items.includes) {
1689 if (includeNotSupport) {
1690 qWarning() << definition.filename <<
"line" << include.line
1691 <<
"<include> is only available since version \"5.53\". Please, increase kateversion.";
1694 success = checkKeywordInclude(definition, include) && success;
1699 for (
const auto& keyword : keywordsIt.value().items.keywords) {
1700 for (
QChar c : keyword.content) {
1701 if (definition.wordDelimiters.contains(c)) {
1702 qWarning() << definition.filename <<
"line" << keyword.line <<
"keyword with delimiter:" << c <<
"in" << keyword.content;
1714 bool checkKeywordInclude(
const Definition &definition,
const Keywords::Items::Item &include)
const
1716 bool containsKeywordName =
true;
1717 int const idx = include.content.indexOf(QStringLiteral(
"##"));
1719 auto it = definition.keywordsList.find(include.content);
1720 containsKeywordName = (it != definition.keywordsList.end());
1722 auto defName = include.content.mid(idx + 2);
1723 auto listName = include.content.left(idx);
1724 auto it = m_definitions.find(defName);
1725 if (it == m_definitions.end()) {
1726 qWarning() << definition.filename <<
"line" << include.line <<
"unknown definition in" << include.content;
1729 containsKeywordName = it->keywordsList.contains(listName);
1732 if (!containsKeywordName) {
1733 qWarning() << definition.filename <<
"line" << include.line <<
"unknown keyword name in" << include.content;
1736 return containsKeywordName;
1745 bool checkUreachableRules(
const QString &filename,
1746 const Context &context,
1749 if (context.isOnlyIncluded) {
1754 RuleAndInclude setRule(
const Context::Rule &rule,
const Context::Rule *includeRules =
nullptr)
1756 auto set = [&](RuleAndInclude &ruleAndInclude) {
1757 auto old = ruleAndInclude;
1758 ruleAndInclude = {&rule, includeRules};
1762 if (rule.firstNonSpace == XmlBool::True) {
1763 return set(firstNonSpace);
1764 }
else if (rule.column == 0) {
1765 return set(column0);
1766 }
else if (rule.column > 0) {
1767 return set(columnGreaterThan0[rule.column]);
1774 RuleAndInclude normal;
1775 RuleAndInclude column0;
1777 RuleAndInclude firstNonSpace;
1786 return m_asciiMap[c.
unicode()];
1788 auto it = m_utf8Map.find(c);
1789 return it == m_utf8Map.end() ? RuleAndInclude{
nullptr,
nullptr} : it.value();
1812 void append(
QChar c,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1815 m_asciiMap[c.
unicode()] = {&rule, includeRule};
1817 m_utf8Map[c] = {&rule, includeRule};
1822 void append(
QStringView s,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1825 append(c, rule, includeRule);
1830 RuleAndInclude m_asciiMap[127]{};
1834 struct Char4Tables {
1836 CharTable charsColumn0;
1838 CharTable charsFirstNonSpace;
1842 struct CharTableArray {
1845 CharTableArray(Char4Tables &tables,
const Context::Rule &rule)
1847 if (rule.firstNonSpace == XmlBool::True) {
1848 appendTable(tables.charsFirstNonSpace);
1851 if (rule.column == 0) {
1852 appendTable(tables.charsColumn0);
1853 }
else if (rule.column > 0) {
1854 appendTable(tables.charsColumnGreaterThan0[rule.column]);
1857 appendTable(tables.chars);
1861 void removeNonSpecialWhenSpecial()
1871 for (
int i = 0; i < m_size; ++i) {
1872 if (
auto ruleAndInclude = m_charTables[i]->
find(c)) {
1873 return ruleAndInclude;
1876 return RuleAndInclude{
nullptr,
nullptr};
1883 for (
int i = 0; i < m_size; ++i) {
1884 auto result = m_charTables[i]->find(s);
1885 if (result.
size()) {
1886 while (++i < m_size) {
1896 void append(
QChar c,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1898 for (
int i = 0; i < m_size; ++i) {
1899 m_charTables[i]->append(c, rule, includeRule);
1904 void append(
QStringView s,
const Context::Rule &rule,
const Context::Rule *includeRule =
nullptr)
1906 for (
int i = 0; i < m_size; ++i) {
1907 m_charTables[i]->append(s, rule, includeRule);
1912 void appendTable(CharTable &t)
1914 m_charTables[m_size] = &t;
1918 CharTable *m_charTables[3];
1922 struct ObservableRule {
1923 const Context::Rule *rule;
1924 const Context::Rule *includeRules;
1926 bool hasResolvedIncludeRules()
const
1928 return rule == includeRules;
1933 struct RuleIterator {
1935 : m_end(&endRule - rules.data())
1941 const Context::Rule *
next()
1944 if (m_includedRules) {
1946 if (m_i2 != m_includedRules->size()) {
1947 return (*m_includedRules)[m_i2];
1950 m_includedRules =
nullptr;
1954 while (m_i < m_end && m_rules[m_i].rule->type == Context::Rule::Type::IncludeRules) {
1955 if (!m_rules[m_i].includeRules && m_rules[m_i].rule->includedRules.size()) {
1957 m_includedRules = &m_rules[m_i].rule->includedRules;
1958 return (*m_includedRules)[m_i2];
1965 return m_rules[m_i - 1].rule;
1972 const Context::Rule *currentIncludeRules()
const
1974 return m_includedRules ? m_rules[m_i].rule : m_rules[m_i].includeRules;
1988 void append(
const Context::Rule &rule,
const Context::Rule *includedRule)
1990 auto array = extractDotRegexes(rule);
1992 *array[0] = {&rule, includedRule};
1995 *array[1] = {&rule, includedRule};
2000 RuleAndInclude
find(
const Context::Rule &rule)
2002 auto array = extractDotRegexes(rule);
2009 return RuleAndInclude{};
2013 using Array = std::array<RuleAndInclude *, 2>;
2015 Array extractDotRegexes(
const Context::Rule &rule)
2019 if (rule.firstNonSpace != XmlBool::True && rule.column == -1) {
2022 if (rule.firstNonSpace == XmlBool::True) {
2023 ret[0] = &dotRegexFirstNonSpace;
2026 if (rule.column == 0) {
2027 ret[1] = &dotRegexColumn0;
2028 }
else if (rule.column > 0) {
2029 ret[1] = &dotRegexColumnGreaterThan0[rule.column];
2036 RuleAndInclude dotRegex{};
2037 RuleAndInclude dotRegexColumn0{};
2039 RuleAndInclude dotRegexFirstNonSpace{};
2042 bool success =
true;
2045 Char4Tables detectChars;
2047 Char4Tables dynamicDetectChars;
2049 Char4Tables lineContinueChars;
2053 Rule4 hlCCharRule{};
2056 Rule4 hlCStringCharRule{};
2057 Rule4 detectIdentifierRule{};
2065 observedRules.
reserve(context.rules.size());
2066 for (
const Context::Rule &rule : context.rules) {
2067 const Context::Rule *includeRule =
nullptr;
2068 if (rule.type == Context::Rule::Type::IncludeRules) {
2069 auto *context = rule.context.context;
2070 if (context && context->isOnlyIncluded) {
2071 includeRule = &rule;
2075 observedRules.
push_back({&rule, includeRule});
2077 for (
const Context::Rule *rule2 : rule.includedRules) {
2078 observedRules.
push_back({rule2, includeRule});
2083 for (
auto &observedRule : observedRules) {
2084 const Context::Rule &rule = *observedRule.rule;
2085 bool isUnreachable =
false;
2089 auto updateUnreachable1 = [&](RuleAndInclude ruleAndInclude) {
2090 if (ruleAndInclude) {
2091 isUnreachable =
true;
2092 unreachableBy.
append(ruleAndInclude);
2098 if (!ruleAndIncludes.isEmpty()) {
2099 isUnreachable =
true;
2100 unreachableBy.
append(ruleAndIncludes);
2105 auto isCompatible = [&rule](Context::Rule
const &rule2) {
2106 return (rule2.firstNonSpace != XmlBool::True && rule2.column == -1) || (rule.column == rule2.column && rule.column != -1)
2107 || (rule.firstNonSpace == rule2.firstNonSpace && rule.firstNonSpace == XmlBool::True);
2110 updateUnreachable1(dotRegex.find(rule));
2112 switch (rule.type) {
2115 case Context::Rule::Type::AnyChar: {
2116 auto tables = CharTableArray(detectChars, rule);
2117 updateUnreachable2(tables.find(rule.string));
2118 tables.removeNonSpecialWhenSpecial();
2119 tables.append(rule.string, rule);
2125 case Context::Rule::Type::DetectChar: {
2126 auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
2127 auto tables = CharTableArray(chars4, rule);
2128 updateUnreachable1(tables.find(rule.char0));
2129 tables.removeNonSpecialWhenSpecial();
2130 tables.append(rule.char0, rule);
2136 case Context::Rule::Type::DetectSpaces: {
2137 auto tables = CharTableArray(detectChars, rule);
2138 updateUnreachable2(tables.find(QStringLiteral(
" \t")));
2139 tables.removeNonSpecialWhenSpecial();
2146 case Context::Rule::Type::HlCChar:
2147 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'\'')));
2148 updateUnreachable1(hlCCharRule.setRule(rule));
2152 case Context::Rule::Type::HlCHex:
2153 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'0')));
2154 updateUnreachable1(hlCHexRule.setRule(rule));
2158 case Context::Rule::Type::HlCOct:
2159 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'0')));
2160 updateUnreachable1(hlCOctRule.setRule(rule));
2164 case Context::Rule::Type::HlCStringChar:
2165 updateUnreachable1(CharTableArray(detectChars, rule).
find(
QLatin1Char(
'\\')));
2166 updateUnreachable1(hlCStringCharRule.setRule(rule));
2170 case Context::Rule::Type::Int:
2171 updateUnreachable2(CharTableArray(detectChars, rule).
find(QStringLiteral(
"0123456789")));
2172 updateUnreachable1(intRule.setRule(rule));
2176 case Context::Rule::Type::Float:
2177 updateUnreachable2(CharTableArray(detectChars, rule).
find(QStringLiteral(
"0123456789.")));
2178 updateUnreachable1(floatRule.setRule(rule));
2180 updateUnreachable1(Rule4(intRule).setRule(rule));
2184 case Context::Rule::Type::DetectIdentifier:
2185 updateUnreachable1(detectIdentifierRule.setRule(rule));
2189 case Context::Rule::Type::LineContinue: {
2190 updateUnreachable1(CharTableArray(detectChars, rule).
find(rule.char0));
2192 auto tables = CharTableArray(lineContinueChars, rule);
2193 updateUnreachable1(tables.find(rule.char0));
2194 tables.removeNonSpecialWhenSpecial();
2195 tables.append(rule.char0, rule);
2200 case Context::Rule::Type::Detect2Chars:
2201 case Context::Rule::Type::RangeDetect:
2202 updateUnreachable1(CharTableArray(detectChars, rule).
find(rule.char0));
2203 if (!isUnreachable) {
2204 RuleIterator ruleIterator(observedRules, observedRule);
2205 while (
const auto *rulePtr = ruleIterator.next()) {
2206 if (isUnreachable) {
2209 const auto &rule2 = *rulePtr;
2210 if (rule2.type == rule.type && isCompatible(rule2) && rule.char0 == rule2.char0 && rule.char1 == rule2.char1) {
2211 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2217 case Context::Rule::Type::RegExpr: {
2218 if (rule.isDotRegex) {
2219 dotRegex.append(rule,
nullptr);
2224 RuleIterator ruleIterator(observedRules, observedRule);
2225 while (
const auto *rulePtr = ruleIterator.next()) {
2226 if (isUnreachable) {
2229 const auto &rule2 = *rulePtr;
2230 if (rule2.type == Context::Rule::Type::RegExpr && isCompatible(rule2) && rule.insensitive == rule2.insensitive
2231 && rule.dynamic == rule2.dynamic && rule.sanitizedString.startsWith(rule2.sanitizedString)) {
2232 bool add = (rule.sanitizedString.startsWith(rule2.string) || rule.sanitizedString.size() < rule2.sanitizedString.size() + 2);
2236 auto c1 = rule.sanitizedString[rule2.sanitizedString.size()].unicode();
2237 auto c2 = rule.sanitizedString[rule2.sanitizedString.size() + 1].unicode();
2238 auto c3 = rule2.sanitizedString.back().unicode();
2239 if (c3 ==
'*' || c3 ==
'?' || c3 ==
'+') {
2241 }
else if (c1 ==
'*' || c1 ==
'?') {
2242 add = !((c2 ==
'?' || c2 ==
'+') || (rule.sanitizedString.size() >= rule2.sanitizedString.size() + 3));
2248 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2256 case Context::Rule::Type::WordDetect:
2257 case Context::Rule::Type::StringDetect: {
2259 if (rule.type == Context::Rule::Type::StringDetect && rule.dynamic == XmlBool::True) {
2260 RuleIterator ruleIterator(observedRules, observedRule);
2261 while (
const auto *rulePtr = ruleIterator.next()) {
2262 if (isUnreachable) {
2266 const auto &rule2 = *rulePtr;
2267 if (rule2.type != Context::Rule::Type::StringDetect || rule2.dynamic != XmlBool::True || !isCompatible(rule2)) {
2271 const bool isSensitive = (rule2.insensitive == XmlBool::True);
2273 if ((isSensitive || rule.insensitive != XmlBool::True) && rule.string.startsWith(rule2.string, caseSensitivity)) {
2274 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2283 if (rule.dynamic == XmlBool::True) {
2284 static const QRegularExpression dynamicPosition(QStringLiteral(R
"(^(?:[^%]*|%(?![1-9]))*)"));
2285 auto result = dynamicPosition.match(rule.string);
2286 s = s.
left(result.capturedLength());
2292 if (rule.type == Context::Rule::Type::RegExpr) {
2293 static const QRegularExpression regularChars(QStringLiteral(R
"(^(?:[^.?*+^$[{(\\|]+|\\[-.?*+^$[\]{}()\\|]+|\[[^^\\]\])+)"));
2294 static const QRegularExpression sanitizeChars(QStringLiteral(R
"(\\([-.?*+^$[\]{}()\\|])|\[([^^\\])\])"));
2295 const qsizetype result = regularChars.match(rule.string).capturedLength();
2296 const qsizetype pos = qMin(result, s.
size());
2297 if (rule.string.indexOf(
QLatin1Char(
'|'), pos) < pos) {
2298 sanitizedRegex = rule.string.
left(qMin(result, s.
size()));
2299 sanitizedRegex.
replace(sanitizeChars, QStringLiteral(
"\\1"));
2308 auto t = CharTableArray(detectChars, rule);
2309 if (rule.insensitive != XmlBool::True) {
2310 updateUnreachable1(t.find(s[0]));
2312 QChar c2[]{s[0].toLower(), s[0].toUpper()};
2318 if (s.
size() > 0 && !isUnreachable) {
2320 RuleAndInclude detect2CharsInsensitives[]{{}, {}, {}, {}};
2322 RuleIterator ruleIterator(observedRules, observedRule);
2323 while (
const auto *rulePtr = ruleIterator.next()) {
2324 if (isUnreachable) {
2327 const auto &rule2 = *rulePtr;
2328 const bool isSensitive = (rule2.insensitive == XmlBool::True);
2331 switch (rule2.type) {
2333 case Context::Rule::Type::Detect2Chars:
2334 if (isCompatible(rule2) && s.
size() >= 2) {
2335 if (rule.insensitive != XmlBool::True) {
2336 if (rule2.char0 == s[0] && rule2.char1 == s[1]) {
2337 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2342 auto set = [&](RuleAndInclude &x,
QChar c1,
QChar c2) {
2343 if (!x && rule2.char0 == c1 && rule2.char0 == c2) {
2344 x = {&rule2, ruleIterator.currentIncludeRules()};
2347 set(detect2CharsInsensitives[0], s[0].toLower(), s[1].toLower());
2348 set(detect2CharsInsensitives[1], s[0].toLower(), s[1].toUpper());
2349 set(detect2CharsInsensitives[2], s[0].toUpper(), s[1].toUpper());
2350 set(detect2CharsInsensitives[3], s[0].toUpper(), s[1].toLower());
2352 if (detect2CharsInsensitives[0] && detect2CharsInsensitives[1] && detect2CharsInsensitives[2]
2353 && detect2CharsInsensitives[3]) {
2354 isUnreachable =
true;
2355 unreachableBy.
append(detect2CharsInsensitives[0]);
2356 unreachableBy.
append(detect2CharsInsensitives[1]);
2357 unreachableBy.
append(detect2CharsInsensitives[2]);
2358 unreachableBy.
append(detect2CharsInsensitives[3]);
2365 case Context::Rule::Type::StringDetect:
2366 if (isCompatible(rule2) && rule2.dynamic != XmlBool::True && (isSensitive || rule.insensitive != XmlBool::True)
2367 && s.
startsWith(rule2.string, caseSensitivity)) {
2368 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2373 case Context::Rule::Type::WordDetect:
2374 if (rule.type == Context::Rule::Type::WordDetect && isCompatible(rule2) && (isSensitive || rule.insensitive != XmlBool::True)
2375 && 0 == rule.string.compare(rule2.string, caseSensitivity)) {
2376 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2389 case Context::Rule::Type::keyword: {
2390 RuleIterator ruleIterator(observedRules, observedRule);
2391 while (
const auto *rulePtr = ruleIterator.next()) {
2392 if (isUnreachable) {
2395 const auto &rule2 = *rulePtr;
2396 if (rule2.type == Context::Rule::Type::keyword && isCompatible(rule2) && rule.string == rule2.string) {
2397 updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
2409 case Context::Rule::Type::IncludeRules:
2410 if (observedRule.includeRules && !observedRule.hasResolvedIncludeRules()) {
2414 if (
auto &ruleAndInclude = includeContexts[rule.context.context]) {
2415 updateUnreachable1(ruleAndInclude);
2417 ruleAndInclude.rule = &rule;
2420 for (
const auto *rulePtr : rule.includedIncludeRules) {
2421 includeContexts.
insert(rulePtr->context.context, RuleAndInclude{rulePtr, &rule});
2424 if (observedRule.includeRules) {
2428 for (
const auto *rulePtr : rule.includedRules) {
2429 const auto &rule2 = *rulePtr;
2430 switch (rule2.type) {
2431 case Context::Rule::Type::AnyChar: {
2432 auto tables = CharTableArray(detectChars, rule2);
2433 tables.removeNonSpecialWhenSpecial();
2434 tables.append(rule2.string, rule2, &rule);
2438 case Context::Rule::Type::DetectChar: {
2439 auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
2440 auto tables = CharTableArray(chars4, rule2);
2441 tables.removeNonSpecialWhenSpecial();
2442 tables.append(rule2.char0, rule2, &rule);
2446 case Context::Rule::Type::DetectSpaces: {
2447 auto tables = CharTableArray(detectChars, rule2);
2448 tables.removeNonSpecialWhenSpecial();
2454 case Context::Rule::Type::HlCChar:
2455 hlCCharRule.setRule(rule2, &rule);
2458 case Context::Rule::Type::HlCHex:
2459 hlCHexRule.setRule(rule2, &rule);
2462 case Context::Rule::Type::HlCOct:
2463 hlCOctRule.setRule(rule2, &rule);
2466 case Context::Rule::Type::HlCStringChar:
2467 hlCStringCharRule.setRule(rule2, &rule);
2470 case Context::Rule::Type::Int:
2471 intRule.setRule(rule2, &rule);
2474 case Context::Rule::Type::Float:
2475 floatRule.setRule(rule2, &rule);
2478 case Context::Rule::Type::LineContinue: {
2479 auto tables = CharTableArray(lineContinueChars, rule2);
2480 tables.removeNonSpecialWhenSpecial();
2481 tables.append(rule2.char0, rule2, &rule);
2485 case Context::Rule::Type::RegExpr:
2486 if (rule2.isDotRegex) {
2487 dotRegex.append(rule2, &rule);
2491 case Context::Rule::Type::WordDetect:
2492 case Context::Rule::Type::StringDetect:
2493 case Context::Rule::Type::Detect2Chars:
2494 case Context::Rule::Type::IncludeRules:
2495 case Context::Rule::Type::DetectIdentifier:
2496 case Context::Rule::Type::keyword:
2497 case Context::Rule::Type::Unknown:
2498 case Context::Rule::Type::RangeDetect:
2504 case Context::Rule::Type::Unknown:
2508 if (observedRule.includeRules && !observedRule.hasResolvedIncludeRules()) {
2509 auto &unreachableIncludedRule = unreachableIncludedRules[&rule];
2510 if (isUnreachable && unreachableIncludedRule.alwaysUnreachable) {
2511 unreachableIncludedRule.unreachableBy.append(unreachableBy);
2513 unreachableIncludedRule.alwaysUnreachable =
false;
2515 }
else if (isUnreachable) {
2519 for (
auto &ruleAndInclude : unreachableBy) {
2520 message += QStringLiteral(
"line ");
2521 if (ruleAndInclude.includeRules) {
2523 message += QStringLiteral(
" [by '");
2524 message += ruleAndInclude.includeRules->context.name;
2525 message += QStringLiteral(
"' line ");
2527 if (ruleAndInclude.includeRules->filename != ruleAndInclude.rule->filename) {
2528 message += QStringLiteral(
" (");
2529 message += ruleAndInclude.rule->filename;
2536 message += QStringLiteral(
", ");
2539 qWarning() << filename <<
"line" << rule.line <<
"unreachable rule by" << message;
2549 bool suggestRuleMerger(
const QString &filename,
const Context &context)
const
2551 bool success =
true;
2553 if (context.rules.isEmpty()) {
2557 auto it = context.rules.begin();
2558 const auto end = context.rules.end() - 1;
2560 for (; it <
end; ++it) {
2562 auto &rule2 = it[1];
2564 auto isCommonCompatible = [&] {
2565 if (rule1.lookAhead != rule2.lookAhead) {
2569 if (rule1.lookAhead != XmlBool::True && rule1.attribute != rule2.attribute) {
2573 return rule1.beginRegion == rule2.beginRegion
2574 && rule1.endRegion == rule2.endRegion
2575 && rule1.firstNonSpace == rule2.firstNonSpace
2576 && rule1.context.context == rule2.context.context
2577 && rule1.context.popCount == rule2.context.popCount;
2581 switch (rule1.type) {
2583 case Context::Rule::Type::AnyChar:
2584 case Context::Rule::Type::DetectChar:
2585 if ((rule2.type == Context::Rule::Type::AnyChar || rule2.type == Context::Rule::Type::DetectChar) && isCommonCompatible()
2586 && rule1.column == rule2.column) {
2587 qWarning() << filename <<
"line" << rule2.line <<
"can be merged as AnyChar with the previous rule";
2593 case Context::Rule::Type::RegExpr:
2594 if (rule2.type == Context::Rule::Type::RegExpr && isCommonCompatible() && rule1.dynamic == rule2.dynamic
2595 && (rule1.column == rule2.column || (rule1.column <= 0 && rule2.column <= 0))) {
2596 qWarning() << filename <<
"line" << rule2.line <<
"can be merged with the previous rule";
2601 case Context::Rule::Type::DetectSpaces:
2602 case Context::Rule::Type::HlCChar:
2603 case Context::Rule::Type::HlCHex:
2604 case Context::Rule::Type::HlCOct:
2605 case Context::Rule::Type::HlCStringChar:
2606 case Context::Rule::Type::Int:
2607 case Context::Rule::Type::Float:
2608 case Context::Rule::Type::LineContinue:
2609 case Context::Rule::Type::WordDetect:
2610 case Context::Rule::Type::StringDetect:
2611 case Context::Rule::Type::Detect2Chars:
2612 case Context::Rule::Type::IncludeRules:
2613 case Context::Rule::Type::DetectIdentifier:
2614 case Context::Rule::Type::keyword:
2615 case Context::Rule::Type::Unknown:
2616 case Context::Rule::Type::RangeDetect:
2632 void resolveContextName(Definition &definition, Context &context, ContextName &contextName,
int line)
2636 contextName.stay =
true;
2639 contextName.stay =
true;
2640 contextName.context = &context;
2642 qWarning() << definition.filename <<
"line" << line <<
"invalid context in" << context.name;
2648 ++contextName.popCount;
2655 qWarning() << definition.filename <<
"line" << line <<
"'!' missing between '#pop' and context name" << context.name;
2661 const int idx =
name.
indexOf(QStringLiteral(
"##"));
2663 auto it = definition.contexts.find(
name.toString());
2664 if (it != definition.contexts.end()) {
2665 contextName.context = &*it;
2668 auto defName =
name.
mid(idx + 2);
2669 auto it = m_definitions.find(defName.toString());
2670 if (it != m_definitions.end()) {
2671 auto listName =
name.
left(idx).toString();
2672 definition.referencedDefinitions.insert(&*it);
2673 auto ctxIt = it->contexts.find(listName.isEmpty() ? it->firstContextName : listName);
2674 if (ctxIt != it->contexts.end()) {
2675 contextName.context = &*ctxIt;
2678 qWarning() << definition.filename <<
"line" << line <<
"unknown definition in" << context.name;
2683 if (!contextName.context) {
2684 qWarning() << definition.filename <<
"line" << line <<
"unknown context" <<
name <<
"in" << context.name;
2692 Definition *m_currentDefinition =
nullptr;
2693 Keywords *m_currentKeywords =
nullptr;
2694 Context *m_currentContext =
nullptr;
2695 bool m_success =
true;
2702 QFile file(fileName);
2709 while (!xml.
atEnd()) {
2719 qWarning() <<
"XML error while reading" << fileName <<
" - " << qPrintable(xml.
errorString()) <<
"@ offset" << xml.
characterOffset();
2737 if (extensionParts.
isEmpty()) {
2742 for (
const auto &extension : extensionParts) {
2743 for (
const auto c : extension) {
2759 qWarning() <<
"invalid character" << c <<
"seen in extensions wildcard";
2770int main(
int argc,
char *argv[])
2776 if (app.arguments().size() < 3) {
2782 XMLPlatformUtils::Initialize();
2783 auto cleanup = qScopeGuard(XMLPlatformUtils::Terminate);
2788 XMLGrammarPoolImpl xsd(XMLPlatformUtils::fgMemoryManager);
2791 SAX2XMLReaderImpl parser(XMLPlatformUtils::fgMemoryManager, &xsd);
2792 init_parser(parser);
2794 CustomErrorHandler eh(&messages);
2795 parser.setErrorHandler(&eh);
2798 const auto xsdFile = app.arguments().at(2);
2799 if (!parser.loadGrammar((
const char16_t *)xsdFile.utf16(), Grammar::SchemaGrammarType,
true) || eh.failed()) {
2800 qWarning(
"Failed to parse XSD %s: %s", qPrintable(xsdFile), qPrintable(messages));
2808 const QString hlFilenamesListing = app.arguments().value(3);
2809 if (hlFilenamesListing.
isEmpty()) {
2813 QStringList hlFilenames = readListing(hlFilenamesListing);
2815 qWarning(
"Failed to read %s", qPrintable(hlFilenamesListing));
2820 const QStringList textAttributes =
QStringList() << QStringLiteral(
"name") << QStringLiteral(
"alternativeNames") << QStringLiteral(
"section")
2821 << QStringLiteral(
"mimetype") << QStringLiteral(
"extensions") << QStringLiteral(
"style")
2822 << QStringLiteral(
"author") << QStringLiteral(
"license") << QStringLiteral(
"indenter");
2825 HlFilesChecker filesChecker;
2828 for (
const QString &hlFilename :
std::as_const(hlFilenames)) {
2829 QFile hlFile(hlFilename);
2831 qWarning(
"Failed to open %s", qPrintable(hlFilename));
2838 SAX2XMLReaderImpl parser(XMLPlatformUtils::fgMemoryManager, &xsd);
2839 init_parser(parser);
2841 CustomErrorHandler eh(&messages);
2842 parser.setErrorHandler(&eh);
2845 parser.parse((
const char16_t *)hlFile.fileName().utf16());
2849 qWarning(
"Failed to validate XML %s: %s", qPrintable(hlFile.fileName()), qPrintable(messages));
2872 for (
const QString &attribute :
std::as_const(textAttributes)) {
2877 if (!checkExtensions(hl[QStringLiteral(
"extensions")].
toString())) {
2878 qWarning() << hlFilename <<
"'extensions' wildcards invalid:" << hl[QStringLiteral(
"extensions")].toString();
2890 hl[QStringLiteral(
"nameUtf8")] = hl[QStringLiteral(
"name")].toString().toUtf8();
2891 hl[QStringLiteral(
"sectionUtf8")] = hl[QStringLiteral(
"section")].toString().toUtf8();
2896 const QString hlName = hl[QStringLiteral(
"name")].toString();
2898 filesChecker.setDefinition(xml.
attributes().
value(QStringLiteral(
"kateversion")), hlFilename, hlName);
2901 while (!xml.
atEnd()) {
2903 filesChecker.processElement(xml);
2912 filesChecker.resolveContexts();
2914 if (!filesChecker.check()) {
2924 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)
const QList< QKeySequence > & next()
const QList< QKeySequence > & find()
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
const QList< QKeySequence > & replace()
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 left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) 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
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QStringView left(qsizetype length) const const
bool contains(QChar c, Qt::CaseSensitivity cs) const const
QChar first() const const
bool isNull() const const
qsizetype size() 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