1// Copyright (C) 2021 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 QQMLDOMASTCREATOR_P_H
5#define QQMLDOMASTCREATOR_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 "qqmldomelements_p.h"
19#include "qqmldomitem_p.h"
20#include "qqmldompath_p.h"
21#include "qqmldomscriptelements_p.h"
22
23#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h>
24
25#include <QtQml/private/qqmljsastvisitor_p.h>
26#include <memory>
27#include <type_traits>
28#include <variant>
29
30QT_BEGIN_NAMESPACE
31
32namespace QQmlJS {
33namespace Dom {
34
35class QQmlDomAstCreator final : public AST::Visitor
36{
37 Q_DECLARE_TR_FUNCTIONS(QQmlDomAstCreator)
38 using AST::Visitor::endVisit;
39 using AST::Visitor::visit;
40
41 static constexpr const auto className = "QmlDomAstCreator";
42
43 class DomValue
44 {
45 public:
46 template<typename T>
47 DomValue(const T &obj) : kind(T::kindValue), value(obj)
48 {
49 }
50 DomType kind;
51 std::variant<QmlObject, MethodInfo, QmlComponent, PropertyDefinition, Binding, EnumDecl,
52 EnumItem, ConstantData, Id>
53 value;
54 };
55
56 class QmlStackElement
57 {
58 public:
59 Path path;
60 DomValue item;
61 FileLocations::Tree fileLocations;
62 };
63
64 /*!
65 \internal
66 Contains a ScriptElementVariant, that can be used everywhere in the DOM representation, or a
67 List that should always be inside of something else, e.g., that cannot be the root of the
68 script element DOM representation.
69
70 Also, it makes sure you do not mistreat a list as a regular script element and vice versa.
71
72 The reason for this is that Lists can get pretty unintuitive, as a List could be a Block of
73 statements or a list of variable declarations (let i = 3, j = 4, ...) or something completely
74 different. Instead, always put lists inside named construct (BlockStatement,
75 VariableDeclaration, ...).
76 */
77 class ScriptStackElement
78 {
79 public:
80 template<typename T>
81 static ScriptStackElement from(const T &obj)
82 {
83 if constexpr (std::is_same_v<T, ScriptElements::ScriptList>) {
84 ScriptStackElement s{ ScriptElements::ScriptList::kindValue, obj };
85 return s;
86 } else {
87 ScriptStackElement s{ obj->kind(), ScriptElementVariant::fromElement(obj) };
88 return s;
89 }
90 Q_UNREACHABLE();
91 }
92
93 DomType kind;
94 using Variant = std::variant<ScriptElementVariant, ScriptElements::ScriptList>;
95 Variant value;
96
97 ScriptElementVariant takeVariant()
98 {
99 Q_ASSERT_X(std::holds_alternative<ScriptElementVariant>(value), "takeVariant",
100 "Should be a variant, did the parser change?");
101 return std::get<ScriptElementVariant>(v: std::move(value));
102 }
103
104 bool isList() const { return std::holds_alternative<ScriptElements::ScriptList>(v: value); };
105
106 ScriptElements::ScriptList takeList()
107 {
108 Q_ASSERT_X(std::holds_alternative<ScriptElements::ScriptList>(value), "takeList",
109 "Should be a List, did the parser change?");
110 return std::get<ScriptElements::ScriptList>(v: std::move(value));
111 }
112
113 void setSemanticScope(const QQmlJSScope::Ptr &scope)
114 {
115 if (auto x = std::get_if<ScriptElementVariant>(ptr: &value)) {
116 x->base()->setSemanticScope(scope);
117 return;
118 } else if (auto x = std::get_if<ScriptElements::ScriptList>(ptr: &value)) {
119 x->setSemanticScope(scope);
120 return;
121 }
122 Q_UNREACHABLE();
123 }
124 };
125
126public:
127 void enableScriptExpressions(bool enable = true) { m_enableScriptExpressions = enable; }
128
129private:
130
131 MutableDomItem qmlFile;
132 std::shared_ptr<QmlFile> qmlFilePtr;
133 QVector<QmlStackElement> nodeStack;
134 QList<ScriptStackElement> scriptNodeStack;
135 QVector<int> arrayBindingLevels;
136 FileLocations::Tree rootMap;
137 bool m_enableScriptExpressions;
138
139 template<typename T>
140 QmlStackElement &currentEl(int idx = 0)
141 {
142 Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
143 "Stack does not contain enough elements!");
144 int i = nodeStack.size() - idx;
145 while (i-- > 0) {
146 DomType k = nodeStack.at(i).item.kind;
147 if (k == T::kindValue)
148 return nodeStack[i];
149 }
150 Q_ASSERT_X(false, "currentEl", "Stack does not contan object of type ");
151 return nodeStack.last();
152 }
153
154 template<typename T>
155 ScriptStackElement &currentScriptEl(int idx = 0)
156 {
157 Q_ASSERT_X(m_enableScriptExpressions, "currentScriptEl",
158 "Cannot access script elements when they are disabled!");
159
160 Q_ASSERT_X(idx < scriptNodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
161 "Stack does not contain enough elements!");
162 int i = scriptNodeStack.size() - idx;
163 while (i-- > 0) {
164 DomType k = scriptNodeStack.at(i).kind;
165 if (k == T::element_type::kindValue)
166 return scriptNodeStack[i];
167 }
168 Q_ASSERT_X(false, "currentEl", "Stack does not contain object of type ");
169 return scriptNodeStack.last();
170 }
171
172 template<typename T>
173 T &current(int idx = 0)
174 {
175 return std::get<T>(currentEl<T>(idx).item.value);
176 }
177
178 index_type currentIndex() { return currentNodeEl().path.last().headIndex(); }
179
180 QmlStackElement &currentQmlObjectOrComponentEl(int idx = 0);
181
182 QmlStackElement &currentNodeEl(int i = 0);
183 ScriptStackElement &currentScriptNodeEl(int i = 0);
184
185 DomValue &currentNode(int i = 0);
186
187 void removeCurrentNode(std::optional<DomType> expectedType);
188 void removeCurrentScriptNode(std::optional<DomType> expectedType);
189
190 void pushEl(Path p, DomValue it, AST::Node *n)
191 {
192 nodeStack.append(t: { .path: p, .item: it, .fileLocations: createMap(k: it.kind, p, n) });
193 }
194
195 FileLocations::Tree createMap(FileLocations::Tree base, Path p, AST::Node *n);
196
197 FileLocations::Tree createMap(DomType k, Path p, AST::Node *n);
198
199 const ScriptElementVariant &finalizeScriptExpression(const ScriptElementVariant &element,
200 Path pathFromOwner,
201 const FileLocations::Tree &base);
202
203 const ScriptElementVariant &finalizeScriptList(AST::Node *ast, FileLocations::Tree base);
204 void setScriptExpression (const std::shared_ptr<ScriptExpression>& value);
205
206 Path pathOfLastScriptNode() const;
207
208 /*!
209 \internal
210 Helper to create string literals from AST nodes.
211 */
212 template<typename AstNodeT>
213 static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value,
214 AstNodeT *ast)
215 {
216 auto myExp = std::make_shared<ScriptElements::Literal>(ast->firstSourceLocation(),
217 ast->lastSourceLocation());
218 myExp->setLiteralValue(value.toString());
219 return myExp;
220 }
221
222 static std::shared_ptr<ScriptElements::Literal> makeStringLiteral(QStringView value,
223 QQmlJS::SourceLocation loc)
224 {
225 auto myExp = std::make_shared<ScriptElements::Literal>(args&: loc);
226 myExp->setLiteralValue(value.toString());
227 return myExp;
228 }
229
230 /*!
231 \internal
232 Helper to create script elements from AST nodes, as the DOM classes should be completely
233 dependency-free from AST and parser classes. Using the AST classes in qqmldomastcreator is
234 fine because it needs them for the construction/visit. \sa makeScriptList
235 */
236 template<typename ScriptElementT, typename AstNodeT,
237 typename Enable =
238 std::enable_if_t<!std::is_same_v<ScriptElementT, ScriptElements::ScriptList>>>
239 static decltype(auto) makeScriptElement(AstNodeT *ast)
240 {
241 auto myExp = std::make_shared<ScriptElementT>(ast->firstSourceLocation(),
242 ast->lastSourceLocation());
243 return myExp;
244 }
245
246 /*!
247 \internal
248 Helper to create generic script elements from AST nodes.
249 \sa makeScriptElement
250 */
251 template<typename AstNodeT>
252 static std::shared_ptr<ScriptElements::GenericScriptElement>
253 makeGenericScriptElement(AstNodeT *ast, DomType kind)
254 {
255 auto myExp = std::make_shared<ScriptElements::GenericScriptElement>(
256 ast->firstSourceLocation(), ast->lastSourceLocation());
257 myExp->setKind(kind);
258 return myExp;
259 }
260
261 static std::shared_ptr<ScriptElements::GenericScriptElement>
262 makeGenericScriptElement(SourceLocation location, DomType kind)
263 {
264 auto myExp = std::make_shared<ScriptElements::GenericScriptElement>(args&: location);
265 myExp->setKind(kind);
266 return myExp;
267 }
268
269 /*!
270 \internal
271 Helper to create script lists from AST nodes.
272 \sa makeScriptElement
273 */
274 template<typename AstNodeT>
275 static decltype(auto) makeScriptList(AstNodeT *ast)
276 {
277 auto myExp =
278 ScriptElements::ScriptList(ast->firstSourceLocation(), ast->lastSourceLocation());
279 return myExp;
280 }
281
282 template<typename ScriptElementT>
283 void pushScriptElement(ScriptElementT element)
284 {
285 Q_ASSERT_X(m_enableScriptExpressions, "pushScriptElement",
286 "Cannot create script elements when they are disabled!");
287 scriptNodeStack.append(ScriptStackElement::from(element));
288 }
289
290 void disableScriptElements()
291 {
292 m_enableScriptExpressions = false;
293 scriptNodeStack.clear();
294 }
295
296 ScriptElementVariant scriptElementForQualifiedId(AST::UiQualifiedId *expression);
297
298public:
299 QQmlDomAstCreator(MutableDomItem qmlFile);
300
301 bool visit(AST::UiProgram *program) override;
302 void endVisit(AST::UiProgram *) override;
303
304 bool visit(AST::UiPragma *el) override;
305
306 bool visit(AST::UiImport *el) override;
307
308 bool visit(AST::UiPublicMember *el) override;
309 void endVisit(AST::UiPublicMember *el) override;
310
311 bool visit(AST::UiSourceElement *el) override;
312 void endVisit(AST::UiSourceElement *) override;
313
314 void loadAnnotations(AST::UiObjectMember *el) { AST::Node::accept(node: el->annotations, visitor: this); }
315
316 bool visit(AST::UiObjectDefinition *el) override;
317 void endVisit(AST::UiObjectDefinition *) override;
318
319 bool visit(AST::UiObjectBinding *el) override;
320 void endVisit(AST::UiObjectBinding *) override;
321
322 bool visit(AST::UiScriptBinding *el) override;
323 void endVisit(AST::UiScriptBinding *) override;
324
325 bool visit(AST::UiArrayBinding *el) override;
326 void endVisit(AST::UiArrayBinding *) override;
327
328 bool visit(AST::UiQualifiedId *) override;
329
330 bool visit(AST::UiEnumDeclaration *el) override;
331 void endVisit(AST::UiEnumDeclaration *) override;
332
333 bool visit(AST::UiEnumMemberList *el) override;
334 void endVisit(AST::UiEnumMemberList *el) override;
335
336 bool visit(AST::UiInlineComponent *el) override;
337 void endVisit(AST::UiInlineComponent *) override;
338
339 bool visit(AST::UiRequired *el) override;
340
341 bool visit(AST::UiAnnotation *el) override;
342 void endVisit(AST::UiAnnotation *) override;
343
344 // for Script elements:
345 bool visit(AST::BinaryExpression *exp) override;
346 void endVisit(AST::BinaryExpression *exp) override;
347
348 bool visit(AST::Block *block) override;
349 void endVisit(AST::Block *) override;
350
351 bool visit(AST::ReturnStatement *block) override;
352 void endVisit(AST::ReturnStatement *) override;
353
354 bool visit(AST::ForStatement *forStatement) override;
355 void endVisit(AST::ForStatement *forStatement) override;
356
357 bool visit(AST::PatternElement *pe) override;
358 void endVisit(AST::PatternElement *pe) override;
359 void endVisitHelper(AST::PatternElement *pe,
360 const std::shared_ptr<ScriptElements::GenericScriptElement> &element);
361
362 bool visit(AST::IfStatement *) override;
363 void endVisit(AST::IfStatement *) override;
364
365 bool visit(AST::FieldMemberExpression *) override;
366 void endVisit(AST::FieldMemberExpression *) override;
367
368 bool visit(AST::ArrayMemberExpression *) override;
369 void endVisit(AST::ArrayMemberExpression *) override;
370
371 bool visit(AST::CallExpression *) override;
372 void endVisit(AST::CallExpression *) override;
373
374 bool visit(AST::ArrayPattern *) override;
375 void endVisit(AST::ArrayPattern *) override;
376
377 bool visit(AST::ObjectPattern *) override;
378 void endVisit(AST::ObjectPattern *) override;
379
380 bool visit(AST::PatternProperty *) override;
381 void endVisit(AST::PatternProperty *) override;
382
383 bool visit(AST::VariableStatement *) override;
384 void endVisit(AST::VariableStatement *) override;
385
386 bool visit(AST::Type *expression) override;
387 void endVisit(AST::Type *expression) override;
388
389 // lists of stuff whose children do not need a qqmljsscope: visitation order can be custom
390 bool visit(AST::ArgumentList *) override;
391 bool visit(AST::UiParameterList *) override;
392 bool visit(AST::PatternElementList *) override;
393 bool visit(AST::PatternPropertyList *) override;
394 bool visit(AST::VariableDeclarationList *vdl) override;
395 bool visit(AST::Elision *elision) override;
396
397 // lists of stuff whose children need a qqmljsscope: visitation order cannot be custom
398 bool visit(AST::StatementList *list) override;
399 void endVisit(AST::StatementList *list) override;
400
401 // literals and ids
402 bool visit(AST::IdentifierExpression *expression) override;
403 bool visit(AST::NumericLiteral *expression) override;
404 bool visit(AST::StringLiteral *expression) override;
405 bool visit(AST::NullExpression *expression) override;
406 bool visit(AST::TrueLiteral *expression) override;
407 bool visit(AST::FalseLiteral *expression) override;
408 bool visit(AST::ComputedPropertyName *expression) override;
409 bool visit(AST::IdentifierPropertyName *expression) override;
410 bool visit(AST::NumericLiteralPropertyName *expression) override;
411 bool visit(AST::StringLiteralPropertyName *expression) override;
412 bool visit(AST::TypeAnnotation *expression) override;
413
414 void throwRecursionDepthError() override;
415
416public:
417 friend class QQmlDomAstCreatorWithQQmlJSScope;
418};
419
420class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor
421{
422public:
423 QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger);
424
425#define X(name) \
426 bool visit(AST::name *) override; \
427 void endVisit(AST::name *) override;
428 QQmlJSASTClassListToVisit
429#undef X
430
431 virtual void throwRecursionDepthError() override;
432 /*!
433 \internal
434 Disable the DOM for scriptexpressions, as not yet unimplemented script elements might crash
435 the construction.
436 */
437 void enableScriptExpressions(bool enable = true)
438 {
439 m_enableScriptExpressions = enable;
440 m_domCreator.enableScriptExpressions(enable);
441 }
442
443 QQmlJSImporter &importer() { return m_importer; }
444 QQmlJSImportVisitor &scopeCreator() { return m_scopeCreator; }
445
446private:
447 void setScopeInDomAfterEndvisit();
448 void setScopeInDomBeforeEndvisit();
449
450 template<typename U, typename... V>
451 using IsInList = std::disjunction<std::is_same<U, V>...>;
452 template<typename U>
453 using RequiresCustomIteration = IsInList<U, AST::PatternElementList, AST::PatternPropertyList,
454 AST::FormalParameterList>;
455
456 template<typename T>
457 void customListIteration(T *t)
458 {
459 static_assert(RequiresCustomIteration<T>::value);
460 for (auto it = t; it; it = it->next) {
461 if constexpr (std::is_same_v<T, AST::PatternElementList>) {
462 AST::Node::accept(it->elision, this);
463 AST::Node::accept(it->element, this);
464 } else if constexpr (std::is_same_v<T, AST::PatternPropertyList>) {
465 AST::Node::accept(it->property, this);
466 } else if constexpr (std::is_same_v<T, AST::FormalParameterList>) {
467 AST::Node::accept(it->element, this);
468 } else {
469 Q_UNREACHABLE();
470 }
471 }
472 }
473
474 template<typename T>
475 bool visitT(T *t)
476 {
477 if (m_marker && m_marker->nodeKind == t->kind) {
478 m_marker->count += 1;
479 }
480
481 // first case: no marker, both can visit
482 if (!m_marker) {
483 bool continueForDom = m_domCreator.visit(t);
484 bool continueForScope = m_scopeCreator.visit(t);
485 if (!continueForDom && !continueForScope)
486 return false;
487 else if (continueForDom ^ continueForScope) {
488 m_marker.emplace();
489 m_marker->inactiveVisitor = continueForDom ? ScopeCreator : DomCreator;
490 m_marker->count = 1;
491 m_marker->nodeKind = AST::Node::Kind(t->kind);
492
493 if constexpr (RequiresCustomIteration<T>::value) {
494 customListIteration(t);
495 return false;
496 } else {
497 return true;
498 }
499 } else {
500 Q_ASSERT(continueForDom && continueForScope);
501
502 if constexpr (RequiresCustomIteration<T>::value) {
503 customListIteration(t);
504 return false;
505 } else {
506 return true;
507 }
508 }
509 Q_UNREACHABLE();
510 }
511
512 // second case: a marker, just one visit
513 switch (m_marker->inactiveVisitor) {
514 case DomCreator: {
515 const bool continueForScope = m_scopeCreator.visit(t);
516 if (continueForScope) {
517 if constexpr (RequiresCustomIteration<T>::value) {
518 customListIteration(t);
519 return false;
520 }
521 }
522 return continueForScope;
523 }
524 case ScopeCreator: {
525 const bool continueForDom = m_domCreator.visit(t);
526 if (continueForDom) {
527 if constexpr (RequiresCustomIteration<T>::value) {
528 customListIteration(t);
529 return false;
530 }
531 }
532 return continueForDom;
533 }
534 };
535 Q_UNREACHABLE();
536 }
537
538 template<typename T>
539 void endVisitT(T *t)
540 {
541 if (m_marker && m_marker->nodeKind == t->kind) {
542 m_marker->count -= 1;
543 if (m_marker->count == 0)
544 m_marker.reset();
545 }
546
547 if (m_marker) {
548 switch (m_marker->inactiveVisitor) {
549 case DomCreator: {
550 m_scopeCreator.endVisit(t);
551 return;
552 }
553 case ScopeCreator: {
554 m_domCreator.endVisit(t);
555 return;
556 }
557 };
558 Q_UNREACHABLE();
559 }
560
561 setScopeInDomBeforeEndvisit();
562 m_domCreator.endVisit(t);
563 setScopeInDomAfterEndvisit();
564 m_scopeCreator.endVisit(t);
565 }
566
567 QQmlJSScope::Ptr m_root;
568 QQmlJSLogger *m_logger;
569 QQmlJSImporter m_importer;
570 QString m_implicitImportDirectory;
571 QQmlJSImportVisitor m_scopeCreator;
572 QQmlDomAstCreator m_domCreator;
573
574 enum InactiveVisitor : bool { DomCreator, ScopeCreator };
575 struct Marker
576 {
577 qsizetype count;
578 AST::Node::Kind nodeKind;
579 InactiveVisitor inactiveVisitor;
580 };
581 std::optional<Marker> m_marker;
582 bool m_enableScriptExpressions;
583};
584
585} // end namespace Dom
586} // end namespace QQmlJS
587
588QT_END_NAMESPACE
589#endif // QQMLDOMASTCREATOR_P_H
590

source code of qtdeclarative/src/qmldom/qqmldomastcreator_p.h