qtdeclarative/tools/qmllint/findunqualified.cpp

777 lines
29 KiB
C++
Raw Normal View History

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "findunqualified.h"
#include "scopetree.h"
#include "qmljstypedescriptionreader.h"
#include <QFile>
#include <QDirIterator>
#include <QScopedValueRollback>
#include <private/qqmljsast_p.h>
#include <private/qqmljslexer_p.h>
#include <private/qqmljsparser_p.h>
QDebug &operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc);
static QQmlJS::TypeDescriptionReader createReaderForFile(QString const &filename)
{
QFile f(filename);
f.open(QFile::ReadOnly);
QQmlJS::TypeDescriptionReader reader { filename, f.readAll() };
return reader;
}
void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name)
{
m_currentScope = m_currentScope->createNewChildScope(type, name);
}
void FindUnqualifiedIDVisitor::leaveEnvironment()
{
m_currentScope = m_currentScope->parentScope();
}
enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned };
QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
{
static const QLatin1Char Slash('/');
static const QLatin1Char Backslash('\\');
static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes");
const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), QString::SkipEmptyParts);
QStringList qmlDirPathsPaths;
// fully & partially versioned parts + 1 unversioned for each base path
qmlDirPathsPaths.reserve(basePaths.count() * (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) {
// extension with encoded version major (eg. MyModule.3)
return QString::asprintf(".%d", vmaj);
} // else extension without version number (eg. MyModule)
return QString();
};
auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep)
{
QString str;
for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
if (it != refs.cbegin())
str += sep;
str += *it;
}
return str;
};
for (int version = FullyVersioned; version <= Unversioned; ++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;
// append to the end
qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + SlashPluginsDotQmltypes;
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) + SlashPluginsDotQmltypes;
}
}
}
}
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 = completeQmltypesPaths(id, m_qmltypeDirs, major, minor);
QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
QList<QQmlJS::ModuleApiInfo> moduleApis;
QStringList dependencies;
for (auto const &qmltypesPath : qmltypesPaths) {
if (QFile::exists(qmltypesPath)) {
auto reader = createReaderForFile(qmltypesPath);
auto succ = reader(&objects, &moduleApis, &dependencies);
if (!succ) {
qDebug() << reader.errorMessage();
}
break;
}
}
for (auto const &dependency : qAsConst(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);
}
// 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());
}
}
}
LanguageUtils::FakeMetaObject *
FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath)
{
using namespace QQmlJS::AST;
auto fake = new LanguageUtils::FakeMetaObject;
fake->setClassName(QFileInfo { filePath }.baseName());
QFile file(filePath);
if (!file.open(QFile::ReadOnly)) {
return fake;
}
QString code = file.readAll();
file.close();
QQmlJS::Engine engine;
QQmlJS::Lexer lexer(&engine);
lexer.setCode(code, 1, true);
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;
}
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: {
auto enumDeclaration = static_cast<UiEnumDeclaration *>(initMembers->member);
qDebug() << "enumdecl" << enumDeclaration->name;
break;
}
case UiObjectMember::Kind_UiObjectBinding: {
// nothing to do
break;
}
case UiObjectMember::Kind_UiObjectDefinition: {
// creates nothing accessible
/*auto objectDefinition = static_cast<UiObjectDefinition*>(initMembers->member);
qDebug() << "objdef" << objectDefinition->qualifiedTypeNameId->name;*/
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.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 {
qDebug() << "unsupportedd sourceElement at" << sourceElement->firstSourceLocation()
<< sourceElement->sourceElement->kind;
}
break;
}
default: {
qDebug() << "unsupported element of kind" << initMembers->member->kind;
}
}
initMembers = initMembers->next;
}
return fake;
}
void FindUnqualifiedIDVisitor::importExportedNames(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());
}
m_currentScope->addMethodsFromMetaObject(metaObject);
name = metaObject->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_unknownImports.insert(name);
break;
}
}
}
void FindUnqualifiedIDVisitor::throwRecursionDepthError()
{
return;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *)
{
enterEnvironment(ScopeType::QMLScope, "program");
QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects;
QList<QQmlJS::ModuleApiInfo> moduleApis;
QStringList dependencies;
for (auto const &dir : m_qmltypeDirs) {
QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter,
QDirIterator::Subdirectories };
while (it.hasNext()) {
auto reader = createReaderForFile(it.next());
auto succ = reader(&objects, &moduleApis, &dependencies);
if (!succ) {
qDebug() << reader.errorMessage();
}
}
}
// 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()] = {};
// add QML builtins
m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest
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 };
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiProgram *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassExpression *ast)
{
enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassExpression *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassDeclaration *ast)
{
enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString());
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassDeclaration *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForStatement *)
{
enterEnvironment(ScopeType::JSLexicalScope, "forloop");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForStatement *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForEachStatement *)
{
enterEnvironment(ScopeType::JSLexicalScope, "foreachloop");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForEachStatement *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Block *)
{
enterEnvironment(ScopeType::JSLexicalScope, "block");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Block *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::CaseBlock *)
{
enterEnvironment(ScopeType::JSLexicalScope, "case");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *)
{
enterEnvironment(ScopeType::JSLexicalScope, "catch");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *)
{
leaveEnvironment();
}
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 analying unqalified identifiers\n", withStatement->firstSourceLocation().startLine, withStatement->firstSourceLocation().startColumn), Normal);
enterEnvironment(ScopeType::JSLexicalScope, "with");
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb)
{
using namespace QQmlJS::AST;
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);
QString elementName = m_currentScope->name();
m_qmlid2meta.insert(identexp->name.toString(), m_exportedName2MetaObject[elementName]);
if (m_currentScope->isVisualRootScope()) {
m_rootId = identexp->name.toString();
}
} else if (name.startsWith("on") && name.size() > 2 && name.at(2).isUpper()) {
auto statement = uisb->statement;
if (statement->kind == Node::Kind::Kind_ExpressionStatement) {
if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) {
// functions are already handled
// they do not get names inserted according to the signal, but access their formal
// parameters
return true;
}
}
QString signal = name.mid(2).toString();
signal[0] = signal[0].toLower();
if (!m_currentScope->methods().contains(signal)) {
qDebug() << "Info file does not contain signal" << signal;
} else {
auto method = m_currentScope->methods()[signal];
for (auto const &param : method.parameterNames()) {
auto firstSourceLocation = uisb->statement->firstSourceLocation();
bool hasMultilineStatementBody = uisb->statement->lastSourceLocation().startLine > firstSourceLocation.startLine;
m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, hasMultilineStatementBody);
}
}
return true;
}
return true;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm)
{
// property bool inactive: !active
// extract name inactive
m_currentScope->insertPropertyIdentifier(uipm->name.toString());
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);
}
return true;
}
FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs,
const QString &code, const QString &fileName)
: m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }),
m_currentScope(m_rootScope.get()),
m_qmltypeDirs(qmltypeDirs),
m_code(code),
m_rootId(QLatin1String("<id>")),
m_filePath(fileName)
{
// 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);
QLatin1String jsGlobVars[] = {
QLatin1String ("Array"), QLatin1String("Boolean"), QLatin1String("Date"), QLatin1String("Function"), QLatin1String("Math"), QLatin1String("Number"), QLatin1String("Object"), QLatin1String("RegExp"), QLatin1String("String"),
QLatin1String("Error"), QLatin1String("EvalError"), QLatin1String("RangeError"), QLatin1String("ReferenceError"), QLatin1String("SyntaxError"), QLatin1String("TypeError"), QLatin1String("URIError"),
QLatin1String("encodeURI"), QLatin1String("encodeURIComponent"), QLatin1String("decodeURI"), QLatin1String("decodeURIComponent"), QLatin1String("escape"), QLatin1String("unescape"),
QLatin1String("isFinite"), QLatin1String("isNanN"), QLatin1String("parseFloat"), QLatin1String("parseInt"),
QLatin1String("eval"), QLatin1String("console"), QLatin1String("print"), QLatin1String("gc"),
QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"),
QLatin1String("XMLHttpRequest"), QLatin1String("JSON"), QLatin1String("Promise"),
QLatin1String("undefined")
};
for (const auto& jsGlobVar: jsGlobVars)
m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const);
}
FindUnqualifiedIDVisitor::~FindUnqualifiedIDVisitor() = default;
bool FindUnqualifiedIDVisitor::check()
{
// 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);
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);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl)
{
while (vdl) {
m_currentScope->insertJSIdentifier(vdl->declaration->bindingIdentifier.toString(),
vdl->declaration->scope);
vdl = vdl->next;
}
return true;
}
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 {
m_currentScope->insertJSIdentifier(name, VariableScope::Const);
}
}
// qDebug() << fexpr->firstSourceLocation() << "function expression" << fexpr->name;
QString name = fexpr->name.toString();
if (name.isEmpty())
name = "<anon>";
enterEnvironment(ScopeType::JSFunctionScope, name);
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr)
{
visitFunctionExpressionHelper(fexpr);
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionExpression *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl)
{
visitFunctionExpressionHelper(fdecl);
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FormalParameterList *fpl)
{
for (auto const &boundName : fpl->boundNames()) {
m_currentScope->insertJSIdentifier(boundName.id, QQmlJS::AST::VariableScope::Const);
}
return true;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import)
{
// construct path
QString prefix = QLatin1String("");
if (import->asToken.isValid()) {
prefix += import->importId + QLatin1Char('.');
}
auto dirname = import->fileName.toString();
if (!dirname.isEmpty()) {
QFileInfo info { dirname };
if (info.isRelative()) {
dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname);
}
QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
while (it.hasNext()) {
LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next());
m_exportedName2MetaObject.insert(
fake->className(), QSharedPointer<const LanguageUtils::FakeMetaObject>(fake));
}
}
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
}
if (import->version) {
auto uri = import->importUri;
while (uri) {
path.append(uri->name);
path.append("/");
uri = uri->next;
}
path.chop(1);
importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion);
}
return true;
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
{
m_currentScope->insertQMLIdentifier(uied->name.toString());
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;
while (id) {
name += id->name.toString() + QLatin1Char('.');
id = id->next;
}
name.chop(1);
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 *)
{
leaveEnvironment();
}
bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod)
{
QString name {};
auto id = uiod->qualifiedTypeNameId;
QStringRef prefix = uiod->qualifiedTypeNameId->name;
while (id) {
name += id->name.toString() + QLatin1Char('.');
id = id->next;
}
name.chop(1);
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;
auto member = uiod->initializer->members;
while (member) {
if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) {
auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member);
if (asBinding->qualifiedId->name == QLatin1String("target")) {
if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) {
auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression;
if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) {
target = idexpr->name.toString();
} else {
// more complex expressions are not supported
}
}
break;
}
}
member = member->next;
}
LanguageUtils::FakeMetaObject::ConstPtr metaObject {};
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()];
} 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;
} else {
m_outstandingConnections.push_back({target, m_currentScope, uiod});
return false; // visit children later once target is known
}
}
m_currentScope->addMethodsFromMetaObject(metaObject);
}
return true;
}
void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
{
leaveEnvironment();
}
QDebug &operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << loc.startLine;
dbg.nospace() << ":";
dbg.nospace() << loc.startColumn;
return dbg.maybeSpace();
}