8#include <private/qqmljsimporter_p.h>
9#include <private/qqmljsimportvisitor_p.h>
10#include <private/qqmljslinterpasses_p.h>
11#include <private/qqmljslintervisitor_p.h>
12#include <private/qqmljsliteralbindingcheck_p.h>
13#include <private/qqmljsloggingutils_p.h>
14#include <private/qqmljsutils_p.h>
15#include <private/qqmlsa_p.h>
17#include <QtCore/qjsonobject.h>
18#include <QtCore/qfileinfo.h>
19#include <QtCore/qloggingcategory.h>
20#include <QtCore/qpluginloader.h>
21#include <QtCore/qlibraryinfo.h>
22#include <QtCore/qdir.h>
23#include <QtCore/private/qduplicatetracker_p.h>
24#include <QtCore/qscopedpointer.h>
28# include <QtCore/qdiriterator.h>
29# include <QtCore/qlibrary.h>
32#if QT_CONFIG(qmlcontextpropertydump)
33# include <QtCore/qsettings.h>
36#include <QtQml/private/qqmljslexer_p.h>
37#include <QtQml/private/qqmljsparser_p.h>
38#include <QtQml/private/qqmljsengine_p.h>
39#include <QtQml/private/qqmljsastvisitor_p.h>
40#include <QtQml/private/qqmljsast_p.h>
41#include <QtQml/private/qqmljsdiagnosticmessage_p.h>
46using namespace Qt::StringLiterals;
48class HasFunctionDefinitionVisitor
final :
public QQmlJS::AST::Visitor
51 bool visit(QQmlJS::AST::FunctionDeclaration *functionDeclaration)
override
53 m_result = !functionDeclaration->name.isEmpty();
58 bool result()
const {
return m_result; }
59 void reset() { m_result =
false; }
62 bool m_result =
false;
65class UnreachableVisitor
final :
public QQmlJS::AST::Visitor
73 node->accept(&m_hasFunctionDefinition);
77 bool visit(QQmlJS::AST::StatementList *unreachable)
override
79 QQmlJS::SourceLocation location;
80 auto report = [
this, &location]() {
81 if (location.isValid()) {
82 m_logger->log(u"Unreachable code"_s, qmlUnreachableCode, location);
84 location = QQmlJS::SourceLocation{};
87 for (
auto it = unreachable; it && it->statement; it = it->next) {
88 if (containsFunctionDeclaration(it->statement)) {
92 location = combine(location,
93 combine(it->statement->firstSourceLocation(),
94 it->statement->lastSourceLocation()));
102 QQmlJSLogger *m_logger =
nullptr;
103 HasFunctionDefinitionVisitor m_hasFunctionDefinition;
106class CodegenWarningInterface
final :
public QV4::Compiler::CodegenWarningInterface
114 QQmlJS::SourceLocation declarationLocation,
115 QQmlJS::SourceLocation accessLocation)
override
119 m_logger->log(
"Identifier '%1' is used here before its declaration."_L1.arg(name),
120 qmlVarUsedBeforeDeclaration, accessLocation);
121 m_logger->log(
"Note: declaration of '%1' here"_L1.arg(name), qmlVarUsedBeforeDeclaration,
122 declarationLocation,
true,
true, {}, accessLocation.startLine);
126 QQmlJS::SourceLocation declarationLocation,
127 QQmlJS::SourceLocation accessLocation)
override
131 m_logger->log(
"Function '%1' is used here before its declaration."_L1.arg(name),
132 qmlFunctionUsedBeforeDeclaration, accessLocation);
133 m_logger->log(
"Note: declaration of '%1' here"_L1.arg(name),
134 qmlFunctionUsedBeforeDeclaration, declarationLocation);
140 QQmlJSLogger *m_logger;
141 UnreachableVisitor m_unreachableVisitor;
145 bool useAbsolutePath)
146 : m_useAbsolutePath(useAbsolutePath),
147 m_enablePlugins(
true),
152 m_plugins = loadPlugins(extraPluginPaths);
161 , m_instance(
std::move(plugin.m_instance))
163 , m_isInternal(
std::move(plugin.m_isInternal))
164 , m_isValid(
std::move(plugin.m_isValid))
167 Q_ASSERT(!plugin.m_loader);
168 plugin.m_instance =
nullptr;
169 plugin.m_isValid =
false;
172#if QT_CONFIG(library)
173QQmlJSLinter::Plugin::Plugin(QString path)
175 m_loader = std::make_unique<QPluginLoader>(path);
176 if (!parseMetaData(m_loader->metaData(), path))
179 QObject *object = m_loader->instance();
183 m_instance = qobject_cast<QQmlSA::LintPlugin *>(object);
193 if (!parseMetaData(staticPlugin.metaData(), u"built-in"_s))
196 m_instance = qobject_cast<
QQmlSA::LintPlugin *>(staticPlugin.instance());
205#if QT_CONFIG(library)
206 if (m_loader !=
nullptr) {
208 m_loader->deleteLater();
213bool QQmlJSLinter::
Plugin::parseMetaData(
const QJsonObject &metaData, QString pluginName)
215 const QString pluginIID = QStringLiteral(QmlLintPluginInterface_iid);
217 if (metaData[u"IID"].toString() != pluginIID)
220 QJsonObject pluginMetaData = metaData[u"MetaData"].toObject();
222 for (
const QString &requiredKey :
223 { u"name"_s, u"version"_s, u"author"_s, u"loggingCategories"_s }) {
224 if (!pluginMetaData.contains(requiredKey)) {
225 qWarning() << pluginName <<
"is missing the required " << requiredKey
226 <<
"metadata, skipping";
231 m_name = pluginMetaData[u"name"].toString();
232 m_author = pluginMetaData[u"author"].toString();
233 m_version = pluginMetaData[u"version"].toString();
234 m_description = pluginMetaData[u"description"].toString(u"-/-"_s);
235 m_isInternal = pluginMetaData[u"isInternal"].toBool(
false);
237 if (!pluginMetaData[u"loggingCategories"].isArray()) {
238 qWarning() << pluginName <<
"has loggingCategories which are not an array, skipping";
242 const QJsonArray categories = pluginMetaData[u"loggingCategories"].toArray();
243 for (
const QJsonValue &value : categories) {
244 if (!value.isObject()) {
245 qWarning() << pluginName <<
"has invalid loggingCategories entries, skipping";
249 const QJsonObject object = value.toObject();
251 for (
const QString &requiredKey : { u"name"_s, u"description"_s }) {
252 if (!object.contains(requiredKey)) {
253 qWarning() << pluginName <<
" logging category is missing the required "
254 << requiredKey <<
"metadata, skipping";
259 const QString prefix = (m_isInternal ? u""_s : u"Plugin."_s).append(m_name).append(u'.');
260 const QString categoryId =
261 prefix + object[u"name"].toString();
262 const auto settingsNameIt = object.constFind(u"settingsName");
263 const QString settingsName = (settingsNameIt == object.constEnd())
265 : prefix + settingsNameIt->toString(categoryId);
266 m_categories << QQmlJS::LoggerCategory{ categoryId, settingsName,
267 object[
"description"_L1].toString(),
268 QQmlJS::WarningSeverity::Warning };
269 const auto itSeverity = object.find(
"defaultSeverity"_L1);
270 if (itSeverity == object.end())
273 const QString severityName = itSeverity->toString();
274 const auto severity = QQmlJS::LoggingUtils::severityFromString(severityName);
275 if (!severity.has_value()) {
276 qWarning() <<
"Invalid logging severity" << severityName <<
"provided for"
277 << m_categories.last().id().name().toString()
278 <<
"(allowed are: disable, info, warning, error) found in plugin metadata.";
282 m_categories.last().setSeverity(severity.value());
288std::vector<QQmlJSLinter::Plugin>
QQmlJSLinter::loadPlugins(QStringList extraPluginPaths)
290 std::vector<Plugin> plugins;
292 QDuplicateTracker<QString> seenPlugins;
294 const auto &staticPlugins = QPluginLoader::staticPlugins();
295 for (
const QStaticPlugin &staticPlugin : staticPlugins) {
296 Plugin plugin(staticPlugin);
297 if (!plugin.isValid())
300 if (seenPlugins.hasSeen(plugin.name().toLower())) {
301 qWarning() <<
"Two plugins named" << plugin.name()
302 <<
"present, make sure no plugins are duplicated. The second plugin will "
307 plugins.push_back(std::move(plugin));
310#if QT_CONFIG(library)
311 const QStringList paths = [&extraPluginPaths]() {
312 QStringList result{ extraPluginPaths };
313 const QStringList libraryPaths = QCoreApplication::libraryPaths();
314 for (
const auto &path : libraryPaths) {
315 result.append(path + u"/qmllint"_s);
319 for (
const QString &pluginDir : paths) {
320 QDirIterator it{ pluginDir, QDir::Files };
322 while (it.hasNext()) {
323 auto potentialPlugin = it.next();
325 if (!QLibrary::isLibrary(potentialPlugin))
328 Plugin plugin(potentialPlugin);
330 if (!plugin.isValid())
333 if (seenPlugins.hasSeen(plugin.name().toLower())) {
334 qWarning() <<
"Two plugins named" << plugin.name()
335 <<
"present, make sure no plugins are duplicated. The second plugin "
336 "will not be loaded.";
340 plugins.push_back(std::move(plugin));
344 Q_UNUSED(extraPluginPaths)
349 const QList<QQmlJS::SourceLocation> &comments)
351 QHash<
int, QSet<QString>> disablesPerLine;
352 QHash<
int, QSet<QString>> enablesPerLine;
353 QHash<
int, QSet<QString>> oneLineDisablesPerLine;
355 struct PostponedWarning
358 QQmlSA::LoggerWarningId category;
359 QQmlJS::SourceLocation location;
362 std::vector<PostponedWarning> postponedWarnings;
363 auto guard = qScopeGuard([&postponedWarnings, &logger]() {
366 for (
const auto &warning : postponedWarnings)
367 logger->log(warning.message, warning.category, warning.location);
370 const QString code = logger->code();
371 const QStringList lines = code.split(u'\n');
372 const auto loggerCategories = logger->categories();
374 for (
const auto &loc : comments) {
375 const QString comment = code.mid(loc.offset, loc.length);
376 if (!comment.startsWith(u" qmllint ") && !comment.startsWith(u"qmllint "))
379 QStringList words = comment.split(u' ', Qt::SkipEmptyParts);
380 if (words.size() < 2)
383 QSet<QString> categories;
384 for (qsizetype i = 2; i < words.size(); i++) {
385 const QString category = words.at(i);
386 const auto categoryExists = std::any_of(
387 loggerCategories.cbegin(), loggerCategories.cend(),
388 [&](
const QQmlJS::LoggerCategory &cat) {
return cat.id().name() == category; });
391 categories << category;
393 postponedWarnings.push_back(
394 { u"qmllint directive on unknown category \"%1\""_s.arg(category),
395 qmlInvalidLintDirective, loc });
399 if (words.size() == 2) {
400 const auto &loggerCategories = logger->categories();
401 for (
const auto &option : loggerCategories)
402 categories << option.id().name().toString();
405 const QString command = words.at(1);
406 if (command == u"disable"_s) {
407 if (
const qsizetype lineIndex = loc.startLine - 1; lineIndex < lines.size()) {
408 const QString line = lines[lineIndex];
409 const QString preComment = line.left(line.indexOf(comment) - 2);
411 bool lineHasContent =
false;
412 for (qsizetype i = 0; i < preComment.size(); i++) {
413 if (!preComment[i].isSpace()) {
414 lineHasContent =
true;
420 oneLineDisablesPerLine[loc.startLine] |= categories;
422 disablesPerLine[loc.startLine] |= categories;
424 }
else if (command == u"enable"_s) {
425 enablesPerLine[loc.startLine + 1] |= categories;
427 postponedWarnings.push_back(
428 { u"Invalid qmllint directive \"%1\" provided"_s.arg(command),
429 qmlInvalidLintDirective, loc });
433 if (disablesPerLine.isEmpty() && oneLineDisablesPerLine.isEmpty())
436 QSet<QString> currentlyDisabled;
437 for (qsizetype i = 1; i <= lines.size(); i++) {
438 currentlyDisabled.unite(disablesPerLine[i]).subtract(enablesPerLine[i]);
440 currentlyDisabled.unite(oneLineDisablesPerLine[i]);
442 if (!currentlyDisabled.isEmpty())
443 logger->ignoreWarnings(i, currentlyDisabled);
445 currentlyDisabled.subtract(oneLineDisablesPerLine[i]);
449static void addJsonWarning(QJsonArray &warnings,
const QQmlJS::DiagnosticMessage &message,
450 QAnyStringView id,
const std::optional<QQmlJSFixSuggestion> &suggestion = {})
452 QJsonObject jsonMessage;
455 switch (message.type) {
463 type = u"critical"_s;
476 jsonMessage[u"type"_s] = type;
477 jsonMessage[u"id"_s] = id.toString();
479 const auto convertLocation = [](
const QQmlJS::SourceLocation &source, QJsonObject *target) {
480 target->insert(
"line"_L1,
int(source.startLine));
481 target->insert(
"column"_L1,
int(source.startColumn));
482 target->insert(
"charOffset"_L1,
int(source.offset));
483 target->insert(
"length"_L1,
int(source.length));
486 if (message.loc.isValid())
487 convertLocation(message.loc, &jsonMessage);
489 jsonMessage[u"message"_s] = message.message;
491 QJsonArray suggestions;
492 if (suggestion.has_value()) {
493 QJsonArray documentEdits;
494 for (
const auto &documentEdit : suggestion->documentEdits()) {
495 QJsonObject location;
496 convertLocation(documentEdit.m_location, &location);
498 {
"filename"_L1, documentEdit.m_filename },
499 {
"location"_L1, location },
500 {
"replacement"_L1, documentEdit.m_replacement }
502 documentEdits.append(edit);
505 QJsonObject jsonFix {
506 {
"message"_L1, suggestion->description() },
507 {
"documentEdits"_L1, documentEdits },
508 {
"isAutoApplicable"_L1, suggestion->isAutoApplicable() },
510 convertLocation(suggestion->location(), &jsonFix);
511 const QString filename = suggestion->filename();
512 if (!filename.isEmpty())
513 jsonFix.insert(
"fileName"_L1, filename);
514 suggestions << jsonFix;
516 jsonMessage[u"suggestions"] = suggestions;
518 warnings << jsonMessage;
523 m_logger->iterateAllMessages([&](
const Message &message) {
524 addJsonWarning(warnings, message, message.id, message.fixSuggestion);
529 const QString &filename, QQmlJSResourceFileMapper *mapper,
530 const QQmlJS::HeuristicContextProperties &heuristicContextProperties)
532 ContextPropertyInfo result;
533 if (m_userContextPropertySettings.search(filename).isValid()) {
534 result.userContextProperties =
535 QQmlJS::UserContextProperties{ m_userContextPropertySettings };
538 if (heuristicContextProperties.isValid()) {
539 result.heuristicContextProperties = heuristicContextProperties;
543#if QT_CONFIG(qmlcontextpropertydump)
544 const QString buildPath = QQmlJSUtils::qmlBuildPathFromSourcePath(mapper, filename);
545 if (
const auto searchResult = m_heuristicContextPropertySearcher.search(buildPath);
546 searchResult.isValid()) {
547 QSettings settings(searchResult.iniFilePath, QSettings::IniFormat);
548 result.heuristicContextProperties =
549 QQmlJS::HeuristicContextProperties::collectFrom(&settings);
559 QJsonArray *json,
const QStringList &qmlImportPaths,
560 const QStringList &qmldirFiles,
const QStringList &resourceFiles,
561 const QList<QQmlJS::LoggerCategory> &categories,
562 const QQmlJS::HeuristicContextProperties &heuristicContextProperties)
565 lintFileImpl(filename, fileContents, silent, json, qmlImportPaths, qmldirFiles,
566 resourceFiles, categories, heuristicContextProperties);
571 processMessages(warnings);
574 result[u"filename"_s] = QFileInfo(filename).absoluteFilePath();
575 result[u"warnings"] = warnings;
578 json->append(result);
582void QQmlJSLinter::setupLoggingCategoriesInLogger(
const QList<QQmlJS::LoggerCategory> &categories)
584 if (m_enablePlugins) {
585 for (
const Plugin &plugin : m_plugins) {
586 for (
const QQmlJS::LoggerCategory &category : plugin.categories())
587 m_logger->registerCategory(category);
591 for (
auto it = categories.cbegin(); it != categories.cend(); ++it) {
592 if (
auto logger = *it; !QQmlJS::LoggerCategoryPrivate::get(&logger)->hasChanged())
595 m_logger->setCategorySeverity(it->id(), it->severity());
600QQmlJSLinter::lintFileImpl(
const QString &filename,
const QString *fileContents,
const bool silent,
601 QJsonArray *json,
const QStringList &qmlImportPaths,
602 const QStringList &qmldirFiles,
const QStringList &resourceFiles,
603 const QList<QQmlJS::LoggerCategory> &categories,
604 const QQmlJS::HeuristicContextProperties &heuristicContextProperties)
608 QFileInfo info(filename);
609 const QString lowerSuffix = info.suffix().toLower();
610 const bool isESModule = lowerSuffix == QLatin1String(
"mjs");
611 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String(
"js");
613 m_logger.reset(
new QQmlJSLogger);
614 m_logger->setFilePath(m_useAbsolutePath ? info.absoluteFilePath() : filename);
615 m_logger->setSilent(silent || json);
616 setupLoggingCategoriesInLogger(categories);
618 if (fileContents ==
nullptr) {
619 QFile file(filename);
620 if (!file.open(QFile::ReadOnly)) {
621 m_logger->log(
"Failed to open file %1: %2"_L1.arg(filename, file.errorString()),
622 qmlImport, QQmlJS::SourceLocation());
626 code = QString::fromUtf8(file.readAll());
629 code = *fileContents;
632 m_fileContents = code;
633 m_logger->setCode(code);
635 QQmlJS::Engine engine;
636 QQmlJS::Lexer lexer(&engine);
638 lexer.setCode(code, 1, !isJavaScript);
639 QQmlJS::Parser parser(&engine);
641 const bool parseSuccess = isJavaScript
642 ? (isESModule ? parser.parseModule() : parser.parseProgram())
644 const auto diagnosticMessages = parser.diagnosticMessages();
645 for (
const QQmlJS::DiagnosticMessage &m : diagnosticMessages)
646 m_logger->log(m.message, qmlSyntax, m.loc);
654 m_importer.setImportPaths(qmlImportPaths);
656 std::optional<QQmlJSResourceFileMapper> mapper;
657 if (!resourceFiles.isEmpty())
658 mapper.emplace(resourceFiles);
659 m_importer.setResourceFileMapper(mapper.has_value() ? &*mapper :
nullptr);
661 QQmlJS::LinterVisitor v{ &m_importer, m_logger.get(),
662 QQmlJSImportVisitor::implicitImportDirectory(
663 m_logger->filePath(), m_importer.resourceFileMapper()),
664 qmldirFiles, &engine };
666 parseComments(m_logger.get(), engine.comments());
668 QQmlJSTypeResolver typeResolver(&m_importer);
675 typeResolver.setParentMode(QQmlJSTypeResolver::UseDocumentParent);
679 typeResolver.setCloneMode(QQmlJSTypeResolver::DoNotCloneTypes);
681 typeResolver.init(&v, parser.rootNode());
683 const QStringList resourcePaths = mapper
684 ? mapper->resourcePaths(QQmlJSResourceFileMapper::localFileFilter(filename))
686 const QString resolvedPath =
687 (resourcePaths.size() == 1) ? u':' + resourcePaths.first() : filename;
690 contextPropertiesFor(filename, mapper ? &*mapper :
nullptr,
691 heuristicContextProperties) };
692 codegen.setTypeResolver(
std::move(typeResolver));
693 codegen.setScopesById(v.addressableScopes());
694 codegen.setRenamedComponents(&v.renamedComponents());
695 codegen.setKnownUnresolvedTypes(v.knownUnresolvedTypes());
697 using PassManagerPtr =
698 std::unique_ptr<QQmlSA::PassManager,
699 decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>;
700 PassManagerPtr passMan(
701 QQmlSA::PassManagerPrivate::createPassManager(&v, codegen.typeResolver()),
702 &QQmlSA::PassManagerPrivate::deletePassManager);
703 QQmlJSLinterPasses::registerDefaultPasses(passMan.get());
705 if (m_enablePlugins) {
706 for (
const Plugin &plugin : m_plugins) {
707 if (!plugin.isValid() || !plugin.isEnabled())
710 QQmlSA::LintPlugin *instance = plugin.m_instance;
712 instance->registerPasses(passMan.get(), QQmlJSScope::createQQmlSAElement(v.result()));
715 passMan->analyze(QQmlJSScope::createQQmlSAElement(v.result()));
717 if (m_logger->hasErrors())
724 QQmlJSSaveFunction saveFunction = [](
const QV4::CompiledData::SaveableUnitPointer &,
725 const QQmlJSAotFunctionMap &, QString *) {
return true; };
727 QQmlJSCompileError error;
729 QLoggingCategory::setFilterRules(u"qt.qml.compiler=false"_s);
731 CodegenWarningInterface warningInterface(m_logger.get());
732 qCompileQmlFile(filename, saveFunction, &codegen, &error,
true, &warningInterface,
735 QList<QQmlJS::DiagnosticMessage> globalWarnings = m_importer.takeGlobalWarnings();
737 if (!globalWarnings.isEmpty()) {
738 m_logger->log(QStringLiteral(
"Type warnings occurred while evaluating file:"), qmlImport,
739 QQmlJS::SourceLocation());
740 m_logger->processMessages(globalWarnings, qmlImport);
743 if (m_logger->hasErrors())
745 if (m_logger->hasWarnings())
752 const QString &module,
const bool silent, QJsonArray *json,
753 const QStringList &qmlImportPaths,
const QStringList &resourceFiles)
755 const LintResult lintResult = lintModuleImpl(module, silent, json, qmlImportPaths, resourceFiles);
760 processMessages(warnings);
763 result[u"module"_s] = module;
764 result[u"warnings"] = warnings;
767 json->append(result);
772 const QString &module,
const bool silent, QJsonArray *json,
773 const QStringList &qmlImportPaths,
const QStringList &resourceFiles)
779 m_importer.clearCache();
782 const QQmlJSImporterFlags oldFlags = m_importer.flags();
783 QQmlJSImporterFlags newFlags = oldFlags;
784 newFlags.setFlag(TolerateFileSelectors,
false);
785 m_importer.setFlags(newFlags);
786 auto flagGuard = qScopeGuard([
this, oldFlags]() { m_importer.setFlags(oldFlags); });
787 m_importer.setImportPaths(qmlImportPaths);
789 QQmlJSResourceFileMapper mapper(resourceFiles);
790 if (!resourceFiles.isEmpty())
791 m_importer.setResourceFileMapper(&mapper);
793 m_importer.setResourceFileMapper(
nullptr);
795 m_logger.reset(
new QQmlJSLogger);
796 m_logger->setFilePath(module);
797 m_logger->setCode(u""_s);
798 m_logger->setSilent(silent || json);
800 const QQmlJSImporter::ImportedTypes types =
801 m_importer.importModule(module, QQmlJS::PrecedenceValues::Default);
803 QList<QQmlJS::DiagnosticMessage> importWarnings =
804 m_importer.takeGlobalWarnings() + types.warnings();
806 if (!importWarnings.isEmpty()) {
807 m_logger->log(QStringLiteral(
"Warnings occurred while importing module:"), qmlImport,
808 QQmlJS::SourceLocation());
809 m_logger->processMessages(importWarnings, qmlImport);
812 QMap<QString, QSet<QString>> missingTypes;
813 QMap<QString, QSet<QString>> partiallyResolvedTypes;
815 const QString modulePrefix = u"$module$."_s;
816 const QString internalPrefix = u"$internal$."_s;
818 for (
auto &&[typeName, importedScope] : types.types().asKeyValueRange()) {
819 QString name = typeName;
820 const QQmlJSScope::ConstPtr scope = importedScope.scope;
822 if (name.startsWith(modulePrefix))
825 if (name.startsWith(internalPrefix)) {
826 name = name.mid(internalPrefix.size());
829 if (scope.isNull()) {
830 if (!missingTypes.contains(name))
831 missingTypes[name] = {};
835 if (!scope->isFullyResolved()) {
836 if (!partiallyResolvedTypes.contains(name))
837 partiallyResolvedTypes[name] = {};
839 const auto &ownProperties = scope->ownProperties();
840 for (
const auto &property : ownProperties) {
841 if (property.typeName().isEmpty()) {
846 if (property.type().isNull()) {
847 missingTypes[property.typeName()]
848 << scope->internalName() + u'.' + property.propertyName();
851 if (!property.type()->isFullyResolved()) {
852 partiallyResolvedTypes[property.typeName()]
853 << scope->internalName() + u'.' + property.propertyName();
856 if (scope->attachedType() && !scope->attachedType()->isFullyResolved()) {
857 m_logger->log(u"Attached type of \"%1\" not fully resolved"_s.arg(name),
858 qmlUnresolvedType, scope->sourceLocation());
861 const auto &ownMethods = scope->ownMethods();
862 for (
const auto &method : ownMethods) {
863 if (method.returnTypeName().isEmpty())
865 if (method.returnType().isNull()) {
866 missingTypes[method.returnTypeName()] << u"return type of "_s
867 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
868 }
else if (!method.returnType()->isFullyResolved()) {
869 partiallyResolvedTypes[method.returnTypeName()] << u"return type of "_s
870 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
873 const auto parameters = method.parameters();
874 for (qsizetype i = 0; i < parameters.size(); i++) {
875 auto ¶meter = parameters[i];
876 const QString typeName = parameter.typeName();
877 const QSharedPointer<
const QQmlJSScope> type = parameter.type();
878 if (typeName.isEmpty())
881 missingTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
882 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
885 if (!type->isFullyResolved()) {
886 partiallyResolvedTypes[typeName] << u"parameter %1 of "_s.arg(i + 1)
887 + scope->internalName() + u'.' + method.methodName() + u"()"_s;
894 for (
auto &&[name, uses] : missingTypes.asKeyValueRange()) {
895 QString message = u"Type \"%1\" not found"_s.arg(name);
897 if (!uses.isEmpty()) {
898 const QStringList usesList = QStringList(uses.begin(), uses.end());
899 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
902 m_logger->log(message, qmlUnresolvedType, QQmlJS::SourceLocation());
905 for (
auto &&[name, uses] : partiallyResolvedTypes.asKeyValueRange()) {
906 QString message = u"Type \"%1\" is not fully resolved"_s.arg(name);
908 if (!uses.isEmpty()) {
909 const QStringList usesList = QStringList(uses.begin(), uses.end());
910 message += u". Used in %1"_s.arg(usesList.join(u", "_s));
913 m_logger->log(message, qmlUnresolvedType, QQmlJS::SourceLocation());
916 return (m_logger->hasWarnings() || m_logger->hasErrors()) ? HasWarnings : LintSuccess;
921 Q_ASSERT(fixedCode !=
nullptr);
927 if (m_logger ==
nullptr)
930 QString code = m_fileContents;
932 QList<QQmlJSFixSuggestion> fixesToApply;
934 QFileInfo info(m_logger->filePath());
935 const QString currentFileAbsolutePath = info.absoluteFilePath();
937 const QString lowerSuffix = info.suffix().toLower();
938 const bool isESModule = lowerSuffix == QLatin1String(
"mjs");
939 const bool isJavaScript = isESModule || lowerSuffix == QLatin1String(
"js");
941 if (isESModule || isJavaScript)
944 m_logger->iterateAllMessages([&](
const Message &msg) {
945 if (!msg.fixSuggestion.has_value() || !msg.fixSuggestion->isAutoApplicable())
949 const QString filename = msg.fixSuggestion->filename();
950 if (!filename.isEmpty()
951 && QFileInfo(filename).absoluteFilePath() != currentFileAbsolutePath) {
955 fixesToApply << msg.fixSuggestion.value();
958 if (fixesToApply.isEmpty())
961 QList<QQmlJSDocumentEdit> documentEdits;
962 for (
const auto &fixToApply : std::as_const(fixesToApply)) {
963 const auto &fixDocumentEdits = fixToApply.documentEdits();
964 for (
const auto &documentEdit : fixDocumentEdits) {
966 if (documentEdit.m_filename == m_logger->filePath())
967 documentEdits << documentEdit;
971 std::sort(documentEdits.begin(), documentEdits.end(),
972 [](
const QQmlJSDocumentEdit &a,
const QQmlJSDocumentEdit &b) {
973 return a.m_location.offset < b.m_location.offset;
976 const auto dupes = std::unique(documentEdits.begin(), documentEdits.end());
977 documentEdits.erase(dupes, documentEdits.end());
979 for (
auto it = documentEdits.begin(); it + 1 != documentEdits.end(); it++) {
980 const QQmlJS::SourceLocation srcLocA = it->m_location;
981 const QQmlJS::SourceLocation srcLocB = (it + 1)->m_location;
982 if (srcLocA.offset + srcLocA.length > srcLocB.offset) {
984 qWarning() <<
"Document edits for warning fixes are overlapping, aborting. "
985 "Please file a bug report if this is a Qt warning";
992 for (
const auto &edit : std::as_const(documentEdits)) {
993 const QQmlJS::SourceLocation fixLocation = edit.m_location;
994 qsizetype cutLocation = fixLocation.offset + offsetEdit;
995 const QString before = code.left(cutLocation);
996 const QString after = code.mid(cutLocation + fixLocation.length);
998 const QString replacement = edit.m_replacement;
999 code = before + replacement + after;
1000 offsetEdit += replacement.size() - fixLocation.length;
1003 QQmlJS::Engine engine;
1004 QQmlJS::Lexer lexer(&engine);
1006 lexer.setCode(code, 1, !isJavaScript);
1007 QQmlJS::Parser parser(&engine);
1009 bool success = parser.parse();
1012 const auto diagnosticMessages = parser.diagnosticMessages();
1015 qDebug() <<
"File became unparseable after suggestions were applied. Please file a bug "
1021 for (
const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
1022 qWarning().noquote() << QString::fromLatin1(
"%1:%2:%3: %4")
1023 .arg(m_logger->filePath())
1024 .arg(m.loc.startLine)
1025 .arg(m.loc.startColumn)
void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) override
void reportFunctionUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) override
UnreachableVisitor * unreachableVisitor() override
CodegenWarningInterface(QQmlJSLogger *logger)
bool visit(QQmlJS::AST::FunctionDeclaration *functionDeclaration) override
void throwRecursionDepthError() override
void setPassManager(QQmlSA::PassManager *passManager)
Plugin(Plugin &&plugin) noexcept
Plugin(const QStaticPlugin &plugin)
FixResult applyFixes(QString *fixedCode, bool silent)
QQmlJSLinter(const QStringList &importPaths, const QStringList &extraPluginPaths={}, bool useAbsolutePath=false)
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, const QQmlJS::HeuristicContextProperties &contextProperties={})
UnreachableVisitor(QQmlJSLogger *logger)
void throwRecursionDepthError() override
bool containsFunctionDeclaration(QQmlJS::AST::Node *node)
bool visit(QQmlJS::AST::StatementList *unreachable) override
Combined button and popup list for selecting options.
static void addJsonWarning(QJsonArray &warnings, const QQmlJS::DiagnosticMessage &message, QAnyStringView id, const std::optional< QQmlJSFixSuggestion > &suggestion={})