Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qqmljslinter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljslinter_p.h"
5
7
8#include <QtQmlCompiler/private/qqmljsimporter_p.h>
9#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
10#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h>
11
12#include <QtCore/qjsonobject.h>
13#include <QtCore/qfileinfo.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/qpluginloader.h>
16#include <QtCore/qlibraryinfo.h>
17#include <QtCore/qdir.h>
18#include <QtCore/private/qduplicatetracker_p.h>
19#include <QtCore/qscopedpointer.h>
20
21#include <QtQmlCompiler/private/qqmlsa_p.h>
22#include <QtQmlCompiler/private/qqmljsloggingutils_p.h>
23
24#if QT_CONFIG(library)
25# include <QtCore/qdiriterator.h>
26# include <QtCore/qlibrary.h>
27#endif
28
29#include <QtQml/private/qqmljslexer_p.h>
30#include <QtQml/private/qqmljsparser_p.h>
31#include <QtQml/private/qqmljsengine_p.h>
32#include <QtQml/private/qqmljsastvisitor_p.h>
33#include <QtQml/private/qqmljsast_p.h>
34#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
35
36
38
39using namespace Qt::StringLiterals;
40
42{
43public:
44 CodegenWarningInterface(QQmlJSLogger *logger) : m_logger(logger) { }
45
47 QQmlJS::SourceLocation declarationLocation,
48 QQmlJS::SourceLocation accessLocation) override
49 {
51 m_logger->log(
52 u"Variable \"%1\" is used here before its declaration. The declaration is at %2:%3."_s
53 .arg(name)
54 .arg(declarationLocation.startLine)
55 .arg(declarationLocation.startColumn),
56 qmlVarUsedBeforeDeclaration, accessLocation);
57 }
58
59private:
60 QQmlJSLogger *m_logger;
61};
62
64{
66}
67
68QQmlJSLinter::QQmlJSLinter(const QStringList &importPaths, const QStringList &pluginPaths,
69 bool useAbsolutePath)
70 : m_useAbsolutePath(useAbsolutePath),
71 m_enablePlugins(true),
72 m_importer(importPaths, nullptr, true)
73{
74 m_plugins = loadPlugins(pluginPaths);
75}
76
78 : m_name(std::move(plugin.m_name))
79 , m_description(std::move(plugin.m_description))
80 , m_version(std::move(plugin.m_version))
81 , m_author(std::move(plugin.m_author))
82 , m_categories(std::move(plugin.m_categories))
83 , m_instance(std::move(plugin.m_instance))
84 , m_loader(std::move(plugin.m_loader))
85 , m_isBuiltin(std::move(plugin.m_isBuiltin))
86 , m_isInternal(std::move(plugin.m_isInternal))
87 , m_isValid(std::move(plugin.m_isValid))
88{
89 // Mark the old Plugin as invalid and make sure it doesn't delete the loader
90 Q_ASSERT(!plugin.m_loader);
91 plugin.m_instance = nullptr;
92 plugin.m_isValid = false;
93}
94
95#if QT_CONFIG(library)
97{
98 m_loader = std::make_unique<QPluginLoader>(path);
99 if (!parseMetaData(m_loader->metaData(), path))
100 return;
101
102 QObject *object = m_loader->instance();
103 if (!object)
104 return;
105
106 m_instance = qobject_cast<QQmlSA::LintPlugin *>(object);
107 if (!m_instance)
108 return;
109
110 m_isValid = true;
111}
112#endif
113
115{
116 if (!parseMetaData(staticPlugin.metaData(), u"built-in"_s))
117 return;
118
119 m_instance = qobject_cast<QQmlSA::LintPlugin *>(staticPlugin.instance());
120 if (!m_instance)
121 return;
122
123 m_isValid = true;
124}
125
127{
128#if QT_CONFIG(library)
129 if (m_loader != nullptr) {
130 m_loader->unload();
131 m_loader->deleteLater();
132 }
133#endif
134}
135
136bool QQmlJSLinter::Plugin::parseMetaData(const QJsonObject &metaData, QString pluginName)
137{
139
140 if (metaData[u"IID"].toString() != pluginIID)
141 return false;
142
143 QJsonObject pluginMetaData = metaData[u"MetaData"].toObject();
144
145 for (const QString &requiredKey :
146 { u"name"_s, u"version"_s, u"author"_s, u"loggingCategories"_s }) {
147 if (!pluginMetaData.contains(requiredKey)) {
148 qWarning() << pluginName << "is missing the required " << requiredKey
149 << "metadata, skipping";
150 return false;
151 }
152 }
153
154 m_name = pluginMetaData[u"name"].toString();
155 m_author = pluginMetaData[u"author"].toString();
156 m_version = pluginMetaData[u"version"].toString();
157 m_description = pluginMetaData[u"description"].toString(u"-/-"_s);
158 m_isInternal = pluginMetaData[u"isInternal"].toBool(false);
159
160 if (!pluginMetaData[u"loggingCategories"].isArray()) {
161 qWarning() << pluginName << "has loggingCategories which are not an array, skipping";
162 return false;
163 }
164
165 QJsonArray categories = pluginMetaData[u"loggingCategories"].toArray();
166
167 for (const QJsonValue value : categories) {
168 if (!value.isObject()) {
169 qWarning() << pluginName << "has invalid loggingCategories entries, skipping";
170 return false;
171 }
172
173 const QJsonObject object = value.toObject();
174
175 for (const QString &requiredKey : { u"name"_s, u"description"_s }) {
176 if (!object.contains(requiredKey)) {
177 qWarning() << pluginName << " logging category is missing the required "
178 << requiredKey << "metadata, skipping";
179 return false;
180 }
181 }
182
183 const auto it = object.find("enabled"_L1);
184 const bool ignored = (it != object.end() && !it->toBool());
185
186 const QString categoryId =
187 (m_isInternal ? u""_s : u"Plugin."_s) + m_name + u'.' + object[u"name"].toString();
188 m_categories << QQmlJS::LoggerCategory{ categoryId, categoryId,
189 object["description"_L1].toString(), QtWarningMsg,
190 ignored };
191 }
192
193 return true;
194}
195
196std::vector<QQmlJSLinter::Plugin> QQmlJSLinter::loadPlugins(QStringList paths)
197{
198 std::vector<Plugin> plugins;
199
200 QDuplicateTracker<QString> seenPlugins;
201
202 for (const QStaticPlugin &staticPlugin : QPluginLoader::staticPlugins()) {
203 Plugin plugin(staticPlugin);
204 if (!plugin.isValid())
205 continue;
206
207 if (seenPlugins.hasSeen(plugin.name().toLower())) {
208 qWarning() << "Two plugins named" << plugin.name()
209 << "present, make sure no plugins are duplicated. The second plugin will "
210 "not be loaded.";
211 continue;
212 }
213
214 plugins.push_back(std::move(plugin));
215 }
216
217#if QT_CONFIG(library)
218 for (const QString &pluginDir : paths) {
219 QDirIterator it { pluginDir };
220
221 while (it.hasNext()) {
222 auto potentialPlugin = it.next();
223
224 if (!QLibrary::isLibrary(potentialPlugin))
225 continue;
226
227 Plugin plugin(potentialPlugin);
228
229 if (!plugin.isValid())
230 continue;
231
232 if (seenPlugins.hasSeen(plugin.name().toLower())) {
233 qWarning() << "Two plugins named" << plugin.name()
234 << "present, make sure no plugins are duplicated. The second plugin "
235 "will not be loaded.";
236 continue;
237 }
238
239 plugins.push_back(std::move(plugin));
240 }
241 }
242#endif
243
244 return plugins;
245}
246
247void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
248 const QList<QQmlJS::SourceLocation> &comments)
249{
250 QHash<int, QSet<QString>> disablesPerLine;
251 QHash<int, QSet<QString>> enablesPerLine;
252 QHash<int, QSet<QString>> oneLineDisablesPerLine;
253
254 const QString code = logger->code();
255 const QStringList lines = code.split(u'\n');
256 const auto loggerCategories = logger->categories();
257
258 for (const auto &loc : comments) {
259 const QString comment = code.mid(loc.offset, loc.length);
260 if (!comment.startsWith(u" qmllint ") && !comment.startsWith(u"qmllint "))
261 continue;
262
263 QStringList words = comment.split(u' ', Qt::SkipEmptyParts);
264 if (words.size() < 2)
265 continue;
266
268 for (qsizetype i = 2; i < words.size(); i++) {
269 const QString category = words.at(i);
270 const auto categoryExists = std::any_of(
271 loggerCategories.cbegin(), loggerCategories.cend(),
272 [&](const QQmlJS::LoggerCategory &cat) { return cat.id().name() == category; });
273
274 if (categoryExists)
276 else
277 logger->log(u"qmllint directive on unknown category \"%1\""_s.arg(category),
279 }
280
281 if (categories.isEmpty()) {
282 for (const auto &option : logger->categories())
284 }
285
286 const QString command = words.at(1);
287 if (command == u"disable"_s) {
288 if (const qsizetype lineIndex = loc.startLine - 1; lineIndex < lines.size()) {
289 const QString line = lines[lineIndex];
290 const QString preComment = line.left(line.indexOf(comment) - 2);
291
292 bool lineHasContent = false;
293 for (qsizetype i = 0; i < preComment.size(); i++) {
294 if (!preComment[i].isSpace()) {
295 lineHasContent = true;
296 break;
297 }
298 }
299
300 if (lineHasContent)
301 oneLineDisablesPerLine[loc.startLine] |= categories;
302 else
303 disablesPerLine[loc.startLine] |= categories;
304 }
305 } else if (command == u"enable"_s) {
306 enablesPerLine[loc.startLine + 1] |= categories;
307 } else {
308 logger->log(u"Invalid qmllint directive \"%1\" provided"_s.arg(command),
310 }
311 }
312
313 if (disablesPerLine.isEmpty() && oneLineDisablesPerLine.isEmpty())
314 return;
315
316 QSet<QString> currentlyDisabled;
317 for (qsizetype i = 1; i <= lines.size(); i++) {
318 currentlyDisabled.unite(disablesPerLine[i]).subtract(enablesPerLine[i]);
319
320 currentlyDisabled.unite(oneLineDisablesPerLine[i]);
321
322 if (!currentlyDisabled.isEmpty())
323 logger->ignoreWarnings(i, currentlyDisabled);
324
325 currentlyDisabled.subtract(oneLineDisablesPerLine[i]);
326 }
327}
328
330 QAnyStringView id, const std::optional<QQmlJSFixSuggestion> &suggestion = {})
331{
332 QJsonObject jsonMessage;
333
335 switch (message.type) {
336 case QtDebugMsg:
337 type = u"debug"_s;
338 break;
339 case QtWarningMsg:
340 type = u"warning"_s;
341 break;
342 case QtCriticalMsg:
343 type = u"critical"_s;
344 break;
345 case QtFatalMsg:
346 type = u"fatal"_s;
347 break;
348 case QtInfoMsg:
349 type = u"info"_s;
350 break;
351 default:
352 type = u"unknown"_s;
353 break;
354 }
355
356 jsonMessage[u"type"_s] = type;
357 jsonMessage[u"id"_s] = id.toString();
358
359 if (message.loc.isValid()) {
360 jsonMessage[u"line"_s] = static_cast<int>(message.loc.startLine);
361 jsonMessage[u"column"_s] = static_cast<int>(message.loc.startColumn);
362 jsonMessage[u"charOffset"_s] = static_cast<int>(message.loc.offset);
363 jsonMessage[u"length"_s] = static_cast<int>(message.loc.length);
364 }
365
366 jsonMessage[u"message"_s] = message.message;
367
368 QJsonArray suggestions;
369 const auto convertLocation = [](const QQmlJS::SourceLocation &source, QJsonObject *target) {
370 target->insert("line"_L1, int(source.startLine));
371 target->insert("column"_L1, int(source.startColumn));
372 target->insert("charOffset"_L1, int(source.offset));
373 target->insert("length"_L1, int(source.length));
374 };
375 if (suggestion.has_value()) {
376 QJsonObject jsonFix {
377 { "message"_L1, suggestion->fixDescription() },
378 { "replacement"_L1, suggestion->replacement() },
379 { "isHint"_L1, !suggestion->isAutoApplicable() },
380 };
381 convertLocation(suggestion->location(), &jsonFix);
382 const QString filename = suggestion->filename();
383 if (!filename.isEmpty())
384 jsonFix.insert("fileName"_L1, filename);
385 suggestions << jsonFix;
386
387 const QString hint = suggestion->hint();
388 if (!hint.isEmpty()) {
389 // We need to keep compatibility with the JSON format.
390 // Therefore the overly verbose encoding of the hint.
391 QJsonObject jsonHint {
392 { "message"_L1, hint },
393 { "replacement"_L1, QString() },
394 { "isHint"_L1, true }
395 };
396 convertLocation(QQmlJS::SourceLocation(), &jsonHint);
397 suggestions << jsonHint;
398 }
399 }
400 jsonMessage[u"suggestions"] = suggestions;
401
402 warnings << jsonMessage;
403
404}
405
406void QQmlJSLinter::processMessages(QJsonArray &warnings)
407{
408 for (const auto &error : m_logger->errors())
409 addJsonWarning(warnings, error, error.id, error.fixSuggestion);
410 for (const auto &warning : m_logger->warnings())
411 addJsonWarning(warnings, warning, warning.id, warning.fixSuggestion);
412 for (const auto &info : m_logger->infos())
413 addJsonWarning(warnings, info, info.id, info.fixSuggestion);
414}
415
417 const QString *fileContents, const bool silent,
418 QJsonArray *json, const QStringList &qmlImportPaths,
419 const QStringList &qmldirFiles,
420 const QStringList &resourceFiles,
422{
423 // Make sure that we don't expose an old logger if we return before a new one is created.
424 m_logger.reset();
425
426 QJsonArray warnings;
428
429 bool success = true;
430
431 QScopeGuard jsonOutput([&] {
432 if (!json)
433 return;
434
435 result[u"filename"_s] = QFileInfo(filename).absoluteFilePath();
436 result[u"warnings"] = warnings;
437 result[u"success"] = success;
438
439 json->append(result);
440 });
441
442 QString code;
443
444 if (fileContents == nullptr) {
445 QFile file(filename);
446 if (!file.open(QFile::ReadOnly)) {
447 if (json) {
449 warnings,
450 QQmlJS::DiagnosticMessage { QStringLiteral("Failed to open file %1: %2")
451 .arg(filename, file.errorString()),
453 qmlImport.name());
454 success = false;
455 } else if (!silent) {
456 qWarning() << "Failed to open file" << filename << file.error();
457 }
458 return FailedToOpen;
459 }
460
462 file.close();
463 } else {
464 code = *fileContents;
465 }
466
467 m_fileContents = code;
468
470 QQmlJS::Lexer lexer(&engine);
471
472 QFileInfo info(filename);
473 const QString lowerSuffix = info.suffix().toLower();
474 const bool isESModule = lowerSuffix == QLatin1String("mjs");
475 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
476
477 lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
478 QQmlJS::Parser parser(&engine);
479
480 success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
481 : parser.parse();
482
483 if (!success) {
484 const auto diagnosticMessages = parser.diagnosticMessages();
485 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
486 if (json) {
487 addJsonWarning(warnings, m, qmlSyntax.name());
488 } else if (!silent) {
489 qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
490 .arg(filename)
491 .arg(m.loc.startLine)
492 .arg(m.loc.startColumn)
493 .arg(m.message);
494 }
495 }
496 return FailedToParse;
497 }
498
499 if (success && !isJavaScript) {
500 const auto check = [&](QQmlJSResourceFileMapper *mapper) {
501 if (m_importer.importPaths() != qmlImportPaths)
502 m_importer.setImportPaths(qmlImportPaths);
503
504 m_importer.setResourceFileMapper(mapper);
505
506 m_logger.reset(new QQmlJSLogger);
507 m_logger->setFileName(m_useAbsolutePath ? info.absoluteFilePath() : filename);
508 m_logger->setCode(code);
509 m_logger->setSilent(silent || json);
511 QQmlJSImportVisitor v { target, &m_importer, m_logger.get(),
513 m_logger->fileName(), m_importer.resourceFileMapper()),
514 qmldirFiles };
515
516 if (m_enablePlugins) {
517 for (const Plugin &plugin : m_plugins) {
518 for (const QQmlJS::LoggerCategory &category : plugin.categories())
519 m_logger->registerCategory(category);
520 }
521 }
522
523 for (auto it = categories.cbegin(); it != categories.cend(); ++it) {
525 continue;
526
527 m_logger->setCategoryIgnored(it->id(), it->isIgnored());
528 m_logger->setCategoryLevel(it->id(), it->level());
529 }
530
531 parseComments(m_logger.get(), engine.comments());
532
533 QQmlJSTypeResolver typeResolver(&m_importer);
534
535 // Type resolving is using document parent mode here so that it produces fewer false
536 // positives on the "parent" property of QQuickItem. It does produce a few false
537 // negatives this way because items can be reparented. Furthermore, even if items are
538 // not reparented, the document parent may indeed not be their visual parent. See
539 // QTBUG-95530. Eventually, we'll need cleverer logic to deal with this.
541 // We don't need to create tracked types and such as we are just linting the code here
542 // and not actually compiling it. The duplicated scopes would cause issues during
543 // linting.
545
546 typeResolver.init(&v, parser.rootNode());
547
548 QQmlJSLiteralBindingCheck literalCheck;
549 literalCheck.run(&v, &typeResolver);
550
552
553 if (m_enablePlugins) {
554 passMan.reset(new QQmlSA::PassManager(&v, &typeResolver));
555
556 for (const Plugin &plugin : m_plugins) {
557 if (!plugin.isValid() || !plugin.isEnabled())
558 continue;
559
560 QQmlSA::LintPlugin *instance = plugin.m_instance;
561 Q_ASSERT(instance);
562 instance->registerPasses(passMan.get(),
564 }
565
566 passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
567 }
568
569 success = !m_logger->hasWarnings() && !m_logger->hasErrors();
570
571 if (m_logger->hasErrors()) {
572 if (json)
573 processMessages(warnings);
574 return;
575 }
576
577 const QStringList resourcePaths = mapper
578 ? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename))
579 : QStringList();
580 const QString resolvedPath =
581 (resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename;
582
583 QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get() };
584 codegen.setTypeResolver(std::move(typeResolver));
585 if (passMan)
586 codegen.setPassManager(passMan.get());
588 const QQmlJSAotFunctionMap &,
589 QString *) { return true; };
590
592
593 QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_s);
594
596 qCompileQmlFile(filename, saveFunction, &codegen, &error, true, &interface,
597 fileContents);
598
599 QList<QQmlJS::DiagnosticMessage> globalWarnings = m_importer.takeGlobalWarnings();
600
601 if (!globalWarnings.isEmpty()) {
602 m_logger->log(QStringLiteral("Type warnings occurred while evaluating file:"),
604 m_logger->processMessages(globalWarnings, qmlImport);
605 }
606
607 success &= !m_logger->hasWarnings() && !m_logger->hasErrors();
608
609 if (json)
610 processMessages(warnings);
611 };
612
613 if (resourceFiles.isEmpty()) {
614 check(nullptr);
615 } else {
616 QQmlJSResourceFileMapper mapper(resourceFiles);
617 check(&mapper);
618 }
619 }
620
621 return success ? LintSuccess : HasWarnings;
622}
623
625 const QString &module, const bool silent, QJsonArray *json,
626 const QStringList &qmlImportPaths, const QStringList &resourceFiles)
627{
628 // Make sure that we don't expose an old logger if we return before a new one is created.
629 m_logger.reset();
630
631 // We can't lint properly if a module has already been pre-cached
632 m_importer.clearCache();
633
634 if (m_importer.importPaths() != qmlImportPaths)
635 m_importer.setImportPaths(qmlImportPaths);
636
637 QQmlJSResourceFileMapper mapper(resourceFiles);
638 if (!resourceFiles.isEmpty())
639 m_importer.setResourceFileMapper(&mapper);
640 else
641 m_importer.setResourceFileMapper(nullptr);
642
643 QJsonArray warnings;
645
646 bool success = true;
647
648 QScopeGuard jsonOutput([&] {
649 if (!json)
650 return;
651
652 result[u"module"_s] = module;
653
654 result[u"warnings"] = warnings;
655 result[u"success"] = success;
656
657 json->append(result);
658 });
659
660 m_logger.reset(new QQmlJSLogger);
661 m_logger->setFileName(module);
662 m_logger->setCode(u""_s);
663 m_logger->setSilent(silent || json);
664
665 const QQmlJSImporter::ImportedTypes types = m_importer.importModule(module);
666
667 QList<QQmlJS::DiagnosticMessage> importWarnings =
668 m_importer.takeGlobalWarnings() + m_importer.takeWarnings();
669
670 if (!importWarnings.isEmpty()) {
671 m_logger->log(QStringLiteral("Warnings occurred while importing module:"), qmlImport,
673 m_logger->processMessages(importWarnings, qmlImport);
674 }
675
676 QMap<QString, QSet<QString>> missingTypes;
677 QMap<QString, QSet<QString>> partiallyResolvedTypes;
678
679 const QString modulePrefix = u"$module$."_s;
680 const QString internalPrefix = u"$internal$."_s;
681
682 for (auto &&[typeName, importedScope] : types.types().asKeyValueRange()) {
684 const QQmlJSScope::ConstPtr scope = importedScope.scope;
685
686 if (name.startsWith(modulePrefix))
687 continue;
688
689 if (name.startsWith(internalPrefix)) {
690 name = name.mid(internalPrefix.size());
691 }
692
693 if (scope.isNull()) {
694 if (!missingTypes.contains(name))
695 missingTypes[name] = {};
696 continue;
697 }
698
699 if (!scope->isFullyResolved()) {
700 if (!partiallyResolvedTypes.contains(name))
701 partiallyResolvedTypes[name] = {};
702 }
703 for (const auto &property : scope->ownProperties()) {
704 if (property.typeName().isEmpty()) {
705 // If the type name is empty, then it's an intentional vaguery i.e. for some
706 // builtins
707 continue;
708 }
709 if (property.type().isNull()) {
710 missingTypes[property.typeName()]
711 << scope->internalName() + u'.' + property.propertyName();
712 continue;
713 }
714 if (!property.type()->isFullyResolved()) {
715 partiallyResolvedTypes[property.typeName()]
716 << scope->internalName() + u'.' + property.propertyName();
717 }
718 }
719 if (scope->attachedType() && !scope->attachedType()->isFullyResolved()) {
720 m_logger->log(u"Attached type of \"%1\" not fully resolved"_s.arg(name),
722 }
723
724 for (const auto &method : scope->ownMethods()) {
725 if (method.returnTypeName().isEmpty())
726 continue;
727 if (method.returnType().isNull()) {
728 missingTypes[method.returnTypeName()] << u"return type of "_s
729 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
730 } else if (!method.returnType()->isFullyResolved()) {
731 partiallyResolvedTypes[method.returnTypeName()] << u"return type of "_s
732 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
733 }
734
735 const auto parameters = method.parameters();
736 for (qsizetype i = 0; i < parameters.size(); i++) {
737 auto &parameter = parameters[i];
738 const QString typeName = parameter.typeName();
739 const QSharedPointer<const QQmlJSScope> type = parameter.type();
740 if (typeName.isEmpty())
741 continue;
742 if (type.isNull()) {
743 missingTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
744 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
745 continue;
746 }
747 if (!type->isFullyResolved()) {
748 partiallyResolvedTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
749 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
750 continue;
751 }
752 }
753 }
754 }
755
756 for (auto &&[name, uses] : missingTypes.asKeyValueRange()) {
757 QString message = u"Type \"%1\" not found"_s.arg(name);
758
759 if (!uses.isEmpty()) {
760 const QStringList usesList = QStringList(uses.begin(), uses.end());
761 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
762 }
763
765 }
766
767 for (auto &&[name, uses] : partiallyResolvedTypes.asKeyValueRange()) {
768 QString message = u"Type \"%1\" is not fully resolved"_s.arg(name);
769
770 if (!uses.isEmpty()) {
771 const QStringList usesList = QStringList(uses.begin(), uses.end());
772 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
773 }
774
776 }
777
778 if (json)
779 processMessages(warnings);
780
781 success &= !m_logger->hasWarnings() && !m_logger->hasErrors();
782
783 return success ? LintSuccess : HasWarnings;
784}
785
787{
788 Q_ASSERT(fixedCode != nullptr);
789
790 // This means that the necessary analysis for applying fixes hasn't run for some reason
791 // (because it was JS file, a syntax error etc.). We can't procede without it and if an error
792 // has occurred that has to be handled by the caller of lintFile(). Just say that there is
793 // nothing to fix.
794 if (m_logger == nullptr)
795 return NothingToFix;
796
797 QString code = m_fileContents;
798
799 QList<QQmlJSFixSuggestion> fixesToApply;
800
801 QFileInfo info(m_logger->fileName());
802 const QString currentFileAbsolutePath = info.absoluteFilePath();
803
804 const QString lowerSuffix = info.suffix().toLower();
805 const bool isESModule = lowerSuffix == QLatin1String("mjs");
806 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
807
808 if (isESModule || isJavaScript)
809 return NothingToFix;
810
811 for (const auto &messages : { m_logger->infos(), m_logger->warnings(), m_logger->errors() })
812 for (const Message &msg : messages) {
813 if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
814 continue;
815
816 // Ignore fix suggestions for other files
817 const QString filename = msg.fixSuggestion->filename();
818 if (!filename.isEmpty()
819 && QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
820 continue;
821 }
822
823 fixesToApply << msg.fixSuggestion.value();
824 }
825
826 if (fixesToApply.isEmpty())
827 return NothingToFix;
828
829 std::sort(fixesToApply.begin(), fixesToApply.end(),
830 [](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) {
831 return a.location().offset < b.location().offset;
832 });
833
834 for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
835 const QQmlJS::SourceLocation srcLocA = it->location();
836 const QQmlJS::SourceLocation srcLocB = (it + 1)->location();
837 if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
838 if (!silent)
839 qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug "
840 "report.";
841 return FixError;
842 }
843 }
844
845 int offsetChange = 0;
846
847 for (const auto &fix : fixesToApply) {
848 const QQmlJS::SourceLocation fixLocation = fix.location();
849 qsizetype cutLocation = fixLocation.offset + offsetChange;
850 const QString before = code.left(cutLocation);
851 const QString after = code.mid(cutLocation + fixLocation.length);
852
853 const QString replacement = fix.replacement();
854 code = before + replacement + after;
855 offsetChange += replacement.size() - fixLocation.length;
856 }
857
859 QQmlJS::Lexer lexer(&engine);
860
861 lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
862 QQmlJS::Parser parser(&engine);
863
864 bool success = parser.parse();
865
866 if (!success) {
867 const auto diagnosticMessages = parser.diagnosticMessages();
868
869 if (!silent) {
870 qDebug() << "File became unparseable after suggestions were applied. Please file a bug "
871 "report.";
872 } else {
873 return FixError;
874 }
875
876 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
877 qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
878 .arg(m_logger->fileName())
879 .arg(m.loc.startLine)
880 .arg(m.loc.startColumn)
881 .arg(m.message);
882 }
883 return FixError;
884 }
885
886 *fixedCode = code;
887 return FixSuccess;
888}
889
void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) override
CodegenWarningInterface(QQmlJSLogger *logger)
\inmodule QtCore
The QDirIterator class provides an iterator for directory entrylists.
static QChar separator()
Returns the native directory separator: "/" under Unix and "\\" under Windows.
Definition qdir.h:206
bool hasSeen(const T &s)
FileError error() const
Returns the file error status.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QString suffix() const
Returns the suffix (extension) of the file.
QString absoluteFilePath() const
Returns an absolute path including the file name.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
\inmodule QtCore
Definition qhash.h:818
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
QString errorString() const
Returns a human-readable description of the last device error that occurred.
\inmodule QtCore\reentrant
Definition qjsonarray.h:18
void insert(qsizetype i, const QJsonValue &value)
Inserts value at index position i in the array.
void append(const QJsonValue &value)
Inserts value at the end of the array.
\inmodule QtCore\reentrant
Definition qjsonobject.h:20
bool contains(const QString &key) const
Returns true if the object contains key key.
\inmodule QtCore\reentrant
Definition qjsonvalue.h:24
static QString path(LibraryPath p)
static bool isLibrary(const QString &fileName)
Returns true if fileName has a valid suffix for a loadable library; otherwise returns false.
Definition qlibrary.cpp:607
Definition qlist.h:74
bool isEmpty() const noexcept
Definition qlist.h:390
iterator end()
Definition qlist.h:609
T value(qsizetype i) const
Definition qlist.h:661
iterator begin()
Definition qlist.h:608
static void setFilterRules(const QString &rules)
Configures which categories and message types should be enabled through a set of rules.
Definition qmap.h:186
auto asKeyValueRange() &
Definition qmap.h:613
bool contains(const Key &key) const
Definition qmap.h:340
\inmodule QtCore
Definition qobject.h:90
static QList< QStaticPlugin > staticPlugins()
Returns a list of QStaticPlugins held by the plugin loader.
static QString implicitImportDirectory(const QString &localFile, QQmlJSResourceFileMapper *mapper)
QStringList importPaths() const
QList< QQmlJS::DiagnosticMessage > takeWarnings()
void setImportPaths(const QStringList &importPaths)
QList< QQmlJS::DiagnosticMessage > takeGlobalWarnings()
void setResourceFileMapper(QQmlJSResourceFileMapper *mapper)
ImportedTypes importModule(const QString &module, const QString &prefix=QString(), QTypeRevision version=QTypeRevision(), QStringList *staticModuleList=nullptr)
FixResult applyFixes(QString *fixedCode, bool silent)
static std::vector< Plugin > loadPlugins(QStringList paths)
const QQmlJSLogger * logger() const
QQmlJSLinter(const QStringList &importPaths, const QStringList &pluginPaths={ QQmlJSLinter::defaultPluginPath() }, bool useAbsolutePath=false)
std::vector< Plugin > & plugins()
static QString defaultPluginPath()
LintResult lintModule(const QString &uri, const bool silent, QJsonArray *json, const QStringList &qmlImportPaths, const QStringList &resourceFiles)
LintResult lintFile(const QString &filename, const QString *fileContents, const bool silent, QJsonArray *json, const QStringList &qmlImportPaths, const QStringList &qmldirFiles, const QStringList &resourceFiles, const QList< QQmlJS::LoggerCategory > &categories)
void run(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
void processMessages(const QList< QQmlJS::DiagnosticMessage > &messages, const QQmlJS::LoggerWarningId id)
QString code() const
void setCode(const QString &code)
bool hasWarnings() const
QString fileName() const
QList< QQmlJS::LoggerCategory > categories() const
void setSilent(bool silent)
void setFileName(const QString &fileName)
const QList< Message > & warnings() const
void setCategoryIgnored(QQmlJS::LoggerWarningId id, bool error)
void setCategoryLevel(QQmlJS::LoggerWarningId id, QtMsgType level)
const QList< Message > & errors() const
void log(const QString &message, QQmlJS::LoggerWarningId id, const QQmlJS::SourceLocation &srcLocation, bool showContext=true, bool showFileName=true, const std::optional< QQmlJSFixSuggestion > &suggestion={}, const QString overrideFileName=QString())
bool hasErrors() const
void registerCategory(const QQmlJS::LoggerCategory &category)
void ignoreWarnings(uint32_t line, const QSet< QString > &categories)
const QList< Message > & infos() const
static QQmlJSScope::Ptr create()
QString internalName() const
static QQmlSA::Element createQQmlSAElement(const ConstPtr &)
bool isFullyResolved() const
QQmlJSScope::ConstPtr attachedType() const
QHash< QString, QQmlJSMetaProperty > ownProperties() const
QQmlJS::SourceLocation sourceLocation() const
QMultiHash< QString, QQmlJSMetaMethod > ownMethods() const
void setParentMode(ParentMode mode)
void init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program)
void setCloneMode(CloneMode mode)
void setCode(const QString &code, int lineno, bool qmlMode=true, CodeContinuation codeContinuation=CodeContinuation::Reset)
static LoggerCategoryPrivate * get(LoggerCategory *)
const QAnyStringView name() const
\inmodule QtQmlCompiler
Definition qqmlsa.h:337
virtual void registerPasses(PassManager *manager, const Element &rootElement)=0
Adds a pass manager that will be executed on rootElement.
\inmodule QtQmlCompiler
Definition qqmlsa.h:309
\inmodule QtCore
T * get() const noexcept
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
Definition qset.h:18
iterator end()
Definition qset.h:140
bool isEmpty() const
Definition qset.h:52
const_iterator cend() const noexcept
Definition qset.h:142
iterator find(const T &value)
Definition qset.h:159
QSet< T > & unite(const QSet< T > &other)
Definition qset.h:225
const_iterator cbegin() const noexcept
Definition qset.h:138
QSet< T > & subtract(const QSet< T > &other)
Definition qset.h:273
\inmodule QtCore
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5299
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
QStringList split(const QString &sep, Qt::SplitBehavior behavior=Qt::KeepEmptyParts, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Splits the string into substrings wherever sep occurs, and returns the list of those strings.
Definition qstring.cpp:7956
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8606
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3110
QString toLower() const &
Definition qstring.h:368
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
Definition qstring.cpp:5161
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
const QLoggingCategory & category()
[1]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ SkipEmptyParts
Definition qnamespace.h:127
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
DBusConnection const char DBusError * error
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char * method
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qDebug
[1]
Definition qlogging.h:160
@ QtCriticalMsg
Definition qlogging.h:32
@ QtInfoMsg
Definition qlogging.h:34
@ QtWarningMsg
Definition qlogging.h:31
@ QtFatalMsg
Definition qlogging.h:33
@ QtDebugMsg
Definition qlogging.h:30
#define qWarning
Definition qlogging.h:162
const char * typeName
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLint location
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
const GLfloat * m
GLboolean GLboolean GLboolean GLboolean a
[7]
GLsizei GLenum GLenum * types
GLenum GLuint id
[7]
GLenum type
GLsizei const GLuint * paths
GLenum target
GLuint GLsizei const GLchar * message
GLuint name
GLsizei GLsizei GLchar * source
GLsizei GLenum * categories
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLuint GLenum option
bool qCompileQmlFile(const QString &inputFileName, QQmlJSSaveFunction saveFunction, QQmlJSAotCompiler *aotCompiler, QQmlJSCompileError *error, bool storeSourceLocation, QV4::Compiler::CodegenWarningInterface *interface, const QString *fileContents)
std::function< bool(const QV4::CompiledData::SaveableUnitPointer &, const QQmlJSAotFunctionMap &, QString *)> QQmlJSSaveFunction
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message, QAnyStringView id, const std::optional< QQmlJSFixSuggestion > &suggestion={})
const QQmlJS::LoggerWarningId qmlImport
const QQmlJS::LoggerWarningId qmlUnresolvedType
const QQmlJS::LoggerWarningId qmlVarUsedBeforeDeclaration
const QQmlJS::LoggerWarningId qmlInvalidLintDirective
const QQmlJS::LoggerWarningId qmlSyntax
#define QmlLintPluginInterface_iid
Definition qqmlsa.h:435
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int(*) void arg)
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:70
const char property[13]
Definition qwizard.cpp:101
QFile file
[0]
QFileInfo info(fileName)
[8]
QObject::connect nullptr
QDataWidgetMapper * mapper
[0]
char * toString(const MyType &t)
[31]
QJSEngine engine
[0]
static Filter localFileFilter(const QString &file)
\inmodule QtCore
Definition qplugin.h:110
QJsonObject metaData() const
\variable QStaticPlugin::instance
QtPluginInstanceFunction instance
Definition qplugin.h:115