| 1 | // Copyright (C) 2023 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #ifndef QQMLDOMSCRIPTELEMENTS_P_H |
| 5 | #define QQMLDOMSCRIPTELEMENTS_P_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | // |
| 17 | |
| 18 | #include "qqmldomitem_p.h" |
| 19 | #include "qqmldomelements_p.h" |
| 20 | #include "qqmldomfilelocations_p.h" |
| 21 | #include "qqmldompath_p.h" |
| 22 | #include <algorithm> |
| 23 | #include <limits> |
| 24 | #include <type_traits> |
| 25 | #include <utility> |
| 26 | #include <variant> |
| 27 | |
| 28 | QT_BEGIN_NAMESPACE |
| 29 | |
| 30 | namespace QQmlJS { |
| 31 | namespace Dom { |
| 32 | |
| 33 | namespace ScriptElements { |
| 34 | |
| 35 | template<DomType type> |
| 36 | class ScriptElementBase : public ScriptElement |
| 37 | { |
| 38 | public: |
| 39 | using BaseT = ScriptElementBase<type>; |
| 40 | static constexpr DomType kindValue = type; |
| 41 | static constexpr DomKind domKindValue = DomKind::ScriptElement; |
| 42 | |
| 43 | ScriptElementBase(QQmlJS::SourceLocation combinedLocation = QQmlJS::SourceLocation{}) |
| 44 | : ScriptElement(), m_locations({ { FileLocationRegion::MainRegion, combinedLocation } }) |
| 45 | { |
| 46 | } |
| 47 | ScriptElementBase(QQmlJS::SourceLocation first, QQmlJS::SourceLocation last) |
| 48 | : ScriptElementBase(combine(l1: first, l2: last)) |
| 49 | { |
| 50 | } |
| 51 | DomType kind() const override { return type; } |
| 52 | DomKind domKind() const override { return domKindValue; } |
| 53 | |
| 54 | void createFileLocations(const FileLocations::Tree &base) override |
| 55 | { |
| 56 | FileLocations::Tree res = FileLocations::ensure(base, basePath: pathFromOwner()); |
| 57 | for (auto location: m_locations) { |
| 58 | FileLocations::addRegion(fLoc: res, region: location.first, loc: location.second); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | /* |
| 63 | Pretty prints the current DomItem. Currently, for script elements, this is done entirely on |
| 64 | the parser representation (via the AST classes), but it could be moved here if needed. |
| 65 | */ |
| 66 | // void writeOut(const DomItem &self, OutWriter &lw) const override; |
| 67 | |
| 68 | /*! |
| 69 | All of the following overloads are only required for optimization purposes. |
| 70 | The base implementation will work fine, but might be slightly slower. |
| 71 | You can override dump(), fields(), field(), indexes(), index(), keys() or key() if the |
| 72 | performance of the base class becomes problematic. |
| 73 | */ |
| 74 | |
| 75 | // // needed for debug |
| 76 | // void dump(const DomItem &, const Sink &sink, int indent, FilterT filter) const override; |
| 77 | |
| 78 | // // just required for optimization if iterateDirectSubpaths is slow |
| 79 | // QList<QString> fields(const DomItem &self) const override; |
| 80 | // DomItem field(const DomItem &self, QStringView name) const override; |
| 81 | |
| 82 | // index_type indexes(const DomItem &self) const override; |
| 83 | // DomItem index(const DomItem &self, index_type index) const override; |
| 84 | |
| 85 | // QSet<QString> const keys(const DomItem &self) const override; |
| 86 | // DomItem key(const DomItem &self, const QString &name) const override; |
| 87 | |
| 88 | QQmlJS::SourceLocation mainRegionLocation() const |
| 89 | { |
| 90 | Q_ASSERT(m_locations.size() > 0); |
| 91 | Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion); |
| 92 | |
| 93 | auto current = m_locations.front(); |
| 94 | return current.second; |
| 95 | } |
| 96 | void setMainRegionLocation(const QQmlJS::SourceLocation &location) |
| 97 | { |
| 98 | Q_ASSERT(m_locations.size() > 0); |
| 99 | Q_ASSERT(m_locations.front().first == FileLocationRegion::MainRegion); |
| 100 | |
| 101 | m_locations.front().second = location; |
| 102 | } |
| 103 | void addLocation(FileLocationRegion region, QQmlJS::SourceLocation location) |
| 104 | { |
| 105 | Q_ASSERT_X(region != FileLocationRegion::MainRegion, "ScriptElementBase::addLocation" , |
| 106 | "use the setCombinedLocation instead!" ); |
| 107 | m_locations.emplace_back(args&: region, args&: location); |
| 108 | } |
| 109 | |
| 110 | protected: |
| 111 | std::vector<std::pair<FileLocationRegion, QQmlJS::SourceLocation>> m_locations; |
| 112 | }; |
| 113 | |
| 114 | class ScriptList : public ScriptElementBase<DomType::List> |
| 115 | { |
| 116 | public: |
| 117 | using typename ScriptElementBase<DomType::List>::BaseT; |
| 118 | |
| 119 | using BaseT::BaseT; |
| 120 | |
| 121 | // minimal required overload for this to be wrapped as DomItem: |
| 122 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override |
| 123 | { |
| 124 | bool cont = |
| 125 | asList(path: self.pathFromOwner().withKey(name: QString())).iterateDirectSubpaths(self, visitor); |
| 126 | return cont; |
| 127 | } |
| 128 | void updatePathFromOwner(const Path &p) override |
| 129 | { |
| 130 | BaseT::updatePathFromOwner(newPath: p); |
| 131 | for (int i = 0; i < m_list.size(); ++i) { |
| 132 | Q_ASSERT(m_list[i].base()); |
| 133 | m_list[i].base()->updatePathFromOwner(newPath: p.withIndex(i)); |
| 134 | } |
| 135 | } |
| 136 | void createFileLocations(const FileLocations::Tree &base) override |
| 137 | { |
| 138 | BaseT::createFileLocations(base); |
| 139 | |
| 140 | for (int i = 0; i < m_list.size(); ++i) { |
| 141 | Q_ASSERT(m_list[i].base()); |
| 142 | m_list[i].base()->createFileLocations(fileLocationOfOwner: base); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | List asList(const Path &path) const |
| 147 | { |
| 148 | auto asList = List::fromQList<ScriptElementVariant>( |
| 149 | pathFromOwner: path, list: m_list, |
| 150 | elWrapper: [](const DomItem &list, const PathEls::PathComponent &, const ScriptElementVariant &wrapped) |
| 151 | -> DomItem { return list.subScriptElementWrapperItem(obj: wrapped); }); |
| 152 | |
| 153 | return asList; |
| 154 | } |
| 155 | |
| 156 | void append(const ScriptElementVariant &statement) { m_list.push_back(t: statement); } |
| 157 | void append(const ScriptList &list) { m_list.append(l: list.m_list); } |
| 158 | void reverse() { std::reverse(first: m_list.begin(), last: m_list.end()); } |
| 159 | void replaceKindForGenericChildren(DomType oldType, DomType newType); |
| 160 | const QList<ScriptElementVariant> &qList() const { return std::as_const(t: m_list); }; |
| 161 | |
| 162 | private: |
| 163 | QList<ScriptElementVariant> m_list; |
| 164 | }; |
| 165 | |
| 166 | class GenericScriptElement : public ScriptElementBase<DomType::ScriptGenericElement> |
| 167 | { |
| 168 | public: |
| 169 | using BaseT::BaseT; |
| 170 | using VariantT = std::variant<ScriptElementVariant, ScriptList>; |
| 171 | |
| 172 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 173 | void updatePathFromOwner(const Path &p) override; |
| 174 | void createFileLocations(const FileLocations::Tree &base) override; |
| 175 | |
| 176 | DomType kind() const override { return m_kind; } |
| 177 | void setKind(DomType kind) { m_kind = kind; } |
| 178 | |
| 179 | decltype(auto) insertChild(QStringView name, VariantT v) |
| 180 | { |
| 181 | return m_children.insert(x: std::make_pair(x&: name, y&: v)); |
| 182 | } |
| 183 | |
| 184 | ScriptElementVariant elementChild(const QQmlJS::Dom::FieldType &field) |
| 185 | { |
| 186 | auto it = m_children.find(x: field); |
| 187 | if (it == m_children.end()) |
| 188 | return {}; |
| 189 | if (!std::holds_alternative<ScriptElementVariant>(v: it->second)) |
| 190 | return {}; |
| 191 | return std::get<ScriptElementVariant>(v&: it->second); |
| 192 | } |
| 193 | |
| 194 | void insertValue(QStringView name, const QCborValue &v) |
| 195 | { |
| 196 | m_values.insert(x: std::make_pair(x&: name, y: v)); |
| 197 | } |
| 198 | |
| 199 | QCborValue value() const override |
| 200 | { |
| 201 | auto it = m_values.find(x: Fields::value); |
| 202 | if (it == m_values.cend()) |
| 203 | return {}; |
| 204 | |
| 205 | return it->second; |
| 206 | } |
| 207 | |
| 208 | private: |
| 209 | /*! |
| 210 | \internal |
| 211 | The DomItem interface will use iterateDirectSubpaths for all kinds of operations on the |
| 212 | GenericScriptElement. Therefore, to avoid bad surprises when using the DomItem interface, use |
| 213 | a sorted map to always iterate the children in the same order. |
| 214 | */ |
| 215 | std::map<QQmlJS::Dom::FieldType, VariantT> m_children; |
| 216 | // value fields |
| 217 | std::map<QQmlJS::Dom::FieldType, QCborValue> m_values; |
| 218 | DomType m_kind = DomType::Empty; |
| 219 | }; |
| 220 | |
| 221 | class BlockStatement : public ScriptElementBase<DomType::ScriptBlockStatement> |
| 222 | { |
| 223 | public: |
| 224 | using BaseT::BaseT; |
| 225 | |
| 226 | // minimal required overload for this to be wrapped as DomItem: |
| 227 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 228 | void updatePathFromOwner(const Path &p) override; |
| 229 | void createFileLocations(const FileLocations::Tree &base) override; |
| 230 | |
| 231 | ScriptList statements() const { return m_statements; } |
| 232 | void setStatements(const ScriptList &statements) { m_statements = statements; } |
| 233 | |
| 234 | private: |
| 235 | ScriptList m_statements; |
| 236 | }; |
| 237 | |
| 238 | class IdentifierExpression : public ScriptElementBase<DomType::ScriptIdentifierExpression> |
| 239 | { |
| 240 | public: |
| 241 | using BaseT::BaseT; |
| 242 | void setName(QStringView name) { m_name = name.toString(); } |
| 243 | QString name() { return m_name; } |
| 244 | |
| 245 | // minimal required overload for this to be wrapped as DomItem: |
| 246 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 247 | |
| 248 | QCborValue value() const override { return QCborValue(m_name); } |
| 249 | |
| 250 | private: |
| 251 | QString m_name; |
| 252 | }; |
| 253 | |
| 254 | class Literal : public ScriptElementBase<DomType::ScriptLiteral> |
| 255 | { |
| 256 | public: |
| 257 | using BaseT::BaseT; |
| 258 | |
| 259 | using VariantT = std::variant<QString, double, bool, std::nullptr_t>; |
| 260 | |
| 261 | void setLiteralValue(VariantT value) { m_value = value; } |
| 262 | VariantT literalValue() const { return m_value; } |
| 263 | |
| 264 | // minimal required overload for this to be wrapped as DomItem: |
| 265 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 266 | |
| 267 | QCborValue value() const override |
| 268 | { |
| 269 | return std::visit(visitor: [](auto &&e) -> QCborValue { return e; }, variants: m_value); |
| 270 | } |
| 271 | |
| 272 | private: |
| 273 | VariantT m_value; |
| 274 | }; |
| 275 | |
| 276 | // TODO: test this method + implement foreach etc |
| 277 | class ForStatement : public ScriptElementBase<DomType::ScriptForStatement> |
| 278 | { |
| 279 | public: |
| 280 | using BaseT::BaseT; |
| 281 | |
| 282 | // minimal required overload for this to be wrapped as DomItem: |
| 283 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 284 | void updatePathFromOwner(const Path &p) override; |
| 285 | void createFileLocations(const FileLocations::Tree &base) override; |
| 286 | |
| 287 | ScriptElementVariant initializer() const { return m_initializer; } |
| 288 | void setInitializer(const ScriptElementVariant &newInitializer) |
| 289 | { |
| 290 | m_initializer = newInitializer; |
| 291 | } |
| 292 | |
| 293 | ScriptElementVariant declarations() const { return m_declarations; } |
| 294 | void setDeclarations(const ScriptElementVariant &newDeclaration) |
| 295 | { |
| 296 | m_declarations = newDeclaration; |
| 297 | } |
| 298 | ScriptElementVariant condition() const { return m_condition; } |
| 299 | void setCondition(const ScriptElementVariant &newCondition) { m_condition = newCondition; } |
| 300 | ScriptElementVariant expression() const { return m_expression; } |
| 301 | void setExpression(const ScriptElementVariant &newExpression) { m_expression = newExpression; } |
| 302 | ScriptElementVariant body() const { return m_body; } |
| 303 | void setBody(const ScriptElementVariant &newBody) { m_body = newBody; } |
| 304 | |
| 305 | private: |
| 306 | ScriptElementVariant m_initializer; |
| 307 | ScriptElementVariant m_declarations; |
| 308 | ScriptElementVariant m_condition; |
| 309 | ScriptElementVariant m_expression; |
| 310 | ScriptElementVariant m_body; |
| 311 | }; |
| 312 | |
| 313 | class IfStatement : public ScriptElementBase<DomType::ScriptIfStatement> |
| 314 | { |
| 315 | public: |
| 316 | using BaseT::BaseT; |
| 317 | |
| 318 | // minimal required overload for this to be wrapped as DomItem: |
| 319 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 320 | void updatePathFromOwner(const Path &p) override; |
| 321 | void createFileLocations(const FileLocations::Tree &base) override; |
| 322 | |
| 323 | ScriptElementVariant condition() const { return m_condition; } |
| 324 | void setCondition(const ScriptElementVariant &condition) { m_condition = condition; } |
| 325 | ScriptElementVariant consequence() { return m_consequence; } |
| 326 | void setConsequence(const ScriptElementVariant &consequence) { m_consequence = consequence; } |
| 327 | ScriptElementVariant alternative() { return m_alternative; } |
| 328 | void setAlternative(const ScriptElementVariant &alternative) { m_alternative = alternative; } |
| 329 | |
| 330 | private: |
| 331 | ScriptElementVariant m_condition; |
| 332 | ScriptElementVariant m_consequence; |
| 333 | ScriptElementVariant m_alternative; |
| 334 | }; |
| 335 | |
| 336 | class ReturnStatement : public ScriptElementBase<DomType::ScriptReturnStatement> |
| 337 | { |
| 338 | public: |
| 339 | using BaseT::BaseT; |
| 340 | |
| 341 | // minimal required overload for this to be wrapped as DomItem: |
| 342 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 343 | void updatePathFromOwner(const Path &p) override; |
| 344 | void createFileLocations(const FileLocations::Tree &base) override; |
| 345 | |
| 346 | ScriptElementVariant expression() const { return m_expression; } |
| 347 | void setExpression(ScriptElementVariant expression) { m_expression = expression; } |
| 348 | |
| 349 | private: |
| 350 | ScriptElementVariant m_expression; |
| 351 | }; |
| 352 | |
| 353 | class BinaryExpression : public ScriptElementBase<DomType::ScriptBinaryExpression> |
| 354 | { |
| 355 | public: |
| 356 | using BaseT::BaseT; |
| 357 | |
| 358 | enum Operator : char { |
| 359 | FieldMemberAccess, |
| 360 | ArrayMemberAccess, |
| 361 | TO_BE_IMPLEMENTED = std::numeric_limits<char>::max(), // not required by qmlls |
| 362 | }; |
| 363 | |
| 364 | // minimal required overload for this to be wrapped as DomItem: |
| 365 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 366 | void updatePathFromOwner(const Path &p) override; |
| 367 | void createFileLocations(const FileLocations::Tree &base) override; |
| 368 | |
| 369 | ScriptElementVariant left() const { return m_left; } |
| 370 | void setLeft(const ScriptElementVariant &newLeft) { m_left = newLeft; } |
| 371 | ScriptElementVariant right() const { return m_right; } |
| 372 | void setRight(const ScriptElementVariant &newRight) { m_right = newRight; } |
| 373 | int op() const { return m_operator; } |
| 374 | void setOp(Operator op) { m_operator = op; } |
| 375 | |
| 376 | private: |
| 377 | ScriptElementVariant m_left; |
| 378 | ScriptElementVariant m_right; |
| 379 | Operator m_operator = TO_BE_IMPLEMENTED; |
| 380 | }; |
| 381 | |
| 382 | class VariableDeclarationEntry : public ScriptElementBase<DomType::ScriptVariableDeclarationEntry> |
| 383 | { |
| 384 | public: |
| 385 | using BaseT::BaseT; |
| 386 | |
| 387 | enum ScopeType { Var, Let, Const }; |
| 388 | |
| 389 | ScopeType scopeType() const { return m_scopeType; } |
| 390 | void setScopeType(ScopeType scopeType) { m_scopeType = scopeType; } |
| 391 | |
| 392 | ScriptElementVariant identifier() const { return m_identifier; } |
| 393 | void setIdentifier(const ScriptElementVariant &identifier) { m_identifier = identifier; } |
| 394 | |
| 395 | ScriptElementVariant initializer() const { return m_initializer; } |
| 396 | void setInitializer(const ScriptElementVariant &initializer) { m_initializer = initializer; } |
| 397 | |
| 398 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 399 | void updatePathFromOwner(const Path &p) override; |
| 400 | void createFileLocations(const FileLocations::Tree &base) override; |
| 401 | |
| 402 | private: |
| 403 | ScopeType m_scopeType; |
| 404 | ScriptElementVariant m_identifier; |
| 405 | ScriptElementVariant m_initializer; |
| 406 | }; |
| 407 | |
| 408 | class VariableDeclaration : public ScriptElementBase<DomType::ScriptVariableDeclaration> |
| 409 | { |
| 410 | public: |
| 411 | using BaseT::BaseT; |
| 412 | |
| 413 | // minimal required overload for this to be wrapped as DomItem: |
| 414 | bool iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const override; |
| 415 | void updatePathFromOwner(const Path &p) override; |
| 416 | void createFileLocations(const FileLocations::Tree &base) override; |
| 417 | |
| 418 | void setDeclarations(const ScriptList &list) { m_declarations = list; } |
| 419 | ScriptList declarations() { return m_declarations; } |
| 420 | |
| 421 | private: |
| 422 | ScriptList m_declarations; |
| 423 | }; |
| 424 | |
| 425 | } // namespace ScriptElements |
| 426 | } // end namespace Dom |
| 427 | } // end namespace QQmlJS |
| 428 | |
| 429 | QT_END_NAMESPACE |
| 430 | |
| 431 | #endif // QQMLDOMSCRIPTELEMENTS_P_H |
| 432 | |