8#include <QtQmlCompiler/private/qqmljsimporter_p.h>
9#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
10#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h>
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>
21#include <QtQmlCompiler/private/qqmlsa_p.h>
22#include <QtQmlCompiler/private/qqmljsloggingutils_p.h>
25# include <QtCore/qdiriterator.h>
26# include <QtCore/qlibrary.h>
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>
52 u
"Variable \"%1\" is used here before its declaration. The declaration is at %2:%3."_s
70 : m_useAbsolutePath(useAbsolutePath),
71 m_enablePlugins(true),
72 m_importer(importPaths,
nullptr, true)
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))
91 plugin.m_instance =
nullptr;
92 plugin.m_isValid =
false;
98 m_loader = std::make_unique<QPluginLoader>(
path);
99 if (!parseMetaData(m_loader->metaData(),
path))
102 QObject *
object = m_loader->instance();
106 m_instance = qobject_cast<QQmlSA::LintPlugin *>(
object);
116 if (!parseMetaData(staticPlugin.
metaData(), u
"built-in"_s))
119 m_instance = qobject_cast<QQmlSA::LintPlugin *>(staticPlugin.
instance());
128#if QT_CONFIG(library)
129 if (m_loader !=
nullptr) {
131 m_loader->deleteLater();
136bool QQmlJSLinter::Plugin::parseMetaData(
const QJsonObject &metaData,
QString pluginName)
140 if (metaData[u
"IID"].
toString() != pluginIID)
143 QJsonObject pluginMetaData = metaData[u
"MetaData"].toObject();
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";
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);
160 if (!pluginMetaData[u
"loggingCategories"].isArray()) {
161 qWarning() << pluginName <<
"has loggingCategories which are not an array, skipping";
168 if (!
value.isObject()) {
169 qWarning() << pluginName <<
"has invalid loggingCategories entries, skipping";
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";
183 const auto it =
object.
find(
"enabled"_L1);
184 const bool ignored = (
it !=
object.
end() && !
it->toBool());
187 (m_isInternal ? u
""_s : u
"Plugin."_s) + m_name + u
'.' +
object[u
"name"].
toString();
203 Plugin plugin(staticPlugin);
204 if (!plugin.isValid())
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 "
214 plugins.push_back(std::move(plugin));
217#if QT_CONFIG(library)
221 while (
it.hasNext()) {
222 auto potentialPlugin =
it.next();
227 Plugin plugin(potentialPlugin);
229 if (!plugin.isValid())
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.";
239 plugins.push_back(std::move(plugin));
258 for (
const auto &loc : comments) {
259 const QString comment = code.
mid(loc.offset, loc.length);
264 if (words.size() < 2)
270 const auto categoryExists = std::any_of(
271 loggerCategories.cbegin(), loggerCategories.cend(),
286 const QString command = words.at(1);
287 if (command == u
"disable"_s) {
288 if (
const qsizetype lineIndex = loc.startLine - 1; lineIndex < lines.size()) {
292 bool lineHasContent =
false;
294 if (!preComment[
i].isSpace()) {
295 lineHasContent =
true;
301 oneLineDisablesPerLine[loc.startLine] |=
categories;
305 }
else if (command == u
"enable"_s) {
306 enablesPerLine[loc.startLine + 1] |=
categories;
308 logger->
log(u
"Invalid qmllint directive \"%1\" provided"_s.arg(command),
313 if (disablesPerLine.
isEmpty() && oneLineDisablesPerLine.
isEmpty())
318 currentlyDisabled.
unite(disablesPerLine[
i]).subtract(enablesPerLine[
i]);
320 currentlyDisabled.
unite(oneLineDisablesPerLine[
i]);
322 if (!currentlyDisabled.
isEmpty())
325 currentlyDisabled.
subtract(oneLineDisablesPerLine[
i]);
330 QAnyStringView id,
const std::optional<QQmlJSFixSuggestion> &suggestion = {})
343 type = u
"critical"_s;
356 jsonMessage[u
"type"_s] =
type;
357 jsonMessage[u
"id"_s] =
id.toString();
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);
366 jsonMessage[u
"message"_s] =
message.message;
375 if (suggestion.has_value()) {
377 {
"message"_L1, suggestion->fixDescription() },
378 {
"replacement"_L1, suggestion->replacement() },
379 {
"isHint"_L1, !suggestion->isAutoApplicable() },
381 convertLocation(suggestion->location(), &jsonFix);
382 const QString filename = suggestion->filename();
384 jsonFix.
insert(
"fileName"_L1, filename);
385 suggestions << jsonFix;
388 if (!
hint.isEmpty()) {
392 {
"message"_L1,
hint },
393 {
"replacement"_L1,
QString() },
394 {
"isHint"_L1,
true }
397 suggestions << jsonHint;
400 jsonMessage[u
"suggestions"] = suggestions;
402 warnings << jsonMessage;
406void QQmlJSLinter::processMessages(
QJsonArray &warnings)
408 for (
const auto &
error : m_logger->errors())
410 for (
const auto &warning : m_logger->warnings())
412 for (
const auto &
info : m_logger->infos())
417 const QString *fileContents,
const bool silent,
436 result[u
"warnings"] = warnings;
437 result[u
"success"] = success;
444 if (fileContents ==
nullptr) {
455 }
else if (!silent) {
464 code = *fileContents;
467 m_fileContents = code;
475 const bool isJavaScript = isESModule || lowerSuffix ==
QLatin1String(
"js");
477 lexer.
setCode(code, 1, !isJavaScript);
478 QQmlJS::Parser parser(&
engine);
480 success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram())
484 const auto diagnosticMessages = parser.diagnosticMessages();
488 }
else if (!silent) {
491 .
arg(
m.loc.startLine)
492 .
arg(
m.loc.startColumn)
499 if (success && !isJavaScript) {
513 m_logger->
fileName(), m_importer.resourceFileMapper()),
516 if (m_enablePlugins) {
517 for (
const Plugin &plugin : m_plugins) {
531 parseComments(m_logger.
get(),
engine.comments());
546 typeResolver.
init(&
v, parser.rootNode());
549 literalCheck.
run(&
v, &typeResolver);
553 if (m_enablePlugins) {
556 for (
const Plugin &plugin : m_plugins) {
557 if (!plugin.isValid() || !plugin.isEnabled())
573 processMessages(warnings);
581 (resourcePaths.size() == 1) ? u
':' + resourcePaths.first() : filename;
584 codegen.setTypeResolver(std::move(typeResolver));
586 codegen.setPassManager(passMan.
get());
601 if (!globalWarnings.
isEmpty()) {
610 processMessages(warnings);
613 if (resourceFiles.isEmpty()) {
638 if (!resourceFiles.isEmpty())
652 result[u
"module"_s] = module;
654 result[u
"warnings"] = warnings;
655 result[u
"success"] = success;
670 if (!importWarnings.
isEmpty()) {
679 const QString modulePrefix = u
"$module$."_s;
680 const QString internalPrefix = u
"$internal$."_s;
682 for (
auto &&[
typeName, importedScope] :
types.types().asKeyValueRange()) {
686 if (
name.startsWith(modulePrefix))
689 if (
name.startsWith(internalPrefix)) {
695 missingTypes[
name] = {};
701 partiallyResolvedTypes[
name] = {};
704 if (
property.typeName().isEmpty()) {
710 missingTypes[
property.typeName()]
711 << scope->
internalName() + u
'.' +
property.propertyName();
714 if (!
property.type()->isFullyResolved()) {
715 partiallyResolvedTypes[
property.typeName()]
716 << scope->
internalName() + u
'.' +
property.propertyName();
720 m_logger->
log(u
"Attached type of \"%1\" not fully resolved"_s.arg(
name),
725 if (
method.returnTypeName().isEmpty())
727 if (
method.returnType().isNull()) {
728 missingTypes[
method.returnTypeName()] << u
"return type of "_s
730 }
else if (!
method.returnType()->isFullyResolved()) {
731 partiallyResolvedTypes[
method.returnTypeName()] << u
"return type of "_s
735 const auto parameters =
method.parameters();
737 auto ¶meter = parameters[
i];
743 missingTypes[
typeName] << u
"parameter %1 of "_s.arg(
i + 1)
747 if (!
type->isFullyResolved()) {
748 partiallyResolvedTypes[
typeName] << u
"parameter %1 of "_s.arg(
i + 1)
759 if (!uses.isEmpty()) {
761 message += u
". Used in %1"_s.arg(usesList.join(u
", "_s));
770 if (!uses.isEmpty()) {
772 message += u
". Used in %1"_s.arg(usesList.join(u
", "_s));
779 processMessages(warnings);
794 if (m_logger ==
nullptr)
806 const bool isJavaScript = isESModule || lowerSuffix ==
QLatin1String(
"js");
808 if (isESModule || isJavaScript)
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())
817 const QString filename = msg.fixSuggestion->filename();
823 fixesToApply << msg.fixSuggestion.
value();
829 std::sort(fixesToApply.
begin(), fixesToApply.
end(),
831 return a.location().offset < b.location().offset;
834 for (
auto it = fixesToApply.
begin();
it + 1 != fixesToApply.
end();
it++) {
839 qWarning() <<
"Fixes for two warnings are overlapping, aborting. Please file a bug "
845 int offsetChange = 0;
847 for (
const auto &fix : fixesToApply) {
853 const QString replacement = fix.replacement();
854 code = before + replacement + after;
855 offsetChange += replacement.
size() - fixLocation.
length;
861 lexer.
setCode(code, 1, !isJavaScript);
862 QQmlJS::Parser parser(&
engine);
864 bool success = parser.parse();
867 const auto diagnosticMessages = parser.diagnosticMessages();
870 qDebug() <<
"File became unparseable after suggestions were applied. Please file a bug "
879 .
arg(
m.loc.startLine)
880 .
arg(
m.loc.startColumn)
void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation) override
CodegenWarningInterface(QQmlJSLogger *logger)
The QDirIterator class provides an iterator for directory entrylists.
static QChar separator()
Returns the native directory separator: "/" under Unix and "\\" under Windows.
FileError error() const
Returns the file error status.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
QString suffix() const
Returns the suffix (extension) of the file.
QString absoluteFilePath() const
Returns an absolute path including the file name.
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
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
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
bool contains(const QString &key) const
Returns true if the object contains key key.
\inmodule QtCore\reentrant
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.
bool isEmpty() const noexcept
T value(qsizetype i) const
static void setFilterRules(const QString &rules)
Configures which categories and message types should be enabled through a set of rules.
bool contains(const Key &key) const
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)
void setCode(const QString &code)
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())
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
virtual void registerPasses(PassManager *manager, const Element &rootElement)=0
Adds a pass manager that will be executed on rootElement.
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.
const_iterator cend() const noexcept
iterator find(const T &value)
QSet< T > & unite(const QSet< T > &other)
const_iterator cbegin() const noexcept
QSet< T > & subtract(const QSet< T > &other)
\macro QT_RESTRICTED_CAST_FROM_ASCII
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
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.
qsizetype size() const
Returns the number of characters in this string.
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
QString & insert(qsizetype i, QChar c)
QString toLower() const &
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
const QLoggingCategory & category()
[1]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
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]
static bool contains(const QJsonArray &haystack, unsigned needle)
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLsizei GLenum GLenum * types
GLsizei const GLuint * paths
GLuint GLsizei const GLchar * message
GLsizei GLsizei GLchar * source
GLsizei GLenum * categories
GLsizei const GLchar *const * path
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
QLatin1StringView QLatin1String
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
QFileInfo info(fileName)
[8]
char * toString(const MyType &t)
[31]
static Filter localFileFilter(const QString &file)
QJsonObject metaData() const
\variable QStaticPlugin::instance
QtPluginInstanceFunction instance