qmllint: Warn about failing imports

Warn when an import fails instead of silently ignoring them.
Without these it was impossible to tell whether a type was not resolved due to an unresolvable import or due to other reasons.

It also fixes the previous behavior of just dumping the types of qmltypes into the rootScope if manually imported.
Now they are only are present when actually importing one of the modules mentioned in the type's export statement.

Change-Id: I62bdd99828702ed02512eaac30f7f99b98d1df28
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Maximilian Goldstein 2021-03-19 13:11:03 +01:00
parent b07d653c09
commit 6ddb78735b
5 changed files with 46 additions and 19 deletions

View File

@ -269,25 +269,40 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
/*! /*!
* Imports types from the specified \a qmltypesFiles. * Imports types from the specified \a qmltypesFiles.
*/ */
QQmlJSImporter::ImportedTypes QQmlJSImporter::importQmltypes(const QStringList &qmltypesFiles) void QQmlJSImporter::importQmltypes(const QStringList &qmltypesFiles)
{ {
AvailableTypes types(builtinImportHelper().cppNames); AvailableTypes types(builtinImportHelper().cppNames);
Import result;
for (const auto &qmltypeFile : qmltypesFiles) for (const auto &qmltypeFile : qmltypesFiles) {
Import result;
readQmltypes(qmltypeFile, &result.objects, &result.dependencies); readQmltypes(qmltypeFile, &result.objects, &result.dependencies);
importDependencies(result, &types); // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere else except for cache lookups,
processImport(result, &types); // it will blow up due to a missing file instead of producing weird results.
const QString qmldirName = qmltypeFile + QStringLiteral("_FAKE_QMLDIR");
m_seenQmldirFiles.insert(qmldirName, result);
return types.qmlNames; for (const auto &object : result.objects.values()) {
for (const auto &ex : object->exports()) {
m_seenImports.insert({ex.package(), ex.version()}, qmldirName);
// We also have to handle the case that no version is provided
m_seenImports.insert({ex.package(), QTypeRevision()}, qmldirName);
}
}
}
} }
QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule( QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(
const QString &module, const QString &prefix, QTypeRevision version) const QString &module, const QString &prefix, QTypeRevision version, QQmlJS::SourceLocation location)
{ {
AvailableTypes result(builtinImportHelper().cppNames); AvailableTypes result(builtinImportHelper().cppNames);
importHelper(module, &result, prefix, version); if (!importHelper(module, &result, prefix, version)) {
m_warnings.append({
QStringLiteral("Failed to import %1. Are your include paths set up properly?").arg(module),
QtWarningMsg,
location
});
}
return result.qmlNames; return result.qmlNames;
} }
@ -296,20 +311,22 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames()
return builtinImportHelper().cppNames; return builtinImportHelper().cppNames;
} }
void QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
const QString &prefix, QTypeRevision version) const QString &prefix, QTypeRevision version)
{ {
const QPair<QString, QTypeRevision> importId { module, version }; const QPair<QString, QTypeRevision> importId { module, version };
const auto it = m_seenImports.constFind(importId); const auto it = m_seenImports.constFind(importId);
if (it != m_seenImports.constEnd()) { if (it != m_seenImports.constEnd()) {
if (it->isEmpty()) if (it->isEmpty())
return; // TODO: warn here in the future return false;
const auto import = m_seenQmldirFiles.constFind(*it); const auto import = m_seenQmldirFiles.constFind(*it);
Q_ASSERT(import != m_seenQmldirFiles.constEnd()); Q_ASSERT(import != m_seenQmldirFiles.constEnd());
importDependencies(*import, types, prefix, version); importDependencies(*import, types, prefix, version);
processImport(*import, types, prefix); processImport(*import, types, prefix);
return; return true;
} }
const auto modulePaths = qQmlResolveImportPaths(module, m_importPaths, version); const auto modulePaths = qQmlResolveImportPaths(module, m_importPaths, version);
@ -321,7 +338,7 @@ void QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
m_seenImports.insert(importId, qmldirPath); m_seenImports.insert(importId, qmldirPath);
importDependencies(*it, types, prefix, version); importDependencies(*it, types, prefix, version);
processImport(*it, types, prefix); processImport(*it, types, prefix);
return; return true;
} }
const QFileInfo file(qmldirPath); const QFileInfo file(qmldirPath);
@ -331,12 +348,13 @@ void QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types,
m_seenImports.insert(importId, qmldirPath); m_seenImports.insert(importId, qmldirPath);
importDependencies(import, types, prefix, version); importDependencies(import, types, prefix, version);
processImport(import, types, prefix); processImport(import, types, prefix);
return; return true;
} }
} }
// TODO: warn here in the future
m_seenImports.insert(importId, QString()); m_seenImports.insert(importId, QString());
return false;
} }
QQmlJSScope::Ptr QQmlJSImporter::localFile2ScopeTree(const QString &filePath) QQmlJSScope::Ptr QQmlJSImporter::localFile2ScopeTree(const QString &filePath)

