QmlJS: Improve handling of user defined enums

Improves handling of Qml based enums inside qml documents.

 * completion of enums
 * follow the enum
 * highlighting values inside the declaration
 * displaying the enum declaration inside the outline
 * minor static checks

Task-number: QTCREATORBUG-19226
Change-Id: Ia07fd9a8b7fa3106f2ea53198bfdcc50eecb7307
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Christian Stenger
2024-04-08 15:30:05 +02:00
parent ee8245d8aa
commit e1d90a7965
13 changed files with 184 additions and 4 deletions

View File

@@ -333,6 +333,16 @@ bool Bind::visit(UiInlineComponent *ast)
return true; return true;
} }
bool Bind::visit(UiEnumDeclaration *ast)
{
if (_currentObjectValue) {
UiEnumValue *value = new UiEnumValue(ast, &_valueOwner, _currentObjectValue->originId());
_qmlObjects.insert(ast, value);
_currentObjectValue->setMember(ast->name, value);
}
return true;
}
bool Bind::visit(AST::TemplateLiteral *ast) bool Bind::visit(AST::TemplateLiteral *ast)
{ {
Node::accept(ast->expression, this); Node::accept(ast->expression, this);

View File

@@ -57,6 +57,7 @@ protected:
bool visit(AST::UiScriptBinding *ast) override; bool visit(AST::UiScriptBinding *ast) override;
bool visit(AST::UiArrayBinding *ast) override; bool visit(AST::UiArrayBinding *ast) override;
bool visit(AST::UiInlineComponent *ast) override; bool visit(AST::UiInlineComponent *ast) override;
bool visit(AST::UiEnumDeclaration *ast) override;
// QML/JS // QML/JS
bool visit(AST::TemplateLiteral *ast) override; bool visit(AST::TemplateLiteral *ast) override;

View File

@@ -846,6 +846,30 @@ bool Check::visit(UiObjectInitializer *)
return true; return true;
} }
bool Check::visit(AST::UiEnumDeclaration *ast)
{
const Value *localLookup = _scopeChain.lookup(ast->name.toString());
Utils::FilePath fp;
int line, column;
if (localLookup->getSourceLocation(&fp, &line, &column)) {
// if it's not "us" we get shadowed by another enum declaration
if (ast->identifierToken.startLine != line || ast->identifierToken.startColumn != column)
addMessage(ErrDuplicateId, SourceLocation(0, 0, line, column));
}
return true;
}
bool Check::visit(AST::UiEnumMemberList *ast)
{
QStringList names;
for (auto it = ast; it; it = it->next) {
if (names.contains(it->member)) // duplicate enum value
addMessage(ErrInvalidEnumValue, it->memberToken); // better a different message?
names.append(it->member.toString());
}
return true;
}
bool Check::visit(AST::TemplateLiteral *ast) bool Check::visit(AST::TemplateLiteral *ast)
{ {
Node::accept(ast->expression, this); Node::accept(ast->expression, this);

View File

@@ -57,6 +57,8 @@ protected:
bool visit(AST::FunctionDeclaration *ast) override; bool visit(AST::FunctionDeclaration *ast) override;
bool visit(AST::FunctionExpression *ast) override; bool visit(AST::FunctionExpression *ast) override;
bool visit(AST::UiObjectInitializer *) override; bool visit(AST::UiObjectInitializer *) override;
bool visit(AST::UiEnumDeclaration *ast) override;
bool visit(AST::UiEnumMemberList *ast) override;
bool visit(AST::TemplateLiteral *ast) override; bool visit(AST::TemplateLiteral *ast) override;
bool visit(AST::BinaryExpression *ast) override; bool visit(AST::BinaryExpression *ast) override;

View File

@@ -120,3 +120,8 @@ QIcon Icons::functionDeclarationIcon()
{ {
return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::FuncPublic); return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::FuncPublic);
} }
QIcon Icons::enumMemberIcon()
{
return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Enum);
}

View File

@@ -28,6 +28,7 @@ public:
static QIcon scriptBindingIcon(); static QIcon scriptBindingIcon();
static QIcon publicMemberIcon(); static QIcon publicMemberIcon();
static QIcon functionDeclarationIcon(); static QIcon functionDeclarationIcon();
static QIcon enumMemberIcon();
private: private:
Icons(); Icons();

View File

