aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2019-11-08 18:43:31 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-01-10 21:19:07 +0100
commit0989e5a2b7036674be8d43fac35a686fd04896c5 (patch)
tree262b9fabba959d7654169ab7739e064245727870 /tools
parent1cd494fbfb3eaf21717697c3c7df39b214b48ee3 (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.cpp328
-rw-r--r--tools/qmllint/findunqualified.h8
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