diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2019-11-08 18:43:31 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-01-10 21:19:07 +0100 |
commit | 0989e5a2b7036674be8d43fac35a686fd04896c5 (patch) | |
tree | 262b9fabba959d7654169ab7739e064245727870 /tools | |
parent | 1cd494fbfb3eaf21717697c3c7df39b214b48ee3 (diff) |
qmllint: parse JS files for methods
Change-Id: I3888231ac82f9babd51e6332af3c5457bf3c9141
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmllint/findunqualified.cpp | 328 | ||||
-rw-r--r-- | tools/qmllint/findunqualified.h | 8 |
2 files changed, 196 insertions, 140 deletions
diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index f90b13ed29..9cf26be4ad 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -40,6 +40,11 @@ #include <QtCore/qdiriterator.h> #include <QtCore/qscopedvaluerollback.h> +static const QString prefixedName(const QString &prefix, const QString &name) +{ + return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); +} + static QQmlDirParser createQmldirParserForFile(const QString &filename) { QFile f(filename); @@ -67,6 +72,150 @@ 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; + } +} + +void FindUnqualifiedIDVisitor::parseMembers(QQmlJS::AST::UiObjectMemberList *member, + ScopeTree *scope) +{ + using namespace QQmlJS::AST; + + // member should be the sole element + Q_ASSERT(!member->next); + Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition); + auto definition = cast<UiObjectDefinition *>(member->member); + auto qualifiedId = definition->qualifiedTypeNameId; + while (qualifiedId && qualifiedId->next) { + qualifiedId = qualifiedId->next; + } + scope->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 = cast<UiPublicMember *>(initMembers->member); + switch (publicMember->type) { + case UiPublicMember::Signal: { + UiParameterList *param = publicMember->parameters; + MetaMethod method; + method.setMethodType(MetaMethod::Signal); + method.setMethodName(publicMember->name.toString()); + while (param) { + method.addParameter(param->name.toString(), param->type->name.toString()); + param = param->next; + } + scope->addMethod(method); + break; + } + case UiPublicMember::Property: { + MetaProperty prop { + publicMember->name.toString(), + publicMember->typeModifier.toString(), + false, + false, + false, + 0 + }; + scope->addProperty(prop); + break; + } + } + break; + } + case UiObjectMember::Kind_UiScriptBinding: { + // does not create anything new, ignore + break; + } + case UiObjectMember::Kind_UiSourceElement: { + auto sourceElement = cast<UiSourceElement *>(initMembers->member); + if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { + MetaMethod method; + method.setMethodName(fexpr->name.toString()); + method.setMethodType(MetaMethod::Method); + FormalParameterList *parameters = fexpr->formals; + while (parameters) { + method.addParameter(parameters->element->bindingIdentifier.toString(), ""); + parameters = parameters->next; + } + scope->addMethod(method); + } else if (ClassExpression *clexpr = + sourceElement->sourceElement->asClassDefinition()) { + const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; + scope->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; + } +} + +void FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope) +{ + using namespace QQmlJS::AST; + 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(), ""); + scope->addMethod(method); + } + } +} + enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; QStringList completeImportPaths(const QString &uri, const QString &basePath, int vmaj, int vmin) @@ -168,7 +317,7 @@ FindUnqualifiedIDVisitor::Import FindUnqualifiedIDVisitor::readQmldir(const QStr auto mo = qmlComponents.find(it.key()); if (mo == qmlComponents.end()) - mo = qmlComponents.insert(it.key(), localQmlFile2ScopeTree(filePath)); + mo = qmlComponents.insert(it.key(), localFile2ScopeTree(filePath)); (*mo)->addExport( it.key(), reader.typeNamespace(), @@ -203,11 +352,11 @@ void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUn // add objects for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { const auto &val = it.value(); - m_exportedName2Scope.insert(prefix + val->className(), val); + m_exportedName2Scope.insert(prefixedName(prefix, val->className()), val); const auto exports = val->exports(); for (const auto &valExport : exports) - m_exportedName2Scope.insert(prefix + valExport.type(), val); + m_exportedName2Scope.insert(prefixedName(prefix, valExport.type()), val); const auto enums = val->enums(); for (const auto &valEnum : enums) @@ -248,11 +397,12 @@ void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString } } -ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &filePath) +ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath) { using namespace QQmlJS::AST; auto scope = new ScopeTree(ScopeType::QMLScope); - QString baseName = QFileInfo { filePath }.baseName(); + const QFileInfo info { filePath }; + QString baseName = info.baseName(); scope->setClassName(baseName.endsWith(".ui") ? baseName.chopped(3) : baseName); QFile file(filePath); if (!file.open(QFile::ReadOnly)) @@ -264,146 +414,48 @@ ScopeTree *FindUnqualifiedIDVisitor::localQmlFile2ScopeTree(const QString &fileP QQmlJS::Engine engine; QQmlJS::Lexer lexer(&engine); - lexer.setCode(code, 1, true); + const QString lowerSuffix = info.suffix().toLower(); + const bool isJavaScript = (lowerSuffix == QLatin1String("js") || lowerSuffix == QLatin1String("mjs")); + const bool isESModule = lowerSuffix == QLatin1String("mjs"); + lexer.setCode(code, /*line = */ 1, /*qmlMode=*/ !isJavaScript); QQmlJS::Parser parser(&engine); - if (!parser.parse()) { + + const bool success = isJavaScript ? (isESModule ? parser.parseModule() + : parser.parseProgram()) + : parser.parse(); + if (!success) return scope; + + if (!isJavaScript) { + QQmlJS::AST::UiProgram *program = parser.ast(); + parseHeaders(program->headers); + parseMembers(program->members, scope); + } else { + // TODO: Anything special to do with ES modules here? + parseProgram(QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), scope); } - 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 = cast<UiObjectDefinition *>(member->member); - auto qualifiedId = definition->qualifiedTypeNameId; - while (qualifiedId && qualifiedId->next) { - qualifiedId = qualifiedId->next; - } - scope->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 = cast<UiPublicMember *>(initMembers->member); - switch (publicMember->type) { - case UiPublicMember::Signal: { - UiParameterList *param = publicMember->parameters; - MetaMethod method; - method.setMethodType(MetaMethod::Signal); - method.setMethodName(publicMember->name.toString()); - while (param) { - method.addParameter(param->name.toString(), param->type->name.toString()); - param = param->next; - } - scope->addMethod(method); - break; - } - case UiPublicMember::Property: { - const MetaProperty property { - publicMember->name.toString(), - publicMember->typeModifier.toString(), - false, - false, - false, - 0 - }; - scope->addProperty(property); - break; - } - } - break; - } - case UiObjectMember::Kind_UiScriptBinding: { - // does not create anything new, ignore - break; - } - case UiObjectMember::Kind_UiSourceElement: { - auto sourceElement = cast<UiSourceElement *>(initMembers->member); - if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { - MetaMethod method; - method.setMethodName(fexpr->name.toString()); - method.setMethodType(MetaMethod::Method); - FormalParameterList *parameters = fexpr->formals; - while (parameters) { - method.addParameter(parameters->element->bindingIdentifier.toString(), ""); - parameters = parameters->next; - } - scope->addMethod(method); - } else if (ClassExpression *clexpr = - sourceElement->sourceElement->asClassDefinition()) { - const MetaProperty prop { clexpr->name.toString(), "", false, false, false, 1 }; - scope->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; - } + return scope; } -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()) { - ScopeTree::ConstPtr scope(localQmlFile2ScopeTree(it.next())); + ScopeTree::ConstPtr scope(localFile2ScopeTree(it.next())); if (!scope->className().isEmpty()) - m_exportedName2Scope.insert(prefix + scope->className(), scope); + m_exportedName2Scope.insert(prefixedName(prefix, scope->className()), scope); } } @@ -471,7 +523,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) // using an empty ScopeTree m_exportedName2Scope.insert(QFileInfo { m_filePath }.baseName(), {}); - importDirectory(".", QString()); + importFileOrDirectory(".", QString()); return true; } @@ -778,11 +830,11 @@ 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()) { diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 483a6da039..a70bf8032f 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -95,11 +95,15 @@ private: Import readQmldir(const QString &dirname); void processImport(const QString &prefix, const Import &import); - ScopeTree *localQmlFile2ScopeTree(const QString &filePath); + ScopeTree *localFile2ScopeTree(const QString &filePath); - void importDirectory(const QString &directory, const QString &prefix); + void importFileOrDirectory(const QString &directory, const QString &prefix); void importExportedNames(const QStringRef &prefix, QString name); + void parseHeaders(QQmlJS::AST::UiHeaderItemList *headers); + void parseMembers(QQmlJS::AST::UiObjectMemberList *members, ScopeTree *scope); + void parseProgram(QQmlJS::AST::Program *program, ScopeTree *scope); + void throwRecursionDepthError() override; // start block/scope handling |