@@ -635,6 +635,52 @@ const CppComponentValue *QmlEnumValue::owner() const
return m_owner; return m_owner;
} }
UiEnumValue::UiEnumValue(AST::UiEnumDeclaration *ast,
ValueOwner *valueOwner, const QString &originId)
: ObjectValue(valueOwner, originId)
, m_name(ast->name.toString())
{
setClassName("enum");
m_path = Utils::FilePath::fromUserInput(originId);
m_line = ast->identifierToken.startLine;
m_column = ast->identifierToken.startColumn;
for (auto it = ast->members; it; it = it->next) {
const QString name = it->member.toString();
setMember(name, valueOwner->intValue());
setPropertyInfo(name, PropertyInfo(PropertyInfo::Readable|PropertyInfo::ValueType));
m_keys.append(name);
m_values.append(it->value);
}
}
UiEnumValue::~UiEnumValue()
{
}
const UiEnumValue *UiEnumValue::asUiEnumValue() const
{
return this;
}
bool UiEnumValue::getSourceLocation(Utils::FilePath *path, int *line, int *column) const
{
*path = m_path;
*line = m_line;
*column = m_column;
return true;
}
QString UiEnumValue::name() const
{
return m_name;
}
QStringList UiEnumValue::keys() const
{
return m_keys;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// ValueVisitor // ValueVisitor
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -791,6 +837,11 @@ const QmlEnumValue *Value::asQmlEnumValue() const
return nullptr; return nullptr;
} }
const UiEnumValue *Value::asUiEnumValue() const
{
return nullptr;
}
const QmlPrototypeReference *Value::asQmlPrototypeReference() const const QmlPrototypeReference *Value::asQmlPrototypeReference() const
{ {
return nullptr; return nullptr;

View File

@@ -44,6 +44,7 @@ class NumberValue;
class ModuleApiInfo; class ModuleApiInfo;
class ObjectValue; class ObjectValue;
class QmlEnumValue; class QmlEnumValue;
class UiEnumValue;
class QmlPrototypeReference; class QmlPrototypeReference;
class RealValue; class RealValue;
class Reference; class Reference;
@@ -115,6 +116,7 @@ public:
virtual const CppComponentValue *asCppComponentValue() const; virtual const CppComponentValue *asCppComponentValue() const;
virtual const ASTObjectValue *asAstObjectValue() const; virtual const ASTObjectValue *asAstObjectValue() const;
virtual const QmlEnumValue *asQmlEnumValue() const; virtual const QmlEnumValue *asQmlEnumValue() const;
virtual const UiEnumValue *asUiEnumValue() const;
virtual const QmlPrototypeReference *asQmlPrototypeReference() const; virtual const QmlPrototypeReference *asQmlPrototypeReference() const;
virtual const ASTPropertyReference *asAstPropertyReference() const; virtual const ASTPropertyReference *asAstPropertyReference() const;
virtual const ASTVariableReference *asAstVariableReference() const; virtual const ASTVariableReference *asAstVariableReference() const;
@@ -246,6 +248,12 @@ template <> Q_INLINE_TEMPLATE const QmlEnumValue *value_cast(const Value *v)
else return nullptr; else return nullptr;
} }
template <> Q_INLINE_TEMPLATE const UiEnumValue *value_cast(const Value *v)
{
if (v) return v->asUiEnumValue();
else return nullptr;
}
template <> Q_INLINE_TEMPLATE const QmlPrototypeReference *value_cast(const Value *v) template <> Q_INLINE_TEMPLATE const QmlPrototypeReference *value_cast(const Value *v)
{ {
if (v) return v->asQmlPrototypeReference(); if (v) return v->asQmlPrototypeReference();
@@ -559,6 +567,27 @@ private:
int m_enumIndex; int m_enumIndex;
}; };
class QMLJS_EXPORT UiEnumValue : public ObjectValue
{
public:
UiEnumValue(AST::UiEnumDeclaration *uiEnum, ValueOwner *valueOwner,
const QString &originId);
~UiEnumValue();
const UiEnumValue *asUiEnumValue() const override;
bool getSourceLocation(Utils::FilePath *path, int *line, int *column) const override;
QString name() const;
QStringList keys() const;
private:
const QString m_name;
Utils::FilePath m_path;
int m_line;
int m_column;
QStringList m_keys;
QList<int> m_values;
};
// A ObjectValue based on a FakeMetaObject. // A ObjectValue based on a FakeMetaObject.
// May only have other CppComponentValue as ancestors. // May only have other CppComponentValue as ancestors.

View File

