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 | |
30 | QT_BEGIN_NAMESPACE |
31 | |
32 | namespace QQmlJS { |
33 | namespace Dom { |
34 | |
35 | class 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 | |
126 | public: |
127 | void enableScriptExpressions(bool enable = true) { m_enableScriptExpressions = enable; } |
128 | |
129 | private: |
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 ¤tEl(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 ¤tScriptEl(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 ¤t(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 ¤tQmlObjectOrComponentEl(int idx = 0); |
181 | |
182 | QmlStackElement ¤tNodeEl(int i = 0); |
183 | ScriptStackElement ¤tScriptNodeEl(int i = 0); |
184 | |
185 | DomValue ¤tNode(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 | |
298 | public: |
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 | |
416 | public: |
417 | friend class QQmlDomAstCreatorWithQQmlJSScope; |
418 | }; |
419 | |
420 | class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor |
421 | { |
422 | public: |
423 | QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger); |
424 | |
425 | #define X(name) \ |
426 | bool (AST::name *) override; \ |
427 | void (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 | |
446 | private: |
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 | |
588 | QT_END_NAMESPACE |
589 | #endif // QQMLDOMASTCREATOR_P_H |
590 | |