diff options
author | Maximilian Goldstein <max.goldstein@qt.io> | 2020-04-24 15:10:53 +0200 |
---|---|---|
committer | Maximilian Goldstein <max.goldstein@qt.io> | 2020-04-30 18:56:19 +0200 |
commit | ac5c765f29106837da717ca7c2e1f266f39d5843 (patch) | |
tree | d27d13f5349ac7b217acd14a61ea1eab54f20d89 /tools/qmllint/findunqualified.cpp | |
parent | 50cd8cf97aa89a48a9fbaaeb2515e529e66c7d43 (diff) |
qmllint: Add flags for toggling warnings
Enables all warnings by default and makes it possible to toggle
individual ones using command line flags.
Change-Id: Ie55f32f646fd9422313977969f9f00b59ee9ad99
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools/qmllint/findunqualified.cpp')
-rw-r--r-- | tools/qmllint/findunqualified.cpp | 989 |
1 files changed, 0 insertions, 989 deletions
diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp deleted file mode 100644 index abd9c07c31..0000000000 --- a/tools/qmllint/findunqualified.cpp +++ /dev/null @@ -1,989 +0,0 @@ -/**************************************************************************** -** -** 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 "importedmembersvisitor.h" -#include "scopetree.h" -#include "typedescriptionreader.h" -#include "checkidentifiers.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 <QtCore/qfile.h> -#include <QtCore/qdiriterator.h> -#include <QtCore/qscopedvaluerollback.h> - -static const QString prefixedName(const QString &prefix, const QString &name) -{ - Q_ASSERT(!prefix.endsWith('.')); - return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); -} - -static QQmlDirParser createQmldirParserForFile(const QString &filename) -{ - QFile f(filename); - f.open(QFile::ReadOnly); - QQmlDirParser parser; - parser.parse(f.readAll()); - return parser; -} - -static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename) -{ - QFile f(filename); - f.open(QFile::ReadOnly); - TypeDescriptionReader reader { filename, f.readAll() }; - return reader; -} - -void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name) -{ - m_currentScope = ScopeTree::create(type, name, m_currentScope); -} - -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); - importHelper(path, - import->asToken.isValid() ? import->importId.toString() : QString(), - import->version->version); - } - } - header = header->next; - } -} - -ScopeTree::Ptr FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, - const QString &name) -{ - using namespace QQmlJS::AST; - ScopeTree::Ptr result = ScopeTree::create(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 QString &basePath, QTypeRevision version) -{ - static const QLatin1Char Slash('/'); - static const QLatin1Char Backslash('\\'); - - const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), Qt::SkipEmptyParts); - - QStringList qmlDirPathsPaths; - // fully & partially versioned parts + 1 unversioned for each base path - qmlDirPathsPaths.reserve(2 * parts.count() + 1); - - auto versionString = [](QTypeRevision version, ImportVersion mode) - { - if (mode == FullyVersioned) { - // extension with fully encoded version number (eg. MyModule.3.2) - return QString::fromLatin1(".%1.%2").arg(version.majorVersion()) - .arg(version.minorVersion()); - } - if (mode == PartiallyVersioned) { - // extension with encoded version major (eg. MyModule.3) - return QString::fromLatin1(".%1").arg(version.majorVersion()); - } - // 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; - }; - - const ImportVersion initial = (version.hasMinorVersion()) - ? FullyVersioned - : (version.hasMajorVersion() ? PartiallyVersioned : Unversioned); - for (int mode = initial; mode <= BasePath; ++mode) { - const QString ver = versionString(version, ImportVersion(mode)); - - QString dir = basePath; - if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) - dir += Slash; - - if (mode == BasePath) { - qmlDirPathsPaths += dir; - } else { - // append to the end - qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; - } - - if (mode < 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; -} - -static const QLatin1String SlashQmldir = QLatin1String("/qmldir"); -static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes"); - -void FindUnqualifiedIDVisitor::readQmltypes(const QString &filename, - FindUnqualifiedIDVisitor::Import &result) -{ - auto reader = createQmltypesReaderForFile(filename); - auto succ = reader(&result.objects, &result.dependencies); - if (!succ) - m_colorOut.writeUncolored(reader.errorMessage()); -} - -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); - - QHash<QString, ScopeTree::Ptr> 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->version)); - } - 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); - if (split.length() > 1) { - const auto version = split.at(1).split('.'); - importHelper(id, QString(), QTypeRevision::fromVersion( - version.at(0).toInt(), - version.length() > 1 ? version.at(1).toInt() : -1)); - } else { - importHelper(id, QString(), QTypeRevision()); - } - - - } - - // add objects - 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, - QTypeRevision version) -{ - 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, version); - - 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; - } - - if (!m_qmltypeFiles.isEmpty()) - continue; - - Import result; - - QDirIterator it { qmltypesPath, QStringList() << QLatin1String("*.qmltypes"), QDir::Files }; - - while (it.hasNext()) - readQmltypes(it.next(), result); - - processImport(prefix, result); - } - } - - if (!m_qmltypeFiles.isEmpty()) - { - Import result; - - for (const auto &qmltypeFile : m_qmltypeFiles) - readQmltypes(qmltypeFile, result); - - processImport("", result); - } -} - -ScopeTree::Ptr FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath) -{ - using namespace QQmlJS::AST; - 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 ScopeTree::create(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, - scopeName); - } - - QString code = file.readAll(); - file.close(); - - lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); - QQmlJS::Parser parser(&engine); - - const bool success = isJavaScript ? (isESModule ? parser.parseModule() - : parser.parseProgram()) - : parser.parse(); - if (!success) { - return ScopeTree::create(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, - scopeName); - } - - if (!isJavaScript) { - QQmlJS::AST::UiProgram *program = parser.ast(); - parseHeaders(program->headers); - ImportedMembersVisitor membersVisitor(&m_colorOut); - program->members->accept(&membersVisitor); - return membersVisitor.result(scopeName); - } - - // TODO: Anything special to do with ES modules here? - return parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scopeName); -} - -void FindUnqualifiedIDVisitor::importFileOrDirectory(const QString &fileOrDirectory, - const QString &prefix) -{ - 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 { name, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; - while (it.hasNext()) { - ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next())); - if (!scope->className().isEmpty()) - m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), scope); - } -} - -void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QString name) -{ - QList<ScopeTree::ConstPtr> scopes; - for (;;) { - ScopeTree::ConstPtr scope = m_exportedName2Scope.value(m_exportedName2Scope.contains(name) - ? name - : prefix + QLatin1Char('.') + name); - if (scope) { - if (scopes.contains(scope)) { - QString inheritenceCycle = name; - for (const auto seen: qAsConst(scopes)) { - inheritenceCycle.append(QLatin1String(" -> ")); - inheritenceCycle.append(seen->superclassName()); - } - - m_colorOut.write(QLatin1String("Warning: "), Warning); - m_colorOut.write(QString::fromLatin1("%1 is part of an inheritance cycle: %2\n") - .arg(name) - .arg(inheritenceCycle)); - m_unknownImports.insert(name); - m_visitFailed = true; - break; - } - scopes.append(scope); - const auto properties = scope->properties(); - for (auto property : properties) { - property.setType(m_exportedName2Scope.value(property.typeName())); - m_currentScope->insertPropertyIdentifier(property); - } - - 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_unknownImports.insert(name); - m_visitFailed = true; - break; - } - } -} - -void FindUnqualifiedIDVisitor::throwRecursionDepthError() -{ - m_colorOut.write(QStringLiteral("Error"), Error); - m_colorOut.write(QStringLiteral("Maximum statement or expression depth exceeded"), Error); - m_visitFailed = true; -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) -{ - enterEnvironment(ScopeType::QMLScope, "program"); - QHash<QString, ScopeTree::ConstPtr> objects; - QStringList dependencies; - for (auto const &dir : m_qmltypeDirs) { - QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, - QDirIterator::Subdirectories }; - while (it.hasNext()) { - auto reader = createQmltypesReaderForFile(it.next()); - auto succ = reader(&objects, &dependencies); - if (!succ) - m_colorOut.writeUncolored(reader.errorMessage()); - } - } - - if (!m_qmltypeFiles.isEmpty()) - { - for (const auto &qmltypeFile : m_qmltypeFiles) { - auto reader = createQmltypesReaderForFile(qmltypeFile); - auto succ = reader(&objects, &dependencies); - if (!succ) - m_colorOut.writeUncolored(reader.errorMessage()); - } - } - - // add builtins - for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) { - auto val = objectIt.value(); - m_types[objectIt.key()] = val; - - const auto exports = val->exports(); - for (const auto &valExport : exports) - m_exportedName2Scope.insert(valExport.type(), val); - - 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(), {}); - - importFileOrDirectory(".", QString()); - 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 *catchStatement) -{ - enterEnvironment(ScopeType::JSLexicalScope, "catch"); - m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), - ScopeType::JSLexicalScope); - return true; -} - -void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *) -{ - leaveEnvironment(); -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::WithStatement *withStatement) -{ - 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; -} - -void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) -{ - leaveEnvironment(); -} - -static QString signalName(QStringView handlerName) -{ - if (handlerName.startsWith(u"on") && handlerName.size() > 2) { - QString signal = handlerName.mid(2).toString(); - for (int i = 0; i < signal.length(); ++i) { - QChar &ch = signal[i]; - if (ch.isLower()) - return QString(); - if (ch.isUpper()) { - ch = ch.toLower(); - return signal; - } - } - } - return QString(); -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) -{ - using namespace QQmlJS::AST; - auto name = uisb->qualifiedId->name; - if (name == QLatin1String("id")) { - // found id - auto expstat = cast<ExpressionStatement *>(uisb->statement); - auto identexp = cast<IdentifierExpression *>(expstat->expression); - QString elementName = m_currentScope->name(); - 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()) - return true; - - if (!m_currentScope->methods().contains(signal)) { - m_currentScope->addUnmatchedSignalHandler(name.toString(), uisb->firstSourceLocation()); - return true; - } - - const auto statement = uisb->statement; - if (statement->kind == Node::Kind::Kind_ExpressionStatement) { - 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 - return true; - } - } - - const auto methods = m_currentScope->methods(); - const auto methodsRange = methods.equal_range(signal); - for (auto method = methodsRange.first; method != methodsRange.second; ++method) { - if (method->methodType() != MetaMethod::Signal) - continue; - for (auto const ¶m : method->parameterNames()) { - const auto firstSourceLocation = statement->firstSourceLocation(); - bool hasMultilineStatementBody - = 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 - 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())); - m_currentScope->insertPropertyIdentifier(property); - return true; -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) -{ - auto name = idexp->name; - m_currentScope->addIdToAccessed(name.toString(), idexp->firstSourceLocation()); - m_fieldMemberBase = idexp; - return true; -} - -FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QStringList qmltypeFiles, QString code, - QString fileName, bool silent) - : m_rootScope(ScopeTree::create(ScopeType::JSFunctionScope, "global")), - m_qmltypeDirs(std::move(qmltypeDirs)), - m_qmltypeFiles(std::move(qmltypeFiles)), - m_code(std::move(code)), - m_rootId(QLatin1String("<id>")), - m_filePath(std::move(fileName)), - m_colorOut(silent) -{ - m_currentScope = m_rootScope; - - // setup color output - 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 - QLatin1String("console"), QLatin1String("print"), - // garbage collector - QLatin1String("gc"), - // i18n - 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), - ScopeType::JSLexicalScope); - } - for (const auto& jsGlobVar: jsGlobVars) - m_currentScope->insertJSIdentifier(jsGlobVar, ScopeType::JSLexicalScope); -} - -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 targetScope = m_qmlid2scope[outstandingConnection.targetName]; - if (outstandingConnection.scope) - outstandingConnection.scope->addMethods(targetScope->methods()); - QScopedValueRollback<ScopeTree::Ptr> rollback(m_currentScope, outstandingConnection.scope); - outstandingConnection.uiod->initializer->accept(this); - } - - CheckIdentifiers check(&m_colorOut, m_code, m_exportedName2Scope, m_filePath); - return check(m_qmlid2scope, m_rootScope, m_rootId); -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) -{ - while (vdl) { - m_currentScope->insertJSIdentifier( - vdl->declaration->bindingIdentifier.toString(), - (vdl->declaration->scope == QQmlJS::AST::VariableScope::Var) - ? ScopeType::JSFunctionScope - : ScopeType::JSLexicalScope); - vdl = vdl->next; - } - return true; -} - -void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr) -{ - using namespace QQmlJS::AST; - 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, ScopeType::JSLexicalScope); - enterEnvironment(ScopeType::JSFunctionScope, name); - } else { - enterEnvironment(ScopeType::JSFunctionScope, QLatin1String("<anon>")); - } -} - -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, ScopeType::JSLexicalScope); - return true; -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) -{ - // construct path - QString prefix = QLatin1String(""); - if (import->asToken.isValid()) { - prefix += import->importId; - } - auto dirname = import->fileName.toString(); - if (!dirname.isEmpty()) - importFileOrDirectory(dirname, prefix); - - QString path {}; - if (!import->importId.isEmpty()) { - // 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)); - } - 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->version); - } - return true; -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) -{ - 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 {...} - - QString name {}; - auto id = uiob->qualifiedTypeNameId; - QStringRef prefix = uiob->qualifiedTypeNameId->name; - while (id) { - name += id->name.toString() + QLatin1Char('.'); - 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())); - m_currentScope->addProperty(prop); - - enterEnvironment(ScopeType::QMLScope, name); - importExportedNames(prefix, name); - return true; -} - -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; - 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 - - 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; - } - ScopeTree::ConstPtr targetScope; - if (target.isEmpty()) { - // no target set, connection comes from parentF - ScopeTree::Ptr scope = m_currentScope; - do { - scope = scope->parentScope(); // TODO: rename method - } while (scope->scopeType() != ScopeType::QMLScope); - targetScope = m_exportedName2Scope.value(scope->name()); - } else { - // there was a target, check if we already can find it - 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 - } - } - if (targetScope) - m_currentScope->addMethods(targetScope->methods()); - } - return true; -} - -bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::PatternElement *element) -{ - if (element->isVariableDeclaration()) { - QQmlJS::AST::BoundNames names; - element->boundNames(&names); - for (const auto &name : names) { - m_currentScope->insertJSIdentifier( - name.id, (element->scope == QQmlJS::AST::VariableScope::Var) - ? ScopeType::JSFunctionScope - : ScopeType::JSLexicalScope); - } - } - - return true; -} - -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; -} |