Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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 const auto settingsNameIt = object.constFind(u"settingsName");
189 const QString settingsName = (settingsNameIt == object.constEnd())
190 ? categoryId
191 : settingsNameIt->toString(categoryId);
192 m_categories << QQmlJS::LoggerCategory{ categoryId, settingsName,
193 object["description"_L1].toString(), QtWarningMsg,
194 ignored };
195 }
196
197 return true;
198}
199
200std::vector<QQmlJSLinter::Plugin> QQmlJSLinter::loadPlugins(QStringList paths)
201{
202 std::vector<Plugin> plugins;
203
204 QDuplicateTracker<QString> seenPlugins;
205
206 for (const QStaticPlugin &staticPlugin : QPluginLoader::staticPlugins()) {
207 Plugin plugin(staticPlugin);
208 if (!plugin.isValid())
209 continue;
210
211 if (seenPlugins.hasSeen(plugin.name().toLower())) {
212 qWarning() << "Two plugins named" << plugin.name()
213 << "present, make sure no plugins are duplicated. The second plugin will "
214 "not be loaded.";
215 continue;
216 }
217
218 plugins.push_back(std::move(plugin));
219 }
220
221#if QT_CONFIG(library)
222 for (const QString &pluginDir : paths) {
223 QDirIterator it { pluginDir };
224
225 while (it.hasNext()) {
226 auto potentialPlugin = it.next();
227
228 if (!QLibrary::isLibrary(potentialPlugin))
229 continue;
230
231 Plugin plugin(potentialPlugin);
232
233 if (!plugin.isValid())
234 continue;
235
236 if (seenPlugins.hasSeen(plugin.name().toLower())) {
237 qWarning() << "Two plugins named" << plugin.name()
238 << "present, make sure no plugins are duplicated. The second plugin "
239 "will not be loaded.";
240 continue;
241 }
242
243 plugins.push_back(std::move(plugin));
244 }
245 }
246#endif
248 return plugins;
249}
250
251void QQmlJSLinter::parseComments(QQmlJSLogger *logger,
252 const QList<QQmlJS::SourceLocation> &comments)
253{
254 QHash<int, QSet<QString>> disablesPerLine;
255 QHash<int, QSet<QString>> enablesPerLine;
256 QHash<int, QSet<QString>> oneLineDisablesPerLine;
257
258 const QString code = logger->code();
259 const QStringList lines = code.split(u'\n');
260 const auto loggerCategories = logger->categories();
261
262 for (const auto &loc : comments) {
263 const QString comment = code.mid(loc.offset, loc.length);
264 if (!comment.startsWith(u" qmllint ") && !comment.startsWith(u"qmllint "))
265 continue;
266
267 QStringList words = comment.split(u' ', Qt::SkipEmptyParts);
268 if (words.size() < 2)
269 continue;
270
271 QSet<QString> categories;
272 for (qsizetype i = 2; i < words.size(); i++) {
273 const QString category = words.at(i);
274 const auto categoryExists = std::any_of(
275 loggerCategories.cbegin(), loggerCategories.cend(),
276 [&](const QQmlJS::LoggerCategory &cat) { return cat.id().name() == category; });
277
278 if (categoryExists)
280 else
281 logger->log(u"qmllint directive on unknown category \"%1\""_s.arg(category),
283 }
284
285 if (categories.isEmpty()) {
286 for (const auto &option : logger->categories())
288 }
289
290 const QString command = words.at(1);
291 if (command == u"disable"_s) {
292 if (const qsizetype lineIndex = loc.startLine - 1; lineIndex < lines.size()) {
293 const QString line = lines[lineIndex];
294 const QString preComment = line.left(line.indexOf(comment) - 2);
295
296 bool lineHasContent = false;
297 for (qsizetype i = 0; i < preComment.size(); i++) {
298 if (!preComment[i].isSpace()) {
299 lineHasContent = true;
300 break;
301 }
302 }
303
304 if (lineHasContent)
305 oneLineDisablesPerLine[loc.startLine] |= categories;
306 else
307 disablesPerLine[loc.startLine] |= categories;
308 }
309 } else if (command == u"enable"_s) {
310 enablesPerLine[loc.startLine + 1] |= categories;
311 } else {
312 logger->log(u"Invalid qmllint directive \"%1\" provided"_s.arg(command),
314 }
315 }
316
317 if (disablesPerLine.isEmpty() && oneLineDisablesPerLine.isEmpty())
318 return;
319
320 QSet<QString> currentlyDisabled;
321 for (qsizetype i = 1; i <= lines.size(); i++) {
322 currentlyDisabled.unite(disablesPerLine[i]).subtract(enablesPerLine[i]);
323
324 currentlyDisabled.unite(oneLineDisablesPerLine[i]);
325
326 if (!currentlyDisabled.isEmpty())
327 logger->ignoreWarnings(i, currentlyDisabled);
328
329 currentlyDisabled.subtract(oneLineDisablesPerLine[i]);
330 }
331}
332
334 QAnyStringView id, const std::optional<QQmlJSFixSuggestion> &suggestion = {})
335{
336 QJsonObject jsonMessage;
337
339 switch (message.type) {
340 case QtDebugMsg:
341 type = u"debug"_s;
342 break;
343 case QtWarningMsg:
344 type = u"warning"_s;
345 break;
346 case QtCriticalMsg:
347 type = u"critical"_s;
348 break;
349 case QtFatalMsg:
350 type = u"fatal"_s;
351 break;
352 case QtInfoMsg:
353 type = u"info"_s;
354 break;
355 default:
356 type = u"unknown"_s;
357 break;
358 }
359
360 jsonMessage[u"type"_s] = type;
361 jsonMessage[u"id"_s] = id.toString();
362
363 if (message.loc.isValid()) {
364 jsonMessage[u"line"_s] = static_cast<int>(message.loc.startLine);
365 jsonMessage[u"column"_s] = static_cast<int>(message.loc.startColumn);
366 jsonMessage[u"charOffset"_s] = static_cast<int>(message.loc.offset);
367 jsonMessage[u"length"_s] = static_cast<int>(message.loc.length);
368 }
369
370 jsonMessage[u"message"_s] = message.message;
371
372 QJsonArray suggestions;
373 const auto convertLocation = [](const QQmlJS::SourceLocation &source, QJsonObject *target) {
374 target->insert("line"_L1, int(source.startLine));
375 target->insert("column"_L1, int(source.startColumn));
376 target->insert("charOffset"_L1, int(source.offset));
377 target->insert("length"_L1, int(source.length));
378 };
379 if (suggestion.has_value()) {
380 QJsonObject jsonFix {
381 { "message"_L1, suggestion->fixDescription() },
382 { "replacement"_L1, suggestion->replacement() },
383 { "isHint"_L1, !suggestion->isAutoApplicable() },
384 };
385 convertLocation(suggestion->location(), &jsonFix);
386 const QString filename = suggestion->filename();
387 if (!filename.isEmpty())
388 jsonFix.insert("fileName"_L1, filename);
389 suggestions << jsonFix;
390
391 const QString hint = suggestion->hint();
392 if (!hint.isEmpty()) {
393 // We need to keep compatibility with the JSON format.
394 // Therefore the overly verbose encoding of the hint.
395 QJsonObject jsonHint {
396 { "message"_L1, hint },
397 { "replacement"_L1, QString() },
398 { "isHint"_L1, true }
399 };
400 convertLocation(QQmlJS::SourceLocation(), &jsonHint);
401 suggestions << jsonHint;
402 }
403 }
404 jsonMessage[u"suggestions"] = suggestions;
405
406 warnings << jsonMessage;
407
408}
409
410void QQmlJSLinter::processMessages(QJsonArray &warnings)
411{
412 for (const auto &error : m_logger->errors())
413 addJsonWarning(warnings, error, error.id, error.fixSuggestion);
414 for (const auto &warning : m_logger->warnings())
415 addJsonWarning(warnings, warning, warning.id, warning.fixSuggestion);
416 for (const auto &info : m_logger->infos())
417 addJsonWarning(warnings, info, info.id, info.fixSuggestion);
418}
419
421 const QString *fileContents, const bool silent,
422 QJsonArray *json, const QStringList &qmlImportPaths,
423 const QStringList &qmldirFiles,
424 const QStringList &resourceFiles,
425 const QList<QQmlJS::LoggerCategory> &categories)
426{
427 // Make sure that we don't expose an old logger if we return before a new one is created.
428 m_logger.reset();
429
430 QJsonArray warnings;
432
433 bool success = true;
434
435 QScopeGuard jsonOutput([&] {
436 if (!json)
437 return;
438
439 result[u"filename"_s] = QFileInfo(filename).absoluteFilePath();
440 result[u"warnings"] = warnings;
441 result[u"success"] = success;
442
443 json->append(result);
444 });
445
446 QString code;
447
448 if (fileContents == nullptr) {
449 QFile file(filename);
450 if (!file.open(QFile::ReadOnly)) {
451 if (json) {
453 warnings,
454 QQmlJS::DiagnosticMessage { QStringLiteral("Failed to open file %1: %2")
455 .arg(filename, file.errorString()),
457 qmlImport.name());
458 success = false;
459 } else if (!silent) {
460 qWarning() << "Failed to open file" << filename << file.error();
461 }
462 return FailedToOpen;
463 }
464
466 file.close();
467 } else {
468 code = *fileContents;
469 }
470
471 m_fileContents = code;
472
474 QQmlJS::Lexer lexer(&engine);
475
476 QFileInfo info(filename);
477 const QString lowerSuffix = info.suffix().toLower();
478 const bool isESModule = lowerSuffix == QLatin1String("mjs");
479 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
480
481 lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
482 QQmlJS::Parser parser(&engine);
483
484 success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
485 : parser.parse();
486
487 if (!success) {
488 const auto diagnosticMessages = parser.diagnosticMessages();
489 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
490 if (json) {
491 addJsonWarning(warnings, m, qmlSyntax.name());
492 } else if (!silent) {
493 qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
494 .arg(filename)
495 .arg(m.loc.startLine)
496 .arg(m.loc.startColumn)
497 .arg(m.message);
498 }
499 }
500 return FailedToParse;
501 }
502
503 if (success && !isJavaScript) {
504 const auto check = [&](QQmlJSResourceFileMapper *mapper) {
505 if (m_importer.importPaths() != qmlImportPaths)
506 m_importer.setImportPaths(qmlImportPaths);
507
508 m_importer.setResourceFileMapper(mapper);
509
510 m_logger.reset(new QQmlJSLogger);
511 m_logger->setFileName(m_useAbsolutePath ? info.absoluteFilePath() : filename);
512 m_logger->setCode(code);
513 m_logger->setSilent(silent || json);
515 QQmlJSImportVisitor v { target, &m_importer, m_logger.get(),
517 m_logger->fileName(), m_importer.resourceFileMapper()),
518 qmldirFiles };
519
520 if (m_enablePlugins) {
521 for (const Plugin &plugin : m_plugins) {
522 for (const QQmlJS::LoggerCategory &category : plugin.categories())
523 m_logger->registerCategory(category);
524 }
525 }
526
527 for (auto it = categories.cbegin(); it != categories.cend(); ++it) {
528 if (auto logger = *it; !QQmlJS::LoggerCategoryPrivate::get(&logger)->hasChanged())
529 continue;
530
531 m_logger->setCategoryIgnored(it->id(), it->isIgnored());
532 m_logger->setCategoryLevel(it->id(), it->level());
533 }
534
535 parseComments(m_logger.get(), engine.comments());
536
537 QQmlJSTypeResolver typeResolver(&m_importer);
538
539 // Type resolving is using document parent mode here so that it produces fewer false
540 // positives on the "parent" property of QQuickItem. It does produce a few false
541 // negatives this way because items can be reparented. Furthermore, even if items are
542 // not reparented, the document parent may indeed not be their visual parent. See
543 // QTBUG-95530. Eventually, we'll need cleverer logic to deal with this.
545 // We don't need to create tracked types and such as we are just linting the code here
546 // and not actually compiling it. The duplicated scopes would cause issues during
547 // linting.
549
550 typeResolver.init(&v, parser.rootNode());
551
552 using PassManagerPtr = std::unique_ptr<
554 PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&v, &typeResolver),
556 passMan->registerPropertyPass(
557 std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), QString(),
558 QString(), QString());
559
560 if (m_enablePlugins) {
561 for (const Plugin &plugin : m_plugins) {
562 if (!plugin.isValid() || !plugin.isEnabled())
563 continue;
564
565 QQmlSA::LintPlugin *instance = plugin.m_instance;
566 Q_ASSERT(instance);
567 instance->registerPasses(passMan.get(),
569 }
570 }
571 passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
572
573 success = !m_logger->hasWarnings() && !m_logger->hasErrors();
574
575 if (m_logger->hasErrors()) {
576 if (json)
577 processMessages(warnings);
578 return;
579 }
580
581 const QStringList resourcePaths = mapper
582 ? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename))
583 : QStringList();
584 const QString resolvedPath =
585 (resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename;
586
587 QQmlJSLinterCodegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get() };
588 codegen.setTypeResolver(std::move(typeResolver));
589 if (passMan)
590 codegen.setPassManager(passMan.get());
592 const QQmlJSAotFunctionMap &,
593 QString *) { return true; };
594
596
597 QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_s);
598
600 qCompileQmlFile(filename, saveFunction, &codegen, &error, true, &interface,
601 fileContents);
602
603 QList<QQmlJS::DiagnosticMessage> globalWarnings = m_importer.takeGlobalWarnings();
604
605 if (!globalWarnings.isEmpty()) {
606 m_logger->log(QStringLiteral("Type warnings occurred while evaluating file:"),
608 m_logger->processMessages(globalWarnings, qmlImport);
609 }
610
611 success &= !m_logger->hasWarnings() && !m_logger->hasErrors();
612
613 if (json)
614 processMessages(warnings);
615 };
616
617 if (resourceFiles.isEmpty()) {
618 check(nullptr);
619 } else {
620 QQmlJSResourceFileMapper mapper(resourceFiles);
621 check(&mapper);
622 }
623 }
624
625 return success ? LintSuccess : HasWarnings;
626}
627
629 const QString &module, const bool silent, QJsonArray *json,
630 const QStringList &qmlImportPaths, const QStringList &resourceFiles)
631{
632 // Make sure that we don't expose an old logger if we return before a new one is created.
633 m_logger.reset();
634
635 // We can't lint properly if a module has already been pre-cached
636 m_importer.clearCache();
637
638 if (m_importer.importPaths() != qmlImportPaths)
639 m_importer.setImportPaths(qmlImportPaths);
640
641 QQmlJSResourceFileMapper mapper(resourceFiles);
642 if (!resourceFiles.isEmpty())
643 m_importer.setResourceFileMapper(&mapper);
644 else
645 m_importer.setResourceFileMapper(nullptr);
646
647 QJsonArray warnings;
649
650 bool success = true;
651
652 QScopeGuard jsonOutput([&] {
653 if (!json)
654 return;
655
656 result[u"module"_s] = module;
657
658 result[u"warnings"] = warnings;
659 result[u"success"] = success;
660
661 json->append(result);
662 });
663
664 m_logger.reset(new QQmlJSLogger);
665 m_logger->setFileName(module);
666 m_logger->setCode(u""_s);
667 m_logger->setSilent(silent || json);
668
669 const QQmlJSImporter::ImportedTypes types = m_importer.importModule(module);
670
671 QList<QQmlJS::DiagnosticMessage> importWarnings =
672 m_importer.takeGlobalWarnings() + m_importer.takeWarnings();
673
674 if (!importWarnings.isEmpty()) {
675 m_logger->log(QStringLiteral("Warnings occurred while importing module:"), qmlImport,
677 m_logger->processMessages(importWarnings, qmlImport);
678 }
679
680 QMap<QString, QSet<QString>> missingTypes;
681 QMap<QString, QSet<QString>> partiallyResolvedTypes;
682
683 const QString modulePrefix = u"$module$."_s;
684 const QString internalPrefix = u"$internal$."_s;
685
686 for (auto &&[typeName, importedScope] : types.types().asKeyValueRange()) {
688 const QQmlJSScope::ConstPtr scope = importedScope.scope;
689
690 if (name.startsWith(modulePrefix))
691 continue;
692
693 if (name.startsWith(internalPrefix)) {
694 name = name.mid(internalPrefix.size());
695 }
696
697 if (scope.isNull()) {
698 if (!missingTypes.contains(name))
699 missingTypes[name] = {};
700 continue;
701 }
702
703 if (!scope->isFullyResolved()) {
704 if (!partiallyResolvedTypes.contains(name))
705 partiallyResolvedTypes[name] = {};
706 }
707 for (const auto &property : scope->ownProperties()) {
708 if (property.typeName().isEmpty()) {
709 // If the type name is empty, then it's an intentional vaguery i.e. for some
710 // builtins
711 continue;
712 }
713 if (property.type().isNull()) {
714 missingTypes[property.typeName()]
715 << scope->internalName() + u'.' + property.propertyName();
716 continue;
717 }
718 if (!property.type()->isFullyResolved()) {
719 partiallyResolvedTypes[property.typeName()]
720 << scope->internalName() + u'.' + property.propertyName();
721 }
722 }
723 if (scope->attachedType() && !scope->attachedType()->isFullyResolved()) {
724 m_logger->log(u"Attached type of \"%1\" not fully resolved"_s.arg(name),
726 }
727
728 for (const auto &method : scope->ownMethods()) {
729 if (method.returnTypeName().isEmpty())
730 continue;
731 if (method.returnType().isNull()) {
732 missingTypes[method.returnTypeName()] << u"return type of "_s
733 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
734 } else if (!method.returnType()->isFullyResolved()) {
735 partiallyResolvedTypes[method.returnTypeName()] << u"return type of "_s
736 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
737 }
738
739 const auto parameters = method.parameters();
740 for (qsizetype i = 0; i < parameters.size(); i++) {
741 auto &parameter = parameters[i];
742 const QString typeName = parameter.typeName();
743 const QSharedPointer<const QQmlJSScope> type = parameter.type();
744 if (typeName.isEmpty())
745 continue;
746 if (type.isNull()) {
747 missingTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
748 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
749 continue;
750 }
751 if (!type->isFullyResolved()) {
752 partiallyResolvedTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
753 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
754 continue;
755 }
756 }
757 }
758 }
759
760 for (auto &&[name, uses] : missingTypes.asKeyValueRange()) {
761 QString message = u"Type \"%1\" not found"_s.arg(name);
762
763 if (!uses.isEmpty()) {
764 const QStringList usesList = QStringList(uses.begin(), uses.end());
765 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
766 }
767
769 }
770
771 for (auto &&[name, uses] : partiallyResolvedTypes.asKeyValueRange()) {
772 QString message = u"Type \"%1\" is not fully resolved"_s.arg(name);
773
774 if (!uses.isEmpty()) {
775 const QStringList usesList = QStringList(uses.begin(), uses.end());
776 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
777 }
778
780 }
781
782 if (json)
783 processMessages(warnings);
784
785 success &= !m_logger->hasWarnings() && !m_logger->hasErrors();
786
787 return success ? LintSuccess : HasWarnings;
788}
789
791{
792 Q_ASSERT(fixedCode != nullptr);
793
794 // This means that the necessary analysis for applying fixes hasn't run for some reason
795 // (because it was JS file, a syntax error etc.). We can't procede without it and if an error
796 // has occurred that has to be handled by the caller of lintFile(). Just say that there is
797 // nothing to fix.
798 if (m_logger == nullptr)
799 return NothingToFix;
800
801 QString code = m_fileContents;
802
803 QList<QQmlJSFixSuggestion> fixesToApply;
804
805 QFileInfo info(m_logger->fileName());
806 const QString currentFileAbsolutePath = info.absoluteFilePath();
807
808 const QString lowerSuffix = info.suffix().toLower();
809 const bool isESModule = lowerSuffix == QLatin1String("mjs");
810 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
811
812 if (isESModule || isJavaScript)
813 return NothingToFix;
814
815 for (const auto &messages : { m_logger->infos(), m_logger->warnings(), m_logger->errors() })
816 for (const Message &msg : messages) {
817 if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
818 continue;
819
820 // Ignore fix suggestions for other files
821 const QString filename = msg.fixSuggestion->filename();
822 if (!filename.isEmpty()
823 && QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
824 continue;
825 }
826
827 fixesToApply << msg.fixSuggestion.value();
828 }
829
830 if (fixesToApply.isEmpty())
831 return NothingToFix;
832
833 std::sort(fixesToApply.begin(), fixesToApply.end(),
834 [](const QQmlJSFixSuggestion &a, const QQmlJSFixSuggestion &b) {
835 return a.location().offset < b.location().offset;
836 });
837
838 const auto dupes = std::unique(fixesToApply.begin(), fixesToApply.end());
839 fixesToApply.erase(dupes, fixesToApply.end());
840
841 for (auto it = fixesToApply.begin(); it + 1 != fixesToApply.end(); it++) {
842 const QQmlJS::SourceLocation srcLocA = it->location();
843 const QQmlJS::SourceLocation srcLocB = (it + 1)->location();
844 if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
845 if (!silent)
846 qWarning() << "Fixes for two warnings are overlapping, aborting. Please file a bug "
847 "report.";
848 return FixError;
849 }
850 }
851
852 int offsetChange = 0;
853
854 for (const auto &fix : fixesToApply) {
855 const QQmlJS::SourceLocation fixLocation = fix.location();
856 qsizetype cutLocation = fixLocation.offset + offsetChange;
857 const QString before = code.left(cutLocation);
858 const QString after = code.mid(cutLocation + fixLocation.length);
859
860 const QString replacement = fix.replacement();
861 code = before + replacement + after;
862 offsetChange += replacement.size() - fixLocation.length;
863 }
864
866 QQmlJS::Lexer lexer(&engine);
867
868 lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/!isJavaScript);
869 QQmlJS::Parser parser(&engine);
870
871 bool success = parser.parse();
872
873 if (!success) {
874 const auto diagnosticMessages = parser.diagnosticMessages();
875
876 if (!silent) {
877 qDebug() << "File became unparseable after suggestions were applied. Please file a bug "
878 "report.";
879 } else {
880 return FixError;
881 }
882
883 for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
884 qWarning().noquote() << QString::fromLatin1("%1:%2:%3: %4")
885 .arg(m_logger->fileName())
886 .arg(m.loc.startLine)
887 .arg(m.loc.startColumn)
888 .arg(m.message);
889 }
890 return FixError;
891 }
892
893 *fixedCode = code;
894 return FixSuccess;
895}
896
void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) override
CodegenWarningInterface(QQmlJSLogger *logger)
[custom type definition]
\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:209
FileError error() const
Returns the file error status.
void close() override
Calls QFileDevice::flush() and closes the file.
QString absoluteFilePath() const
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
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
\inmodule QtCore\reentrant
Definition qjsonvalue.h:25
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:614
static void setFilterRules(const QString &rules)
Configures which categories and message types should be enabled through a set of rules.
Definition qmap.h:187
\inmodule QtCore
Definition qobject.h:103
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 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 *)
\inmodule QtQmlCompiler
Definition qqmlsa.h:331
virtual void registerPasses(PassManager *manager, const Element &rootElement)=0
Adds a pass manager that will be executed on rootElement.
QAnyStringView name() const
static void deletePassManager(PassManager *q)
Definition qqmlsa_p.h:201
static PassManager * createPassManager(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
Definition qqmlsa_p.h:195
\inmodule QtQmlCompiler
Definition qqmlsa.h:303
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.
iterator begin()
Definition qset.h:136
iterator end()
Definition qset.h:140
const_iterator cend() const noexcept
Definition qset.h:142
iterator find(const T &value)
Definition qset.h:159
const_iterator cbegin() const noexcept
Definition qset.h:138
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
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:5455
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
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:8218
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8870
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3132
const QLoggingCategory & category()
[1]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ SkipEmptyParts
Definition qnamespace.h:128
QList< QString > QStringList
Constructs a string list that contains the given string, str.
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:164
@ 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:166
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 QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration
const QQmlSA::LoggerWarningId qmlInvalidLintDirective
const QQmlSA::LoggerWarningId qmlUnresolvedType
const QQmlSA::LoggerWarningId qmlImport
const QQmlSA::LoggerWarningId qmlSyntax
#define QmlLintPluginInterface_iid
Definition qqmlsa.h:429
QDebug warning(QAnyStringView fileName, int lineNumber)
#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:165
const char property[13]
Definition qwizard.cpp:101
QFile file
[0]
QObject::connect nullptr
QDataWidgetMapper * mapper
[0]
QHostInfo info
[0]
char * toString(const MyType &t)
[31]
QJSEngine engine
[0]
static Filter localFileFilter(const QString &file)
\inmodule QtCore
Definition qplugin.h:110