Changeset View
Changeset View
Standalone View
Standalone View
src/indexer/katehighlightingindexer.cpp
Show First 20 Lines • Show All 317 Lines • ▼ Show 20 Line(s) | |||||
318 | private: | 318 | private: | ||
319 | QString m_filename; | 319 | QString m_filename; | ||
320 | QSet<QString> m_usedNames; | 320 | QSet<QString> m_usedNames; | ||
321 | QSet<QString> m_existingNames; | 321 | QSet<QString> m_existingNames; | ||
322 | bool m_success = true; | 322 | bool m_success = true; | ||
323 | }; | 323 | }; | ||
324 | 324 | | |||
325 | /** | 325 | /** | ||
326 | * Helper class to search for non-existing contexts | 326 | * Helper class to search for non-existing contexts and invalid version | ||
327 | */ | 327 | */ | ||
328 | class ContextChecker | 328 | class ContextChecker | ||
329 | { | 329 | { | ||
330 | public: | 330 | public: | ||
331 | void setKateVersion(QStringRef verStr, const QString &hlFilename, const QString &hlName) | ||||
332 | { | ||||
333 | const auto idx = verStr.indexOf(QLatin1Char('.')); | ||||
334 | if (idx <= 0) { | ||||
335 | qWarning() << hlFilename << "invalid kateversion" << verStr; | ||||
336 | m_success = false; | ||||
337 | } else { | ||||
338 | auto &language = m_contextMap[hlName]; | ||||
339 | language.version = {verStr.left(idx).toInt(), verStr.mid(idx + 1).toInt()}; | ||||
340 | } | ||||
341 | } | ||||
342 | | ||||
331 | void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml) | 343 | void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml) | ||
332 | { | 344 | { | ||
333 | if (xml.name() == QLatin1String("context")) { | 345 | if (xml.name() == QLatin1String("context")) { | ||
334 | auto & language = m_contextMap[hlName]; | 346 | auto & language = m_contextMap[hlName]; | ||
335 | language.hlFilename = hlFilename; | 347 | language.hlFilename = hlFilename; | ||
336 | const QString name = xml.attributes().value(QLatin1String("name")).toString(); | 348 | const QString name = xml.attributes().value(QLatin1String("name")).toString(); | ||
337 | if (language.isFirstContext) { | 349 | if (language.isFirstContext) { | ||
338 | language.isFirstContext = false; | 350 | language.isFirstContext = false; | ||
Show All 10 Lines | |||||
349 | if (xml.attributes().value(QLatin1String("fallthroughContext")).toString() == QLatin1String("#stay")) { | 361 | if (xml.attributes().value(QLatin1String("fallthroughContext")).toString() == QLatin1String("#stay")) { | ||
350 | qWarning() << hlFilename << "possible infinite loop due to fallthroughContext=\"#stay\" in context " << name; | 362 | qWarning() << hlFilename << "possible infinite loop due to fallthroughContext=\"#stay\" in context " << name; | ||
351 | m_success = false; | 363 | m_success = false; | ||
352 | } | 364 | } | ||
353 | 365 | | |||
354 | processContext(hlName, xml.attributes().value(QLatin1String("lineEndContext")).toString()); | 366 | processContext(hlName, xml.attributes().value(QLatin1String("lineEndContext")).toString()); | ||
355 | processContext(hlName, xml.attributes().value(QLatin1String("lineEmptyContext")).toString()); | 367 | processContext(hlName, xml.attributes().value(QLatin1String("lineEmptyContext")).toString()); | ||
356 | processContext(hlName, xml.attributes().value(QLatin1String("fallthroughContext")).toString()); | 368 | processContext(hlName, xml.attributes().value(QLatin1String("fallthroughContext")).toString()); | ||
369 | } else if (xml.name() == QLatin1String("include")) { | ||||
370 | // <include> tag inside <list> | ||||
371 | processVersion(hlFilename, hlName, xml, {5, 53}, QLatin1String("<include>")); | ||||
357 | } else { | 372 | } else { | ||
358 | if (xml.attributes().hasAttribute(QLatin1String("context"))) { | 373 | if (xml.attributes().hasAttribute(QLatin1String("context"))) { | ||
359 | const QString context = xml.attributes().value(QLatin1String("context")).toString(); | 374 | const QString context = xml.attributes().value(QLatin1String("context")).toString(); | ||
360 | if (context.isEmpty()) { | 375 | if (context.isEmpty()) { | ||
361 | qWarning() << hlFilename << "Missing context name in line" << xml.lineNumber(); | 376 | qWarning() << hlFilename << "Missing context name in line" << xml.lineNumber(); | ||
362 | m_success = false; | 377 | m_success = false; | ||
363 | } else { | 378 | } else { | ||
364 | processContext(hlName, context); | 379 | processContext(hlName, context); | ||
365 | } | 380 | } | ||
366 | } | 381 | } | ||
367 | } | 382 | } | ||
368 | } | 383 | } | ||
369 | 384 | | |||
370 | bool check() const | 385 | bool check() const | ||
371 | { | 386 | { | ||
372 | bool success = m_success; | 387 | bool success = m_success; | ||
388 | | ||||
389 | // recursive search for the required miximal version | ||||
390 | struct GetRequiredVersion | ||||
391 | { | ||||
392 | QHash<const Language*, Version> versionMap; | ||||
393 | | ||||
394 | Version operator()(const QHash<QString, Language> &contextMap, const Language &language) | ||||
395 | { | ||||
396 | auto& version = versionMap[&language]; | ||||
397 | if (version < language.version) { | ||||
398 | version = language.version; | ||||
399 | for (auto &languageName : language.usedLanguageName) { | ||||
400 | auto it = contextMap.find(languageName); | ||||
401 | if (it != contextMap.end()) { | ||||
402 | version = std::max(operator()(contextMap, *it), version); | ||||
403 | } | ||||
404 | } | ||||
405 | } | ||||
406 | return version; | ||||
407 | }; | ||||
408 | }; | ||||
409 | GetRequiredVersion getRequiredVersion; | ||||
410 | | ||||
373 | for (auto &language : m_contextMap) { | 411 | for (auto &language : m_contextMap) { | ||
374 | const auto invalidContextNames = language.usedContextNames - language.existingContextNames; | 412 | const auto invalidContextNames = language.usedContextNames - language.existingContextNames; | ||
375 | if (!invalidContextNames.isEmpty()) { | 413 | if (!invalidContextNames.isEmpty()) { | ||
376 | qWarning() << language.hlFilename << "Reference of non-existing contexts:" << invalidContextNames; | 414 | qWarning() << language.hlFilename << "Reference of non-existing contexts:" << invalidContextNames; | ||
377 | success = false; | 415 | success = false; | ||
378 | } | 416 | } | ||
379 | 417 | | |||
380 | const auto unusedNames = language.existingContextNames - language.usedContextNames; | 418 | const auto unusedNames = language.existingContextNames - language.usedContextNames; | ||
381 | if (!unusedNames.isEmpty()) { | 419 | if (!unusedNames.isEmpty()) { | ||
382 | qWarning() << language.hlFilename << "Unused contexts:" << unusedNames; | 420 | qWarning() << language.hlFilename << "Unused contexts:" << unusedNames; | ||
383 | success = false; | 421 | success = false; | ||
384 | } | 422 | } | ||
423 | | ||||
424 | auto requiredVersion = getRequiredVersion(m_contextMap, language); | ||||
425 | if (language.version < requiredVersion) { | ||||
426 | qWarning().nospace() << language.hlFilename << " depends on a language in version " << requiredVersion.major << "." << requiredVersion.minor << ". Please, increase kateversion."; | ||||
427 | success = false; | ||||
428 | } | ||||
385 | } | 429 | } | ||
386 | 430 | | |||
387 | return success; | 431 | return success; | ||
388 | } | 432 | } | ||
389 | 433 | | |||
390 | private: | 434 | private: | ||
391 | //! Extract the referenced context name and language. | 435 | //! Extract the referenced context name and language. | ||
392 | //! Some input / output examples are: | 436 | //! Some input / output examples are: | ||
Show All 15 Lines | 444 | { | |||
408 | // handle cross-language context references | 452 | // handle cross-language context references | ||
409 | if (context.contains(QStringLiteral("##"))) { | 453 | if (context.contains(QStringLiteral("##"))) { | ||
410 | const QStringList list = context.split(QStringLiteral("##"), QString::SkipEmptyParts); | 454 | const QStringList list = context.split(QStringLiteral("##"), QString::SkipEmptyParts); | ||
411 | if (list.size() == 1) { | 455 | if (list.size() == 1) { | ||
412 | // nothing to do, other language is included: e.g. ##Doxygen | 456 | // nothing to do, other language is included: e.g. ##Doxygen | ||
413 | } else if (list.size() == 2) { | 457 | } else if (list.size() == 2) { | ||
414 | // specific context of other language, e.g. Comment##ISO C++ | 458 | // specific context of other language, e.g. Comment##ISO C++ | ||
415 | m_contextMap[list[1]].usedContextNames.insert(list[0]); | 459 | m_contextMap[list[1]].usedContextNames.insert(list[0]); | ||
460 | m_contextMap[language].usedLanguageName.insert(list[1]); | ||||
416 | } | 461 | } | ||
417 | return; | 462 | return; | ||
418 | } | 463 | } | ||
419 | 464 | | |||
420 | // handle #pop!context" (#pop was already removed above) | 465 | // handle #pop!context" (#pop was already removed above) | ||
421 | if (context.startsWith(QLatin1Char('!'))) | 466 | if (context.startsWith(QLatin1Char('!'))) | ||
422 | context.remove(0, 1); | 467 | context.remove(0, 1); | ||
423 | 468 | | |||
424 | if (!context.isEmpty()) | 469 | if (!context.isEmpty()) | ||
425 | m_contextMap[language].usedContextNames.insert(context); | 470 | m_contextMap[language].usedContextNames.insert(context); | ||
426 | } | 471 | } | ||
427 | 472 | | |||
428 | private: | 473 | private: | ||
474 | struct Version | ||||
475 | { | ||||
476 | int major; | ||||
477 | int minor; | ||||
478 | | ||||
479 | Version(int major = 0, int minor = 0) | ||||
480 | : major(major) | ||||
481 | , minor(minor) | ||||
482 | {} | ||||
483 | | ||||
484 | bool operator<(const Version &version) const | ||||
485 | { | ||||
486 | return major < version.major || (major == version.major && minor < version.minor); | ||||
487 | } | ||||
488 | }; | ||||
489 | | ||||
490 | void processVersion(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml, Version const& requiredVersion, QLatin1String item) | ||||
491 | { | ||||
492 | auto &language = m_contextMap[hlName]; | ||||
493 | | ||||
494 | if (language.version < requiredVersion) { | ||||
495 | qWarning().nospace() << hlFilename << " " << item << " in line " << xml.lineNumber() << " is only available since version " << requiredVersion.major << "." << requiredVersion.minor << ". Please, increase kateversion."; | ||||
496 | // update the version to cancel future warnings | ||||
497 | language.version = requiredVersion; | ||||
498 | m_success = false; | ||||
499 | } | ||||
500 | } | ||||
501 | | ||||
429 | class Language | 502 | class Language | ||
430 | { | 503 | { | ||
431 | public: | 504 | public: | ||
432 | // filename on disk or in Qt resource | 505 | // filename on disk or in Qt resource | ||
433 | QString hlFilename; | 506 | QString hlFilename; | ||
434 | 507 | | |||
435 | // Is true, if the first context in xml file is encountered, and | 508 | // Is true, if the first context in xml file is encountered, and | ||
436 | // false in all other cases. This is required, since the first context | 509 | // false in all other cases. This is required, since the first context | ||
437 | // is typically not referenced explicitly. So we will simply add the | 510 | // is typically not referenced explicitly. So we will simply add the | ||
438 | // first context to the usedContextNames list. | 511 | // first context to the usedContextNames list. | ||
439 | bool isFirstContext = true; | 512 | bool isFirstContext = true; | ||
440 | 513 | | |||
441 | // holds all contexts that were referenced | 514 | // holds all contexts that were referenced | ||
442 | QSet<QString> usedContextNames; | 515 | QSet<QString> usedContextNames; | ||
443 | 516 | | |||
444 | // holds all existing context names | 517 | // holds all existing context names | ||
445 | QSet<QString> existingContextNames; | 518 | QSet<QString> existingContextNames; | ||
519 | | ||||
520 | // holds all existing language names | ||||
521 | QSet<QString> usedLanguageName; | ||||
522 | | ||||
523 | // kateversion language attribute | ||||
524 | Version version; | ||||
446 | }; | 525 | }; | ||
447 | 526 | | |||
448 | /** | 527 | /** | ||
449 | * "Language name" to "Language" map. | 528 | * "Language name" to "Language" map. | ||
450 | * Example key: "Doxygen" | 529 | * Example key: "Doxygen" | ||
451 | */ | 530 | */ | ||
452 | QHash<QString, Language> m_contextMap; | 531 | QHash<QString, Language> m_contextMap; | ||
453 | bool m_success = true; | 532 | bool m_success = true; | ||
▲ Show 20 Lines • Show All 148 Lines • ▼ Show 20 Line(s) | 647 | #endif | |||
602 | const QString hidden = xml.attributes().value(QLatin1String("hidden")).toString(); | 681 | const QString hidden = xml.attributes().value(QLatin1String("hidden")).toString(); | ||
603 | hl[QStringLiteral("hidden")] = (hidden == QLatin1String("true") || hidden == QLatin1String("1")); | 682 | hl[QStringLiteral("hidden")] = (hidden == QLatin1String("true") || hidden == QLatin1String("1")); | ||
604 | 683 | | |||
605 | // remember hl | 684 | // remember hl | ||
606 | hls[QFileInfo(hlFile).fileName()] = hl; | 685 | hls[QFileInfo(hlFile).fileName()] = hl; | ||
607 | 686 | | |||
608 | AttributeChecker attributeChecker(hlFilename); | 687 | AttributeChecker attributeChecker(hlFilename); | ||
609 | KeywordChecker keywordChecker(hlFilename); | 688 | KeywordChecker keywordChecker(hlFilename); | ||
689 | | ||||
610 | const QString hlName = hl[QStringLiteral("name")].toString(); | 690 | const QString hlName = hl[QStringLiteral("name")].toString(); | ||
611 | 691 | | |||
692 | contextChecker.setKateVersion(xml.attributes().value(QStringLiteral("kateversion")), hlFilename, hlName); | ||||
693 | | ||||
612 | // scan for broken regex or keywords with spaces | 694 | // scan for broken regex or keywords with spaces | ||
613 | while (!xml.atEnd()) { | 695 | while (!xml.atEnd()) { | ||
614 | xml.readNext(); | 696 | xml.readNext(); | ||
615 | if (!xml.isStartElement()) { | 697 | if (!xml.isStartElement()) { | ||
616 | continue; | 698 | continue; | ||
617 | } | 699 | } | ||
618 | 700 | | |||
619 | // search for used/existing contexts if applicable | 701 | // search for used/existing contexts if applicable | ||
▲ Show 20 Lines • Show All 67 Lines • Show Last 20 Lines |