@@ -599,6 +599,17 @@ protected:
return true; return true;
} }
bool visit(UiEnumDeclaration *node) override
{
if (containsOffset(node->identifierToken)) {
_name = node->name.toString();
_scope = _doc->bind()->findQmlObject(_objectNode);
_targetValue = _scopeChain->context()->lookupType(_doc.data(), QStringList(_name));
return false;
}
return true;
}
bool visit(FunctionDeclaration *node) override bool visit(FunctionDeclaration *node) override
{ {
return visit(static_cast<FunctionExpression *>(node)); return visit(static_cast<FunctionExpression *>(node));

View File

@@ -108,10 +108,6 @@ void QmlJSHighlighter::highlightBlock(const QString &text)
break; break;
} }
} }
if (QStringView(text).mid(token.offset, token.length) == QLatin1String("enum")) {
setFormat(token.offset, token.length, formatForCategory(C_KEYWORD));
break;
}
} else if (index > 0 && maybeQmlBuiltinType(spell)) { } else if (index > 0 && maybeQmlBuiltinType(spell)) {
const Token &previousToken = tokens.at(index - 1); const Token &previousToken = tokens.at(index - 1);
if (previousToken.is(Token::Identifier) if (previousToken.is(Token::Identifier)

View File

@@ -348,6 +348,13 @@ protected:
return visit(static_cast<FunctionExpression *>(ast)); return visit(static_cast<FunctionExpression *>(ast));
} }
bool visit(UiEnumMemberList *ast) override
{
for (auto it = ast; it; it = it->next)
addUse(it->memberToken, SemanticHighlighter::FieldType);
return true;
}
bool visit(PatternElement *ast) override bool visit(PatternElement *ast) override
{ {
if (ast->isVariableDeclaration()) if (ast->isVariableDeclaration())

View File

@@ -233,6 +233,19 @@ private:
m_model->leavePublicMember(); m_model->leavePublicMember();
} }
bool visit(AST::UiEnumDeclaration *enumDecl) override
{
QModelIndex index = m_model->enterEnumDeclaration(enumDecl);
m_nodeToIndex.insert(enumDecl, index);
return true;
}
void endVisit(AST::UiEnumDeclaration *) override
{
m_model->leavePublicMember();
}
bool visit(AST::FunctionDeclaration *functionDeclaration) override bool visit(AST::FunctionDeclaration *functionDeclaration) override
{ {
QModelIndex index = m_model->enterFunctionDeclaration(functionDeclaration); QModelIndex index = m_model->enterFunctionDeclaration(functionDeclaration);
@@ -576,6 +589,33 @@ void QmlOutlineModel::leavePublicMember()
leaveNode(); leaveNode();
} }
QModelIndex QmlOutlineModel::enterEnumDeclaration(AST::UiEnumDeclaration *enumDecl)
{
QMap<int, QVariant> objectData;
if (!enumDecl->name.isEmpty())
objectData.insert(Qt::DisplayRole, enumDecl->name.toString());
objectData.insert(ItemTypeRole, ElementBindingType);
QmlOutlineItem *item = enterNode(objectData, enumDecl, nullptr, Icons::enumMemberIcon());
for (auto member = enumDecl->members; member; member = member->next) {
QMap<int, QVariant> memberData;
if (!member->member.isEmpty())
memberData.insert(Qt::DisplayRole, member->member.toString());
memberData.insert(ItemTypeRole, ElementBindingType);
memberData.insert(AnnotationRole, QString::number(member->value));
enterNode(memberData, member, nullptr, Icons::publicMemberIcon());
leaveNode();
}
return item->index();
}
void QmlOutlineModel::leaveEnumDeclaration()
{
leaveNode();
}
static QString functionDisplayName(QStringView name, AST::FormalParameterList *formals) static QString functionDisplayName(QStringView name, AST::FormalParameterList *formals)
{ {
QString display; QString display;

View File

@@ -91,6 +91,9 @@ private:
QModelIndex enterPublicMember(QmlJS::AST::UiPublicMember *publicMember); QModelIndex enterPublicMember(QmlJS::AST::UiPublicMember *publicMember);
void leavePublicMember(); void leavePublicMember();
QModelIndex enterEnumDeclaration(QmlJS::AST::UiEnumDeclaration *enumDecl);
void leaveEnumDeclaration();
QModelIndex enterFunctionDeclaration(QmlJS::AST::FunctionDeclaration *functionDeclaration); QModelIndex enterFunctionDeclaration(QmlJS::AST::FunctionDeclaration *functionDeclaration);
void leaveFunctionDeclaration(); void leaveFunctionDeclaration();