| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "qmlpropertynode.h" |
| 5 | |
| 6 | #include "classnode.h" |
| 7 | #include "enumnode.h" |
| 8 | #include "genustypes.h" |
| 9 | #include "propertynode.h" |
| 10 | #include "qdocdatabase.h" |
| 11 | #include "utilities.h" |
| 12 | |
| 13 | #include <utility> |
| 14 | |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | using namespace Qt::StringLiterals; |
| 19 | |
| 20 | /*! |
| 21 | Constructor for the QML property node. |
| 22 | */ |
| 23 | QmlPropertyNode::QmlPropertyNode(Aggregate *parent, const QString &name, QString type, |
| 24 | bool attached) |
| 25 | : Node(NodeType::QmlProperty, parent, name), |
| 26 | m_type(std::move(type)), |
| 27 | m_attached(attached) |
| 28 | { |
| 29 | if (m_type == "alias" ) |
| 30 | m_isAlias = true; |
| 31 | if (name.startsWith(s: "__" )) |
| 32 | setStatus(Internal); |
| 33 | // Set the default prefix for enumerators |
| 34 | m_nativeEnum.setPrefix(parent->name()); |
| 35 | } |
| 36 | |
| 37 | /*! |
| 38 | Sets the data type of this property to \a dataType, |
| 39 | preserving the list property modifier if one is set |
| 40 | already. |
| 41 | */ |
| 42 | void QmlPropertyNode::setDataType(const QString &dataType) |
| 43 | { |
| 44 | m_type = dataType; |
| 45 | // Re-apply list modifier if needed |
| 46 | if (auto is_list = m_isList; is_list == FlagValueTrue) { |
| 47 | m_isList = FlagValueDefault; |
| 48 | setIsList(true); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | /*! |
| 53 | \fn bool QmlPropertyNode::isReadOnly() const |
| 54 | |
| 55 | Returns \c true if this QML property node is marked as a |
| 56 | read-only property. |
| 57 | */ |
| 58 | |
| 59 | /*! |
| 60 | Marks this property as a list if \a isList is \c true. |
| 61 | The \c m_type member of a list property is wrapped with |
| 62 | \c {list<>}. |
| 63 | */ |
| 64 | void QmlPropertyNode::setIsList(bool isList) |
| 65 | { |
| 66 | if (m_isList != FlagValueDefault) |
| 67 | return; |
| 68 | |
| 69 | if ((m_isList = toFlagValue(b: isList))) |
| 70 | m_type = "list<%1>"_L1 .arg(args&: m_type); |
| 71 | } |
| 72 | |
| 73 | /*! |
| 74 | Returns \c true if this QML property or attached property is |
| 75 | read-only. If the read-only status is not set explicitly |
| 76 | using the \\readonly command, QDoc attempts to resolve it |
| 77 | from the associated C++ class instantiated by the QML type |
| 78 | that this property belongs to. |
| 79 | |
| 80 | \note Depending on how the QML type is implemented, this |
| 81 | information may not be available to QDoc. If so, add a debug |
| 82 | line but do not treat it as a warning. |
| 83 | */ |
| 84 | bool QmlPropertyNode::isReadOnly() |
| 85 | { |
| 86 | if (m_readOnly != FlagValueDefault) |
| 87 | return fromFlagValue(fv: m_readOnly, defaultValue: false); |
| 88 | |
| 89 | // Find the parent QML type node |
| 90 | auto *parent{this->parent()}; |
| 91 | while (parent && !(parent->isQmlType())) |
| 92 | parent = parent->parent(); |
| 93 | |
| 94 | bool readonly{false}; |
| 95 | if (auto qcn = static_cast<QmlTypeNode *>(parent); qcn && qcn->classNode()) { |
| 96 | if (auto propertyNode = findCorrespondingCppProperty(); propertyNode) |
| 97 | readonly = !propertyNode->isWritable(); |
| 98 | else |
| 99 | qCDebug(lcQdoc).nospace() |
| 100 | << qPrintable(defLocation().toString()) |
| 101 | << ": Automatic resolution of QML property attributes failed for " |
| 102 | << name() |
| 103 | << " (Q_PROPERTY not found in the C++ class hierarchy known to QDoc. " |
| 104 | << "Likely, the type is replaced with a private implementation.)" ; |
| 105 | } |
| 106 | markReadOnly(flag: readonly); |
| 107 | return readonly; |
| 108 | } |
| 109 | |
| 110 | /*! |
| 111 | Returns \c true if this QML property is marked with \required or the |
| 112 | corresponding C++ property uses the REQUIRED keyword. |
| 113 | */ |
| 114 | bool QmlPropertyNode::isRequired() |
| 115 | { |
| 116 | if (m_required != FlagValueDefault) |
| 117 | return fromFlagValue(fv: m_required, defaultValue: false); |
| 118 | |
| 119 | PropertyNode *pn = findCorrespondingCppProperty(); |
| 120 | return pn != nullptr && pn->isRequired(); |
| 121 | } |
| 122 | |
| 123 | /*! |
| 124 | Returns a pointer this QML property's corresponding C++ |
| 125 | property, if it has one. |
| 126 | */ |
| 127 | PropertyNode *QmlPropertyNode::findCorrespondingCppProperty() |
| 128 | { |
| 129 | PropertyNode *pn; |
| 130 | Node *n = parent(); |
| 131 | while (n && !(n->isQmlType())) |
| 132 | n = n->parent(); |
| 133 | if (n) { |
| 134 | auto *qcn = static_cast<QmlTypeNode *>(n); |
| 135 | ClassNode *cn = qcn->classNode(); |
| 136 | if (cn) { |
| 137 | /* |
| 138 | If there is a dot in the property name, first |
| 139 | find the C++ property corresponding to the QML |
| 140 | property group. |
| 141 | */ |
| 142 | QStringList dotSplit = name().split(sep: QChar('.')); |
| 143 | pn = cn->findPropertyNode(name: dotSplit[0]); |
| 144 | if (pn) { |
| 145 | /* |
| 146 | Now find the C++ property corresponding to |
| 147 | the QML property in the QML property group, |
| 148 | <group>.<property>. |
| 149 | */ |
| 150 | if (dotSplit.size() > 1) { |
| 151 | QStringList path(extractClassName(string: pn->qualifiedDataType())); |
| 152 | Node *nn = QDocDatabase::qdocDB()->findClassNode(path); |
| 153 | if (nn) { |
| 154 | auto *cn = static_cast<ClassNode *>(nn); |
| 155 | PropertyNode *pn2 = cn->findPropertyNode(name: dotSplit[1]); |
| 156 | /* |
| 157 | If found, return the C++ property |
| 158 | corresponding to the QML property. |
| 159 | Otherwise, return the C++ property |
| 160 | corresponding to the QML property |
| 161 | group. |
| 162 | */ |
| 163 | return (pn2 ? pn2 : pn); |
| 164 | } |
| 165 | } else |
| 166 | return pn; |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 | return nullptr; |
| 171 | } |
| 172 | |
| 173 | // Only define a mapping between C++ and QML value types with different names. |
| 174 | QSet<QString> QmlPropertyNode::cppQmlValueTypes = { |
| 175 | "float" , |
| 176 | "QColor" , |
| 177 | "QDateTime" , |
| 178 | "QFont" , |
| 179 | "QMatrix4x4" , |
| 180 | "QPoint" , |
| 181 | "QPointF" , |
| 182 | "QQuaternion" , |
| 183 | "qreal" , |
| 184 | "QRect" , |
| 185 | "QRectF" , |
| 186 | "QSize" , |
| 187 | "QSizeF" , |
| 188 | "QString" , |
| 189 | "QUrl" , |
| 190 | "QVector2D" , |
| 191 | "QVector3D" , |
| 192 | "QVector4D" , |
| 193 | "unsigned int" , |
| 194 | }; |
| 195 | |
| 196 | QRegularExpression QmlPropertyNode::qmlBasicList("^list<([^>]+)>$" ); |
| 197 | QRegularExpression QmlPropertyNode::cppBasicList("^(Q[A-Za-z0-9]+)List$" ); |
| 198 | |
| 199 | /*! |
| 200 | Validates a QML property type for the property, returning true if the type |
| 201 | is a QML type or QML list type, returning false if the type is a Qt value |
| 202 | type or Qt list type. |
| 203 | |
| 204 | Specifically, if the type name matches a known value or object type in |
| 205 | qdoc's database, true is returned immediately. |
| 206 | |
| 207 | If the type name matches the syntax for a non-nested QML list of types, |
| 208 | true is returned if the item type of the list is valid; otherwise false is |
| 209 | returned. |
| 210 | |
| 211 | If the type name is a C or C++ type with a corresponding QML type, or if it |
| 212 | matches the syntax of a Qt list type, such as QStringList, false is |
| 213 | returned. |
| 214 | |
| 215 | If none of the above applied, the type name is assumed to be valid and true |
| 216 | is returned. |
| 217 | */ |
| 218 | bool QmlPropertyNode::validateDataType(const QString &type) const |
| 219 | { |
| 220 | QString qmlType = type; |
| 221 | if (qmlType.isNull()) |
| 222 | qmlType = dataType(); |
| 223 | |
| 224 | if (QDocDatabase::qdocDB()->getQmlValueTypes().contains(key: qmlType) || |
| 225 | QDocDatabase::qdocDB()->findQmlType(name: qmlType)) |
| 226 | return true; |
| 227 | |
| 228 | auto match = qmlBasicList.match(subject: qmlType); |
| 229 | if (match.hasMatch()) |
| 230 | return validateDataType(type: match.captured(nth: 1)); |
| 231 | |
| 232 | if (cppQmlValueTypes.contains(value: qmlType) || |
| 233 | cppBasicList.match(subject: qmlType).hasMatch()) |
| 234 | return false; |
| 235 | |
| 236 | return true; |
| 237 | } |
| 238 | |
| 239 | QT_END_NAMESPACE |
| 240 | |