aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmllint/findunqualified.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qmllint/findunqualified.cpp')
-rw-r--r--tools/qmllint/findunqualified.cpp757
1 files changed, 414 insertions, 343 deletions
diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp
index d72f02b94a..807110c3c1 100644
--- a/tools/qmllint/findunqualified.cpp
+++ b/tools/qmllint/findunqualified.cpp
@@ -27,19 +27,24 @@
****************************************************************************/
#include "findunqualified.h"
+#include "importedmembersvisitor.h"
#include "scopetree.h"
+#include "typedescriptionreader.h"
-#include "qmljstypedescriptionreader.h"
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+#include <QtQml/private/qqmljsparser_p.h>
+#include <QtQml/private/qv4codegen_p.h>
+#include <QtQml/private/qqmldirparser_p.h>
-#include <QFile>
-#include <QDirIterator>
-#include <QScopedValueRollback>
+#include <QtCore/qfile.h>
+#include <QtCore/qdiriterator.h>
+#include <QtCore/qscopedvaluerollback.h>
-#include <private/qqmljsast_p.h>
-#include <private/qqmljslexer_p.h>
-#include <private/qqmljsparser_p.h>
-#include <private/qv4codegen_p.h>
-#include <private/qqmldirparser_p.h>
+static const QString prefixedName(const QString &prefix, const QString &name)
+{
+ return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
+}
static QQmlDirParser createQmldirParserForFile(const QString &filename)
{
@@ -50,17 +55,17 @@ static QQmlDirParser createQmldirParserForFile(const QString &filename)
return parser;
}
-static QQmlJS::TypeDescriptionReader createQmltypesReaderForFile(QString const &filename)
+static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename)
{
QFile f(filename);
f.open(QFile::ReadOnly);
- QQmlJS::TypeDescriptionReader reader { filename, f.readAll() };
+ TypeDescriptionReader reader { filename, f.readAll() };
return reader;
}
-void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name)
+void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name)
{
- m_currentScope = m_currentScope->createNewChildScope(type, name);
+ m_currentScope = m_currentScope->createNewChildScope(type, name).get();
}
void FindUnqualifiedIDVisitor::leaveEnvironment()
@@ -68,9 +73,53 @@ void FindUnqualifiedIDVisitor::leaveEnvironment()
m_currentScope = m_currentScope->parentScope();
}
+void FindUnqualifiedIDVisitor::parseHeaders(QQmlJS::AST::UiHeaderItemList *header)
+{
+ using namespace QQmlJS::AST;
+
+ while (header) {
+ if (auto import = cast<UiImport *>(header->headerItem)) {
+ if (import->version) {
+ QString path;
+ auto uri = import->importUri;
+ while (uri) {
+ path.append(uri->name);
+ path.append("/");
+ uri = uri->next;
+ }
+ path.chop(1);
+ QString prefix = QLatin1String("");
+ if (import->asToken.isValid()) {
+ prefix += import->importId + QLatin1Char('.');
+ }
+ importHelper(path, prefix, import->version->majorVersion,
+ import->version->minorVersion);
+ }
+ }
+ header = header->next;
+ }
+}
+
+ScopeTree *FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program,
+ const QString &name)
+{
+ using namespace QQmlJS::AST;
+ ScopeTree *result = new ScopeTree(ScopeType::JSLexicalScope, name);
+ for (auto *statement = program->statements; statement; statement = statement->next) {
+ if (auto *function = cast<FunctionDeclaration *>(statement->statement)) {
+ MetaMethod method(function->name.toString());
+ method.setMethodType(MetaMethod::Method);
+ for (auto *parameters = function->formals; parameters; parameters = parameters->next)
+ method.addParameter(parameters->element->bindingIdentifier.toString(), "");
+ result->addMethod(method);
+ }
+ }
+ return result;
+}
+
enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath };
-QStringList completeImportPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
+QStringList completeImportPaths(const QString &uri, const QString &basePath, int vmaj, int vmin)
{
static const QLatin1Char Slash('/');
static const QLatin1Char Backslash('\\');
@@ -79,21 +128,22 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths
QStringList qmlDirPathsPaths;
// fully & partially versioned parts + 1 unversioned for each base path
- qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1));
+ qmlDirPathsPaths.reserve(2 * parts.count() + 1);
auto versionString = [](int vmaj, int vmin, ImportVersion version)
{
if (version == FullyVersioned) {
// extension with fully encoded version number (eg. MyModule.3.2)
- return QString::asprintf(".%d.%d", vmaj, vmin);
- } else if (version == PartiallyVersioned) {
+ return QString::fromLatin1(".%1.%2").arg(vmaj).arg(vmin);
+ }
+ if (version == PartiallyVersioned) {
// extension with encoded version major (eg. MyModule.3)
- return QString::asprintf(".%d", vmaj);
- } // else extension without version number (eg. MyModule)
+ return QString::fromLatin1(".%1").arg(vmaj);
+ }
+ // else extension without version number (eg. MyModule)
return QString();
};
- auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep)
- {
+ auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep) {
QString str;
for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
if (it != refs.cbegin())
@@ -103,292 +153,242 @@ QStringList completeImportPaths(const QString &uri, const QStringList &basePaths
return str;
};
- for (int version = FullyVersioned; version <= BasePath; ++version) {
+ const ImportVersion initial = (vmin >= 0)
+ ? FullyVersioned
+ : (vmaj >= 0 ? PartiallyVersioned : Unversioned);
+ for (int version = initial; version <= BasePath; ++version) {
const QString ver = versionString(vmaj, vmin, static_cast<ImportVersion>(version));
- for (const QString &path : basePaths) {
- QString dir = path;
- if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
- dir += Slash;
+ QString dir = basePath;
+ if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
+ dir += Slash;
- if (version == BasePath) {
- qmlDirPathsPaths += dir;
- } else {
- // append to the end
- qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver;
- }
+ if (version == BasePath) {
+ qmlDirPathsPaths += dir;
+ } else {
+ // append to the end
+ qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver;
+ }
- if (version < Unversioned) {
- // insert in the middle
- for (int index = parts.count() - 2; index >= 0; --index) {
- qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
- + ver + Slash
- + joinStringRefs(parts.mid(index + 1), Slash);
- }
+ if (version < Unversioned) {
+ // insert in the middle
+ for (int index = parts.count() - 2; index >= 0; --index) {
+ qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
+ + ver + Slash
+ + joinStringRefs(parts.mid(index + 1), Slash);
}
}
}
return qmlDirPathsPaths;
}
-void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor)
-{
- QPair<QString, QString> importId { id, prefix };
- if (m_alreadySeenImports.contains(importId)) {
- return;
- } else {
- m_alreadySeenImports.insert(importId);
- }
- id = id.replace(QLatin1String("/"), QLatin1String("."));
- auto qmltypesPaths = completeImportPaths(id, m_qmltypeDirs, major, minor);
+static const QLatin1String SlashQmldir = QLatin1String("/qmldir");
+static const QLatin1String SlashAppDotQmltypes = QLatin1String("/app.qmltypes");
+static const QLatin1String SlashLibDotQmltypes = QLatin1String("/lib.qmltypes");
+static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes");
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
- QList<QQmlJS::ModuleApiInfo> moduleApis;
- QStringList dependencies;
- static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes");
- static const QLatin1String SlashQmldir("/qmldir");
- for (auto const &qmltypesPath : qmltypesPaths) {
- if (QFile::exists(qmltypesPath + SlashQmldir)) {
- auto reader = createQmldirParserForFile(qmltypesPath + SlashQmldir);
- const auto imports = reader.imports();
- for (const QString &import : imports)
- importHelper(import, prefix, major, minor);
-
- QHash<QString, LanguageUtils::FakeMetaObject *> qmlComponents;
- const auto components = reader.components();
- for (auto it = components.begin(), end = components.end(); it != end; ++it) {
- const QString filePath = qmltypesPath + QLatin1Char('/') + it->fileName;
- if (!QFile::exists(filePath)) {
- m_colorOut.write(QLatin1String("warning: "), Warning);
- m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ")
- + qmltypesPath + SlashQmldir
- + QLatin1String(" but does not exist.\n"));
- continue;
- }
+void FindUnqualifiedIDVisitor::readQmltypes(const QString &filename,
+ FindUnqualifiedIDVisitor::Import &result)
+{
+ auto reader = createQmltypesReaderForFile(filename);
+ auto succ = reader(&result.objects, &result.moduleApis, &result.dependencies);
+ if (!succ)
+ m_colorOut.writeUncolored(reader.errorMessage());
+}
- auto mo = qmlComponents.find(it.key());
- if (mo == qmlComponents.end())
- mo = qmlComponents.insert(it.key(), localQmlFile2FakeMetaObject(filePath));
+FindUnqualifiedIDVisitor::Import FindUnqualifiedIDVisitor::readQmldir(const QString &path)
+{
+ Import result;
+ auto reader = createQmldirParserForFile(path + SlashQmldir);
+ const auto imports = reader.imports();
+ for (const QString &import : imports)
+ result.dependencies.append(import);
- (*mo)->addExport(
- it.key(), reader.typeNamespace(),
- LanguageUtils::ComponentVersion(it->majorVersion, it->minorVersion));
- }
- for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) {
- objects.insert(it.key(),
- QSharedPointer<const LanguageUtils::FakeMetaObject>(it.value()));
- }
- }
- if (QFile::exists(qmltypesPath + SlashPluginsDotQmltypes)) {
- auto reader = createQmltypesReaderForFile(qmltypesPath + SlashPluginsDotQmltypes);
- auto succ = reader(&objects, &moduleApis, &dependencies);
- if (!succ)
- m_colorOut.writeUncolored(reader.errorMessage());
+ QHash<QString, ScopeTree *> qmlComponents;
+ const auto components = reader.components();
+ for (auto it = components.begin(), end = components.end(); it != end; ++it) {
+ const QString filePath = path + QLatin1Char('/') + it->fileName;
+ if (!QFile::exists(filePath)) {
+ m_colorOut.write(QLatin1String("warning: "), Warning);
+ m_colorOut.write(it->fileName + QLatin1String(" is listed as component in ")
+ + path + SlashQmldir
+ + QLatin1String(" but does not exist.\n"));
+ continue;
}
+
+ auto mo = qmlComponents.find(it.key());
+ if (mo == qmlComponents.end())
+ mo = qmlComponents.insert(it.key(), localFile2ScopeTree(filePath));
+
+ (*mo)->addExport(
+ it.key(), reader.typeNamespace(),
+ ComponentVersion(it->majorVersion, it->minorVersion));
}
- for (auto const &dependency : qAsConst(dependencies)) {
+ for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it)
+ result.objects.insert( it.key(), ScopeTree::ConstPtr(it.value()));
+
+ if (!reader.plugins().isEmpty() && QFile::exists(path + SlashPluginsDotQmltypes))
+ readQmltypes(path + SlashPluginsDotQmltypes, result);
+
+ return result;
+}
+
+void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUnqualifiedIDVisitor::Import &import)
+{
+ for (auto const &dependency : qAsConst(import.dependencies)) {
auto const split = dependency.split(" ");
- auto const id = split.at(0);
- auto const major = split.at(1).split('.').at(0).toInt();
- auto const minor = split.at(1).split('.').at(1).toInt();
- importHelper(id, QString(), major, minor);
+ auto const &id = split.at(0);
+ if (split.length() > 1) {
+ const auto version = split.at(1).split('.');
+ importHelper(id, QString(),
+ version.at(0).toInt(),
+ version.length() > 1 ? version.at(1).toInt() : -1);
+ } else {
+ importHelper(id, QString(), -1, -1);
+ }
+
+
}
+
// add objects
- for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) {
- auto val = ob_it.value();
- m_exportedName2MetaObject[prefix + val->className()] = val;
- for (auto export_ : val->exports()) {
- m_exportedName2MetaObject[prefix + export_.type] = val;
- }
- for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) {
- m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name());
+ for (auto it = import.objects.begin(); it != import.objects.end(); ++it) {
+ const auto &val = it.value();
+ m_types[it.key()] = val;
+ m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val);
+
+ const auto exports = val->exports();
+ for (const auto &valExport : exports)
+ m_exportedName2Scope.insert(prefixedName(prefix, valExport.type()), val);
+
+ const auto enums = val->enums();
+ for (const auto &valEnum : enums)
+ m_currentScope->addEnum(valEnum);
+ }
+}
+
+void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString &prefix,
+ int major, int minor)
+{
+ const QString id = QString(module).replace(QLatin1Char('/'), QLatin1Char('.'));
+ QPair<QString, QString> importId { id, prefix };
+ if (m_alreadySeenImports.contains(importId))
+ return;
+ m_alreadySeenImports.insert(importId);
+
+ for (const QString &qmltypeDir : m_qmltypeDirs) {
+ auto qmltypesPaths = completeImportPaths(id, qmltypeDir, major, minor);
+
+ for (auto const &qmltypesPath : qmltypesPaths) {
+ if (QFile::exists(qmltypesPath + SlashQmldir)) {
+ processImport(prefix, readQmldir(qmltypesPath));
+
+ // break so that we don't import unversioned qml components
+ // in addition to versioned ones
+ break;
+ }
+
+ Import result;
+ if (QFile::exists(qmltypesPath + SlashAppDotQmltypes))
+ readQmltypes(qmltypesPath + SlashAppDotQmltypes, result);
+ else if (QFile::exists(qmltypesPath + SlashLibDotQmltypes))
+ readQmltypes(qmltypesPath + SlashLibDotQmltypes, result);
+ else
+ continue;
+ processImport(prefix, result);
}
}
}
-LanguageUtils::FakeMetaObject *
-FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath)
+ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath)
{
using namespace QQmlJS::AST;
- auto fake = new LanguageUtils::FakeMetaObject;
- QString baseName = QFileInfo { filePath }.baseName();
- fake->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName);
+ const QFileInfo info { filePath };
+ QString baseName = info.baseName();
+ const QString scopeName = baseName.endsWith(".ui") ? baseName.chopped(3) : baseName;
+
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+
+ const QString lowerSuffix = info.suffix().toLower();
+ const bool isESModule = lowerSuffix == QLatin1String("mjs");
+ const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js");
+
QFile file(filePath);
if (!file.open(QFile::ReadOnly)) {
- return fake;
+ return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
+ scopeName);
}
+
QString code = file.readAll();
file.close();
- QQmlJS::Engine engine;
- QQmlJS::Lexer lexer(&engine);
-
- lexer.setCode(code, 1, true);
+ lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript);
QQmlJS::Parser parser(&engine);
- if (!parser.parse()) {
- return fake;
- }
- QQmlJS::AST::UiProgram *program = parser.ast();
- auto header = program->headers;
- while (header) {
- if (auto import = cast<UiImport *>(header->headerItem)) {
- if (import->version) {
- QString path;
- auto uri = import->importUri;
- while (uri) {
- path.append(uri->name);
- path.append("/");
- uri = uri->next;
- }
- path.chop(1);
- QString prefix = QLatin1String("");
- if (import->asToken.isValid()) {
- prefix += import->importId + QLatin1Char('.');
- }
- importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion);
- }
- }
- header = header->next;
- }
- auto member = program->members;
- // member should be the sole element
- Q_ASSERT(!member->next);
- Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition);
- auto definition = static_cast<UiObjectDefinition *>(member->member);
- auto qualifiedId = definition->qualifiedTypeNameId;
- while (qualifiedId && qualifiedId->next) {
- qualifiedId = qualifiedId->next;
+
+ const bool success = isJavaScript ? (isESModule ? parser.parseModule()
+ : parser.parseProgram())
+ : parser.parse();
+ if (!success) {
+ return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope,
+ scopeName);
}
- fake->setSuperclassName(qualifiedId->name.toString());
- UiObjectMemberList *initMembers = definition->initializer->members;
- while (initMembers) {
- switch (initMembers->member->kind) {
- case UiObjectMember::Kind_UiArrayBinding: {
- // nothing to do
- break;
- }
- case UiObjectMember::Kind_UiEnumDeclaration: {
- // nothing to do
- break;
- }
- case UiObjectMember::Kind_UiObjectBinding: {
- // nothing to do
- break;
- }
- case UiObjectMember::Kind_UiObjectDefinition: {
- // creates nothing accessible
- break;
- }
- case UiObjectMember::Kind_UiPublicMember: {
- auto publicMember = static_cast<UiPublicMember *>(initMembers->member);
- switch (publicMember->type) {
- case UiPublicMember::Signal: {
- UiParameterList *param = publicMember->parameters;
- LanguageUtils::FakeMetaMethod method;
- method.setMethodType(LanguageUtils::FakeMetaMethod::Signal);
- method.setMethodName(publicMember->name.toString());
- while (param) {
- method.addParameter(param->name.toString(), param->type->name.toString());
- param = param->next;
- }
- fake->addMethod(method);
- break;
- }
- case UiPublicMember::Property: {
- LanguageUtils::FakeMetaProperty fakeprop { publicMember->name.toString(),
- publicMember->typeModifier.toString(),
- false,
- false,
- false,
- 0 };
- fake->addProperty(fakeprop);
- break;
- }
- }
- break;
- }
- case UiObjectMember::Kind_UiScriptBinding: {
- // does not create anything new, ignore
- break;
- }
- case UiObjectMember::Kind_UiSourceElement: {
- auto sourceElement = static_cast<UiSourceElement *>(initMembers->member);
- if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) {
- LanguageUtils::FakeMetaMethod method;
- method.setMethodName(fexpr->name.toString());
- method.setMethodType(LanguageUtils::FakeMetaMethod::Method);
- FormalParameterList *parameters = fexpr->formals;
- while (parameters) {
- method.addParameter(parameters->element->bindingIdentifier.toString(),
- "");
- parameters = parameters->next;
- }
- fake->addMethod(method);
- } else if (ClassExpression *clexpr =
- sourceElement->sourceElement->asClassDefinition()) {
- LanguageUtils::FakeMetaProperty prop {
- clexpr->name.toString(), "", false, false, false, 1
- };
- fake->addProperty(prop);
- } else if (cast<VariableStatement *>(sourceElement->sourceElement)) {
- // nothing to do
- } else {
- const auto loc = sourceElement->firstSourceLocation();
- m_colorOut.writeUncolored(
- "unsupportedd sourceElement at "
- + QString::fromLatin1("%1:%2: ").arg(loc.startLine).arg(loc.startColumn)
- + QString::number(sourceElement->sourceElement->kind));
- }
- break;
- }
- default: {
- m_colorOut.writeUncolored("unsupported element of kind "
- + QString::number(initMembers->member->kind));
- }
- }
- initMembers = initMembers->next;
+
+ if (!isJavaScript) {
+ QQmlJS::AST::UiProgram *program = parser.ast();
+ parseHeaders(program->headers);
+ ImportedMembersVisitor membersVisitor(&m_colorOut);
+ program->members->accept(&membersVisitor);
+ return membersVisitor.result(scopeName);
}
- return fake;
+
+ // TODO: Anything special to do with ES modules here?
+ return parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scopeName);
}
-void FindUnqualifiedIDVisitor::importDirectory(const QString &directory, const QString &prefix)
+void FindUnqualifiedIDVisitor::importFileOrDirectory(const QString &fileOrDirectory,
+ const QString &prefix)
{
- QString dirname = directory;
- QFileInfo info { dirname };
- if (info.isRelative())
- dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname);
+ QString name = fileOrDirectory;
+
+ if (QFileInfo(name).isRelative())
+ name = QDir(QFileInfo { m_filePath }.path()).filePath(name);
+
+ if (QFileInfo(name).isFile()) {
+ m_exportedName2Scope.insert(prefix, ScopeTree::ConstPtr(localFile2ScopeTree(name)));
+ return;
+ }
- QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
+ QDirIterator it { name, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
while (it.hasNext()) {
- LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next());
- m_exportedName2MetaObject.insert(
- prefix + fake->className(),
- QSharedPointer<const LanguageUtils::FakeMetaObject>(fake));
+ ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next()));
+ if (!scope->className().isEmpty())
+ m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), scope);
}
}
-void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name)
+void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QString name)
{
for (;;) {
- auto metaObject = m_exportedName2MetaObject[m_exportedName2MetaObject.contains(name)
- ? name
- : prefix + QLatin1Char('.') + name];
- if (metaObject) {
- auto propertyCount = metaObject->propertyCount();
- for (auto i = 0; i < propertyCount; ++i) {
- m_currentScope->insertPropertyIdentifier(metaObject->property(i).name());
+ ScopeTree::ConstPtr scope = m_exportedName2Scope.value(m_exportedName2Scope.contains(name)
+ ? name
+ : prefix + QLatin1Char('.') + name);
+ if (scope) {
+ const auto properties = scope->properties();
+ for (auto property : properties) {
+ property.setType(m_exportedName2Scope.value(property.typeName()).get());
+ m_currentScope->insertPropertyIdentifier(property);
}
- m_currentScope->addMethodsFromMetaObject(metaObject);
-
- name = metaObject->superclassName();
- if (name.isEmpty() || name == QLatin1String("QObject")) {
+ m_currentScope->addMethods(scope->methods());
+ name = scope->superclassName();
+ if (name.isEmpty() || name == QLatin1String("QObject"))
break;
- }
} else {
m_colorOut.write(QLatin1String("warning: "), Warning);
- m_colorOut.write(name + QLatin1String(" was not found. Did you add all import paths?\n"));
+ m_colorOut.write(name + QLatin1String(" was not found."
+ " Did you add all import paths?\n"));
m_unknownImports.insert(name);
+ m_visitFailed = true;
break;
}
}
@@ -404,8 +404,8 @@ void FindUnqualifiedIDVisitor::throwRecursionDepthError()
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *)
{
enterEnvironment(ScopeType::QMLScope, "program");
- QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
- QList<QQmlJS::ModuleApiInfo> moduleApis;
+ QHash<QString, ScopeTree::ConstPtr> objects;
+ QList<ModuleApiInfo> moduleApis;
QStringList dependencies;
for (auto const &dir : m_qmltypeDirs) {
QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter,
@@ -418,29 +418,23 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *)
}
}
// add builtins
- for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) {
- auto val = ob_it.value();
- for (auto export_ : val->exports()) {
- m_exportedName2MetaObject[export_.type] = val;
- }
- for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) {
- m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name());
- }
- }
- // add "self" (as we only ever check the first part of a qualified identifier, we get away with
- // using an empty FakeMetaObject
- m_exportedName2MetaObject[QFileInfo { m_filePath }.baseName()] = {};
+ for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) {
+ auto val = objectIt.value();
+ m_types[objectIt.key()] = val;
- // add QML builtins
- m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest
+ const auto exports = val->exports();
+ for (const auto &valExport : exports)
+ m_exportedName2Scope.insert(valExport.type(), val);
- LanguageUtils::FakeMetaObject *meta = new LanguageUtils::FakeMetaObject{};
- meta->addProperty(LanguageUtils::FakeMetaProperty {"enabled", "bool", false, false, false, 0});
- meta->addProperty(LanguageUtils::FakeMetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0});
- meta->addProperty(LanguageUtils::FakeMetaProperty {"target", "QObject", false, false, false, 0});
- m_exportedName2MetaObject["Connections"] = LanguageUtils::FakeMetaObject::ConstPtr { meta };
+ const auto enums = val->enums();
+ for (const auto &valEnum : enums)
+ m_currentScope->addEnum(valEnum);
+ }
+ // add "self" (as we only ever check the first part of a qualified identifier, we get away with
+ // using an empty ScopeTree
+ m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {});
- importDirectory(".", QString());
+ importFileOrDirectory(".", QString());
return true;
}
@@ -518,7 +512,8 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *)
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *catchStatement)
{
enterEnvironment(ScopeType::JSLexicalScope, "catch");
- m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), QQmlJS::AST::VariableScope::Let);
+ m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(),
+ QQmlJS::AST::VariableScope::Let);
return true;
}
@@ -529,8 +524,13 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *)
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::WithStatement *withStatement)
{
- m_colorOut.write(QString::asprintf("Warning: "), Warning);
- m_colorOut.write(QString::asprintf("%d:%d: with statements are strongly discouraged in QML and might cause false positives when analysing unqalified identifiers\n", withStatement->firstSourceLocation().startLine, withStatement->firstSourceLocation().startColumn), Normal);
+ m_colorOut.write(QString::fromLatin1("Warning: "), Warning);
+ m_colorOut.write(QString::fromLatin1(
+ "%1:%2: with statements are strongly discouraged in QML "
+ "and might cause false positives when analysing unqalified identifiers\n")
+ .arg(withStatement->firstSourceLocation().startLine)
+ .arg(withStatement->firstSourceLocation().startColumn),
+ Normal);
enterEnvironment(ScopeType::JSLexicalScope, "with");
return true;
}
@@ -540,12 +540,12 @@ void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *)
leaveEnvironment();
}
-static QString signalName(const QStringRef &handlerName)
+static QString signalName(QStringView handlerName)
{
- if (handlerName.startsWith("on") && handlerName.size() > 2) {
+ if (handlerName.startsWith(u"on") && handlerName.size() > 2) {
QString signal = handlerName.mid(2).toString();
for (int i = 0; i < signal.length(); ++i) {
- QCharRef ch = signal[i];
+ QChar &ch = signal[i];
if (ch.isLower())
return QString();
if (ch.isUpper()) {
@@ -563,13 +563,12 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
auto name = uisb->qualifiedId->name;
if (name == QLatin1String("id")) {
// found id
- auto expstat = static_cast<ExpressionStatement *>(uisb->statement);
- auto identexp = static_cast<IdentifierExpression *>(expstat->expression);
+ auto expstat = cast<ExpressionStatement *>(uisb->statement);
+ auto identexp = cast<IdentifierExpression *>(expstat->expression);
QString elementName = m_currentScope->name();
- m_qmlid2meta.insert(identexp->name.toString(), m_exportedName2MetaObject[elementName]);
- if (m_currentScope->isVisualRootScope()) {
+ m_qmlid2scope.insert(identexp->name.toString(), m_currentScope);
+ if (m_currentScope->isVisualRootScope())
m_rootId = identexp->name.toString();
- }
} else {
const QString signal = signalName(name);
if (signal.isEmpty())
@@ -582,7 +581,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
const auto statement = uisb->statement;
if (statement->kind == Node::Kind::Kind_ExpressionStatement) {
- if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
+ if (cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
// functions are already handled
// they do not get names inserted according to the signal, but access their formal
// parameters
@@ -607,37 +606,44 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm)
{
// property bool inactive: !active
// extract name inactive
- m_currentScope->insertPropertyIdentifier(uipm->name.toString());
+ MetaProperty property(
+ uipm->name.toString(),
+ // TODO: signals, complex types etc.
+ uipm->memberType ? uipm->memberType->name.toString() : QString(),
+ uipm->typeModifier == QLatin1String("list"),
+ !uipm->isReadonlyMember,
+ false,
+ uipm->memberType ? (uipm->memberType->name == QLatin1String("alias")) : false,
+ 0);
+ property.setType(m_exportedName2Scope.value(property.typeName()).get());
+ m_currentScope->insertPropertyIdentifier(property);
return true;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
{
auto name = idexp->name;
- if (!m_exportedName2MetaObject.contains(name.toString())) {
- m_currentScope->addIdToAccssedIfNotInParentScopes(
- { name.toString(), idexp->firstSourceLocation() }, m_unknownImports);
- }
+ m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation());
+ m_fieldMemberBase = idexp;
return true;
}
-FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs,
- const QString &code, const QString &fileName,
- bool silent)
+FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QString code,
+ QString fileName, bool silent)
: m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }),
m_currentScope(m_rootScope.get()),
- m_qmltypeDirs(qmltypeDirs),
- m_code(code),
+ m_qmltypeDirs(std::move(qmltypeDirs)),
+ m_code(std::move(code)),
m_rootId(QLatin1String("<id>")),
- m_filePath(fileName),
+ m_filePath(std::move(fileName)),
m_colorOut(silent)
{
// setup color output
- m_colorOut.insertColorMapping(Error, ColorOutput::RedForeground);
- m_colorOut.insertColorMapping(Warning, ColorOutput::PurpleForeground);
- m_colorOut.insertColorMapping(Info, ColorOutput::BlueForeground);
- m_colorOut.insertColorMapping(Normal, ColorOutput::DefaultColor);
- m_colorOut.insertColorMapping(Hint, ColorOutput::GreenForeground);
+ m_colorOut.insertMapping(Error, ColorOutput::RedForeground);
+ m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground);
+ m_colorOut.insertMapping(Info, ColorOutput::BlueForeground);
+ m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor);
+ m_colorOut.insertMapping(Hint, ColorOutput::GreenForeground);
QLatin1String jsGlobVars[] = {
/* Not listed on the MDN page; browser and QML extensions: */
// console/debug api
@@ -645,32 +651,36 @@ FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDir
// garbage collector
QLatin1String("gc"),
// i18n
- QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
+ QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"),
+ QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
// XMLHttpRequest
QLatin1String("XMLHttpRequest")
};
- for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr; ++globalName) {
- m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), QQmlJS::AST::VariableScope::Const);
+ for (const char **globalName = QV4::Compiler::Codegen::s_globalNames;
+ *globalName != nullptr;
+ ++globalName) {
+ m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName),
+ QQmlJS::AST::VariableScope::Const);
}
for (const auto& jsGlobVar: jsGlobVars)
m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const);
}
-FindUnqualifiedIDVisitor::~FindUnqualifiedIDVisitor() = default;
-
bool FindUnqualifiedIDVisitor::check()
{
if (m_visitFailed)
return false;
// now that all ids are known, revisit any Connections whose target were perviously unknown
- for (auto const& outstandingConnection: m_outstandingConnections) {
- auto metaObject = m_qmlid2meta[outstandingConnection.targetName];
- outstandingConnection.scope->addMethodsFromMetaObject(metaObject);
+ for (auto const &outstandingConnection: m_outstandingConnections) {
+ auto targetScope = m_qmlid2scope[outstandingConnection.targetName];
+ if (outstandingConnection.scope)
+ outstandingConnection.scope->addMethods(targetScope->methods());
QScopedValueRollback<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope);
outstandingConnection.uiod->initializer->accept(this);
}
- return m_rootScope->recheckIdentifiers(m_code, m_qmlid2meta, m_rootScope.get(), m_rootId, m_colorOut);
+ return m_rootScope->recheckIdentifiers(m_code, m_qmlid2scope, m_exportedName2Scope,
+ m_rootScope.get(), m_rootId, m_colorOut);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
@@ -686,18 +696,16 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr)
{
using namespace QQmlJS::AST;
- if (!fexpr->name.isEmpty()) {
- auto name = fexpr->name.toString();
- if (m_currentScope->scopeType() == ScopeType::QMLScope) {
- m_currentScope->insertQMLIdentifier(name);
- } else {
+ auto name = fexpr->name.toString();
+ if (!name.isEmpty()) {
+ if (m_currentScope->scopeType() == ScopeType::QMLScope)
+ m_currentScope->addMethod(MetaMethod(name, QLatin1String("void")));
+ else
m_currentScope->insertJSIdentifier(name, VariableScope::Const);
- }
+ enterEnvironment(ScopeType::JSFunctionScope, name);
+ } else {
+ enterEnvironment(ScopeType::JSFunctionScope, QLatin1String("<anon>"));
}
- QString name = fexpr->name.toString();
- if (name.isEmpty())
- name = "<anon>";
- enterEnvironment(ScopeType::JSFunctionScope, name);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
@@ -735,15 +743,17 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import)
// construct path
QString prefix = QLatin1String("");
if (import->asToken.isValid()) {
- prefix += import->importId + QLatin1Char('.');
+ prefix += import->importId;
}
auto dirname = import->fileName.toString();
if (!dirname.isEmpty())
- importDirectory(dirname, prefix);
+ importFileOrDirectory(dirname, prefix);
QString path {};
if (!import->importId.isEmpty()) {
- m_qmlid2meta.insert(import->importId.toString(), {}); // TODO: do not put imported ids into the same space as qml IDs
+ // TODO: do not put imported ids into the same space as qml IDs
+ const QString importId = import->importId.toString();
+ m_qmlid2scope.insert(importId, m_exportedName2Scope.value(importId).get());
}
if (import->version) {
auto uri = import->importUri;
@@ -761,14 +771,17 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import)
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
{
- m_currentScope->insertQMLIdentifier(uied->name.toString());
+ MetaEnum qmlEnum(uied->name.toString());
+ for (const auto *member = uied->members; member; member = member->next)
+ qmlEnum.addKey(member->member.toString());
+ m_currentScope->addEnum(qmlEnum);
return true;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
{
// property QtObject __styleData: QtObject {...}
- m_currentScope->insertPropertyIdentifier(uiob->qualifiedId->name.toString());
+
QString name {};
auto id = uiob->qualifiedTypeNameId;
QStringRef prefix = uiob->qualifiedTypeNameId->name;
@@ -777,20 +790,34 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
id = id->next;
}
name.chop(1);
+
+ MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true,
+ name == QLatin1String("alias"), 0);
+ prop.setType(m_exportedName2Scope.value(uiob->qualifiedTypeNameId->name.toString()).get());
+ m_currentScope->addProperty(prop);
+
enterEnvironment(ScopeType::QMLScope, name);
- if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component and QtObject, but they also have no interesting properties
- return true;
importExportedNames(prefix, name);
return true;
}
-void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
{
+ const auto childScope = m_currentScope;
leaveEnvironment();
+ MetaProperty property(uiob->qualifiedId->name.toString(),
+ uiob->qualifiedTypeNameId->name.toString(),
+ false, true, true,
+ uiob->qualifiedTypeNameId->name == QLatin1String("alias"),
+ 0);
+ property.setType(childScope);
+ m_currentScope->addProperty(property);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
+ using namespace QQmlJS::AST;
+
QString name {};
auto id = uiod->qualifiedTypeNameId;
QStringRef prefix = uiod->qualifiedTypeNameId->name;
@@ -802,8 +829,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
enterEnvironment(ScopeType::QMLScope, name);
if (name.isLower())
return false; // Ignore grouped properties for now
- if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component
- return true;
+
importExportedNames(prefix, name);
if (name.endsWith("Connections")) {
QString target;
@@ -825,25 +851,26 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
}
member = member->next;
}
- LanguageUtils::FakeMetaObject::ConstPtr metaObject {};
+ const ScopeTree *targetScope;
if (target.isEmpty()) {
// no target set, connection comes from parentF
ScopeTree* scope = m_currentScope;
do {
scope = scope->parentScope(); // TODO: rename method
} while (scope->scopeType() != ScopeType::QMLScope);
- auto metaObject = m_exportedName2MetaObject[scope->name()];
+ targetScope = m_exportedName2Scope.value(scope->name()).get();
} else {
// there was a target, check if we already can find it
- auto metaObjectIt = m_qmlid2meta.find(target);
- if (metaObjectIt != m_qmlid2meta.end()) {
- metaObject = *metaObjectIt;
+ auto scopeIt = m_qmlid2scope.find(target);
+ if (scopeIt != m_qmlid2scope.end()) {
+ targetScope = *scopeIt;
} else {
m_outstandingConnections.push_back({target, m_currentScope, uiod});
return false; // visit children later once target is known
}
}
- m_currentScope->addMethodsFromMetaObject(metaObject);
+ if (targetScope)
+ m_currentScope->addMethods(targetScope->methods());
}
return true;
}
@@ -862,5 +889,49 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::PatternElement *element)
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
{
+ auto childScope = m_currentScope;
leaveEnvironment();
+ childScope->updateParentProperty(m_currentScope);
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *)
+{
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
+{
+ using namespace QQmlJS::AST;
+ ExpressionNode *base = fieldMember->base;
+ while (auto *nested = cast<NestedExpression *>(base))
+ base = nested->expression;
+
+ if (m_fieldMemberBase == base) {
+ QString type;
+ if (auto *binary = cast<BinaryExpression *>(base)) {
+ if (binary->op == QSOperator::As) {
+ if (auto *right = cast<IdentifierExpression *>(binary->right))
+ type = right->name.toString();
+ }
+ }
+ m_currentScope->accessMember(fieldMember->name.toString(),
+ type,
+ fieldMember->identifierToken);
+ m_fieldMemberBase = fieldMember;
+ } else {
+ m_fieldMemberBase = nullptr;
+ }
+}
+
+bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::BinaryExpression *)
+{
+ return true;
+}
+
+void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp)
+{
+ if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left)
+ m_fieldMemberBase = binExp;
+ else
+ m_fieldMemberBase = nullptr;
}