View File

@ -59,14 +59,15 @@ public:
QQmlJSResourceFileMapper *resourceFileMapper() { return m_mapper; } QQmlJSResourceFileMapper *resourceFileMapper() { return m_mapper; }
ImportedTypes importBuiltins(); ImportedTypes importBuiltins();
ImportedTypes importQmltypes(const QStringList &qmltypesFiles); void importQmltypes(const QStringList &qmltypesFiles);
QQmlJSScope::Ptr importFile(const QString &file); QQmlJSScope::Ptr importFile(const QString &file);
ImportedTypes importDirectory(const QString &directory, const QString &prefix = QString()); ImportedTypes importDirectory(const QString &directory, const QString &prefix = QString());
ImportedTypes importModule( ImportedTypes importModule(
const QString &module, const QString &prefix = QString(), const QString &module, const QString &prefix = QString(),
QTypeRevision version = QTypeRevision()); QTypeRevision version = QTypeRevision(),
QQmlJS::SourceLocation location = QQmlJS::SourceLocation());
ImportedTypes builtinInternalNames(); ImportedTypes builtinInternalNames();
@ -101,7 +102,7 @@ private:
}; };
AvailableTypes builtinImportHelper(); AvailableTypes builtinImportHelper();
void importHelper(const QString &module, AvailableTypes *types, bool importHelper(const QString &module, AvailableTypes *types,
const QString &prefix = QString(), const QString &prefix = QString(),
QTypeRevision version = QTypeRevision()); QTypeRevision version = QTypeRevision());
void processImport(const Import &import, AvailableTypes *types, void processImport(const Import &import, AvailableTypes *types,

View File

@ -176,7 +176,7 @@ void QQmlJSImportVisitor::importBaseModules()
m_rootScopeImports = m_importer->importBuiltins(); m_rootScopeImports = m_importer->importBuiltins();
if (!m_qmltypesFiles.isEmpty()) if (!m_qmltypesFiles.isEmpty())
m_rootScopeImports.insert(m_importer->importQmltypes(m_qmltypesFiles)); m_importer->importQmltypes(m_qmltypesFiles);
m_rootScopeImports.insert(m_importer->importDirectory(m_implicitImportDirectory)); m_rootScopeImports.insert(m_importer->importDirectory(m_implicitImportDirectory));
m_errors.append(m_importer->takeWarnings()); m_errors.append(m_importer->takeWarnings());
@ -538,7 +538,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
path.chop(1); path.chop(1);
const auto imported = m_importer->importModule( const auto imported = m_importer->importModule(
path, prefix, import->version ? import->version->version : QTypeRevision()); path, prefix, import->version ? import->version->version : QTypeRevision(), import->firstSourceLocation());
m_rootScopeImports.insert(imported); m_rootScopeImports.insert(imported);

View File

@ -0,0 +1,4 @@
import QtQml
import FooBar
QtObject {}

View File

@ -379,6 +379,10 @@ void TestQmllint::dirtyQmlCode_data()
<< QStringLiteral("defaultPropertyWithWrongType2.qml") << QStringLiteral("defaultPropertyWithWrongType2.qml")
<< QStringLiteral("Cannot assign to default property of incompatible type") << QStringLiteral("Cannot assign to default property of incompatible type")
<< QStringLiteral("Cannot assign to non-existent default property"); << QStringLiteral("Cannot assign to non-existent default property");
QTest::newRow("InvalidImport")
<< QStringLiteral("invalidImport.qml")
<< QStringLiteral("Failed to import FooBar. Are your include paths set up properly?")
<< QString();
} }
void TestQmllint::dirtyQmlCode() void TestQmllint::dirtyQmlCode()