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#include "qqmldomastcreator_p.h"
5#include "qqmldomconstants_p.h"
6#include "qqmldomelements_p.h"
7#include "qqmldomitem_p.h"
8#include "qqmldompath_p.h"
9#include "qqmldomscriptelements_p.h"
10#include "qqmldomtop_p.h"
11#include "qqmldomerrormessage_p.h"
12#include "qqmldomastdumper_p.h"
13#include "qqmldomattachedinfo_p.h"
14#include "qqmldomastcreator_p.h"
15
16#include <QtQml/private/qqmljsast_p.h>
17
18#include <QtCore/QDir>
19#include <QtCore/QFileInfo>
20#include <QtCore/QScopeGuard>
21#include <QtCore/QLoggingCategory>
22
23#include <memory>
24#include <optional>
25#include <type_traits>
26#include <variant>
27#include <vector>
28
29static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg);
30
31/*
32 Avoid crashing on files with JS-elements that are not implemented yet.
33 Might be removed (definition + usages) once all script elements are implemented.
34*/
35#define Q_SCRIPTELEMENT_DISABLE() \
36 do { \
37 qDebug() << "Could not construct the JS DOM at" << __FILE__ << ":" << __LINE__ \
38 << ", skipping JS elements..."; \
39 disableScriptElements(); \
40 } while (false)
41
42#define Q_SCRIPTELEMENT_EXIT_IF(check) \
43 do { \
44 if (m_enableScriptExpressions && (check)) { \
45 Q_SCRIPTELEMENT_DISABLE(); \
46 return; \
47 } \
48 } while (false)
49
50QT_BEGIN_NAMESPACE
51namespace QQmlJS {
52namespace Dom {
53
54using namespace AST;
55
56template<typename K, typename V>
57V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx)
58{
59 if (idx < 0)
60 return nullptr;
61 auto it = mmap.find(key);
62 auto end = mmap.end();
63 if (it == end)
64 return nullptr;
65 auto it2 = it;
66 index_type nEl = 0;
67 while (it2 != end && it2.key() == key) {
68 ++it2;
69 ++nEl;
70 }
71 if (nEl <= idx)
72 return nullptr;
73 for (index_type i = idx + 1; i < nEl; ++i)
74 ++it;
75 return &(*it);
76}
77
78static ErrorGroups astParseErrors()
79{
80 static ErrorGroups errs = { .groups: { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"),
81 NewErrorGroup("Parsing") } };
82 return errs;
83}
84
85static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
86{
87 QString result;
88
89 for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
90 if (iter != qualifiedId)
91 result += delimiter;
92
93 result += iter->name;
94 }
95
96 return result;
97}
98
99static QString typeToString(AST::Type *t)
100{
101 Q_ASSERT(t);
102 QString res = toString(qualifiedId: t->typeId);
103
104 if (UiQualifiedId *arg = t->typeArgument)
105 res += u'<' + toString(qualifiedId: arg) + u'>';
106
107 return res;
108}
109
110SourceLocation combineLocations(SourceLocation s1, SourceLocation s2)
111{
112 return combine(l1: s1, l2: s2);
113}
114
115SourceLocation combineLocations(Node *n)
116{
117 return combineLocations(s1: n->firstSourceLocation(), s2: n->lastSourceLocation());
118}
119
120QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx)
121{
122 Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
123 "Stack does not contain enough elements!");
124 int i = nodeStack.size() - idx;
125 while (i-- > 0) {
126 DomType k = nodeStack.at(i).item.kind;
127 if (k == DomType::QmlObject || k == DomType::QmlComponent)
128 return nodeStack[i];
129 }
130 Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack");
131 return nodeStack.last();
132}
133
134QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentNodeEl(int i)
135{
136 Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!");
137 return nodeStack[nodeStack.size() - i - 1];
138}
139
140QQmlDomAstCreator::ScriptStackElement &QQmlDomAstCreator::currentScriptNodeEl(int i)
141{
142 Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode",
143 "Stack does not contain element!");
144 return scriptNodeStack[scriptNodeStack.size() - i - 1];
145}
146
147QQmlDomAstCreator::DomValue &QQmlDomAstCreator::currentNode(int i)
148{
149 Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode",
150 "Stack does not contain element!");
151 return nodeStack[nodeStack.size() - i - 1].item;
152}
153
154void QQmlDomAstCreator::removeCurrentNode(std::optional<DomType> expectedType)
155{
156 Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node");
157 if (expectedType)
158 Q_ASSERT(nodeStack.last().item.kind == *expectedType);
159 nodeStack.removeLast();
160}
161
162void QQmlDomAstCreator::removeCurrentScriptNode(std::optional<DomType> expectedType)
163{
164 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
165 Q_ASSERT_X(!scriptNodeStack.isEmpty(), className,
166 "popCurrentScriptNode() without any node");
167 if (expectedType)
168 Q_ASSERT(scriptNodeStack.last().kind == *expectedType);
169 scriptNodeStack.removeLast();
170}
171
172/*!
173 \internal
174 Prepares a script element DOM representation such that it can be used inside a QML DOM element.
175
176 Make sure to add, for each of its use, a test in tst_qmldomitem:finalizeScriptExpressions, as
177 using a wrong pathFromOwner and/or a wrong base might lead to bugs hard to debug and spurious
178 crashes.
179 */
180const ScriptElementVariant &
181QQmlDomAstCreator::finalizeScriptExpression(const ScriptElementVariant &element, Path pathFromOwner,
182 const FileLocations::Tree &base)
183{
184 auto e = element.base();
185 Q_ASSERT(e);
186
187 e->updatePathFromOwner(newPath: pathFromOwner);
188 e->createFileLocations(fileLocationOfOwner: base);
189 return element;
190}
191
192FileLocations::Tree QQmlDomAstCreator::createMap(FileLocations::Tree base, Path p, AST::Node *n)
193{
194 FileLocations::Tree res = FileLocations::ensure(base, basePath: p, pType: AttachedInfo::PathType::Relative);
195 if (n)
196 FileLocations::addRegion(fLoc: res, locName: QString(), loc: combineLocations(n));
197 return res;
198}
199
200FileLocations::Tree QQmlDomAstCreator::createMap(DomType k, Path p, AST::Node *n)
201{
202 FileLocations::Tree base;
203 switch (k) {
204 case DomType::QmlObject:
205 switch (currentNode().kind) {
206 case DomType::QmlObject:
207 case DomType::QmlComponent:
208 case DomType::PropertyDefinition:
209 case DomType::Binding:
210 case DomType::Id:
211 case DomType::MethodInfo:
212 break;
213 default:
214 qCWarning(domLog) << "unexpected type" << domTypeToString(k: currentNode().kind);
215 Q_UNREACHABLE();
216 }
217 base = currentNodeEl().fileLocations;
218 if (p.length() > 2) {
219 Path p2 = p[p.length() - 2];
220 if (p2.headKind() == Path::Kind::Field
221 && (p2.checkHeadName(name: Fields::children) || p2.checkHeadName(name: Fields::objects)
222 || p2.checkHeadName(name: Fields::value) || p2.checkHeadName(name: Fields::annotations)
223 || p2.checkHeadName(name: Fields::children)))
224 p = p.mid(offset: p.length() - 2, length: 2);
225 else if (p.last().checkHeadName(name: Fields::value)
226 && p.last().headKind() == Path::Kind::Field)
227 p = p.last();
228 else {
229 qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
230 Q_UNREACHABLE();
231 }
232 } else {
233 qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
234 Q_UNREACHABLE();
235 }
236 break;
237 case DomType::EnumItem:
238 base = currentNodeEl().fileLocations;
239 break;
240 case DomType::QmlComponent:
241 case DomType::Pragma:
242 case DomType::Import:
243 case DomType::Id:
244 case DomType::EnumDecl:
245 base = rootMap;
246 break;
247 case DomType::Binding:
248 case DomType::PropertyDefinition:
249 case DomType::MethodInfo:
250 base = currentEl<QmlObject>().fileLocations;
251 if (p.length() > 3)
252 p = p.mid(offset: p.length() - 3, length: 3);
253 break;
254
255 default:
256 qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k);
257 Q_UNREACHABLE();
258 break;
259 }
260 return createMap(base, p, n);
261}
262
263QQmlDomAstCreator::QQmlDomAstCreator(MutableDomItem qmlFile)
264 : qmlFile(qmlFile),
265 qmlFilePtr(qmlFile.ownerAs<QmlFile>()),
266 rootMap(qmlFilePtr->fileLocationsTree())
267{
268}
269
270bool QQmlDomAstCreator::visit(UiProgram *program)
271{
272 QFileInfo fInfo(qmlFile.canonicalFilePath());
273 QString componentName = fInfo.baseName();
274 QmlComponent *cPtr;
275 Path p = qmlFilePtr->addComponent(component: QmlComponent(componentName), option: AddOption::KeepExisting,
276 cPtr: &cPtr);
277 MutableDomItem newC(qmlFile.item(), p);
278 Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent");
279 // QmlFile region == Component region == program span
280 // we hide the component span because the component s written after the imports
281 FileLocations::addRegion(fLoc: rootMap, locName: QString(), loc: combineLocations(n: program));
282 pushEl(p, it: *cPtr, n: program);
283 // implicit imports
284 // add implicit directory import
285 if (!fInfo.canonicalPath().isEmpty()) {
286 Import selfDirImport(QmlUri::fromDirectoryString(importStr: fInfo.canonicalPath()));
287 selfDirImport.implicit = true;
288 qmlFilePtr->addImport(i: selfDirImport);
289 }
290 for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) {
291 i.implicit = true;
292 qmlFilePtr->addImport(i);
293 }
294 return true;
295}
296
297void QQmlDomAstCreator::endVisit(AST::UiProgram *)
298{
299 MutableDomItem newC = qmlFile.path(p: currentNodeEl().path);
300 QmlComponent &comp = current<QmlComponent>();
301 for (const Pragma &p : qmlFilePtr->pragmas()) {
302 if (p.name.compare(s: u"singleton", cs: Qt::CaseInsensitive) == 0) {
303 comp.setIsSingleton(true);
304 comp.setIsCreatable(false); // correct?
305 }
306 }
307 *newC.mutableAs<QmlComponent>() = comp;
308 removeCurrentNode(expectedType: DomType::QmlComponent);
309 Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack");
310}
311
312bool QQmlDomAstCreator::visit(UiPragma *el)
313{
314 QStringList valueList;
315 for (auto t = el->values; t; t = t->next)
316 valueList << t->value.toString();
317
318 createMap(k: DomType::Pragma, p: qmlFilePtr->addPragma(pragma: Pragma(el->name.toString(), valueList)), n: el);
319 return true;
320}
321
322bool QQmlDomAstCreator::visit(UiImport *el)
323{
324 Version v(Version::Latest, Version::Latest);
325 if (el->version && el->version->version.hasMajorVersion())
326 v.majorVersion = el->version->version.majorVersion();
327 if (el->version && el->version->version.hasMinorVersion())
328 v.minorVersion = el->version->version.minorVersion();
329 if (el->importUri != nullptr)
330 createMap(k: DomType::Import,
331 p: qmlFilePtr->addImport(i: Import::fromUriString(importStr: toString(qualifiedId: el->importUri), v,
332 importId: el->importId.toString())),
333 n: el);
334 else
335 createMap(k: DomType::Import,
336 p: qmlFilePtr->addImport(
337 i: Import::fromFileString(importStr: el->fileName.toString(), importId: el->importId.toString())),
338 n: el);
339 return true;
340}
341
342bool QQmlDomAstCreator::visit(AST::UiPublicMember *el)
343{
344 switch (el->type) {
345 case AST::UiPublicMember::Signal: {
346 MethodInfo m;
347 m.name = el->name.toString();
348 m.typeName = toString(qualifiedId: el->memberType);
349 m.isReadonly = el->isReadonly();
350 m.access = MethodInfo::Public;
351 m.methodType = MethodInfo::Signal;
352 m.isList = el->typeModifier == QLatin1String("list");
353 MethodInfo *mPtr;
354 Path p = current<QmlObject>().addMethod(functionDef: m, option: AddOption::KeepExisting, mPtr: &mPtr);
355 pushEl(p, it: *mPtr, n: el);
356 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"signal", loc: el->propertyToken());
357 MethodInfo &mInfo = std::get<MethodInfo>(v&: currentNode().value);
358 AST::UiParameterList *args = el->parameters;
359 while (args) {
360 MethodParameter param;
361 param.name = args->name.toString();
362 param.typeName = args->type ? args->type->toString() : QString();
363 index_type idx = index_type(mInfo.parameters.size());
364 mInfo.parameters.append(t: param);
365 auto argLocs = FileLocations::ensure(base: nodeStack.last().fileLocations,
366 basePath: Path::Field(s: Fields::parameters).index(i: idx),
367 pType: AttachedInfo::PathType::Relative);
368 FileLocations::addRegion(fLoc: argLocs, locName: QString(), loc: combineLocations(n: args));
369 args = args->next;
370 }
371 break;
372 }
373 case AST::UiPublicMember::Property: {
374 PropertyDefinition p;
375 p.name = el->name.toString();
376 p.typeName = toString(qualifiedId: el->memberType);
377 p.isReadonly = el->isReadonly();
378 p.isDefaultMember = el->isDefaultMember();
379 p.isRequired = el->isRequired();
380 p.isList = el->typeModifier == QLatin1String("list");
381 if (!el->typeModifier.isEmpty())
382 p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>');
383 PropertyDefinition *pPtr;
384 Path pPathFromOwner =
385 current<QmlObject>().addPropertyDef(propertyDef: p, option: AddOption::KeepExisting, pDef: &pPtr);
386 pushEl(p: pPathFromOwner, it: *pPtr, n: el);
387 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"property",
388 loc: el->propertyToken());
389 if (p.name == u"id")
390 qmlFile.addError(msg: astParseErrors()
391 .warning(message: tr(sourceText: "id is a special attribute, that should not be "
392 "used as property name"))
393 .withPath(currentNodeEl().path));
394 if (p.isDefaultMember)
395 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"default",
396 loc: el->defaultToken());
397 if (p.isRequired)
398 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"required",
399 loc: el->requiredToken());
400 if (el->statement) {
401 BindingType bType = BindingType::Normal;
402 SourceLocation loc = combineLocations(n: el->statement);
403 QStringView code = qmlFilePtr->code();
404
405 auto script = std::make_shared<ScriptExpression>(
406 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(), args&: el->statement,
407 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::BindingExpression,
408 args&: loc);
409 Binding *bPtr;
410 Path bPathFromOwner = current<QmlObject>().addBinding(binding: Binding(p.name, script, bType),
411 option: AddOption::KeepExisting, bPtr: &bPtr);
412 FileLocations::Tree bLoc = createMap(k: DomType::Binding, p: bPathFromOwner, n: el);
413 FileLocations::addRegion(fLoc: bLoc, locName: u"colon", loc: el->colonToken);
414 FileLocations::Tree valueLoc = FileLocations::ensure(base: bLoc, basePath: Path::Field(s: Fields::value),
415 pType: AttachedInfo::PathType::Relative);
416 FileLocations::addRegion(fLoc: valueLoc, locName: QString(), loc: combineLocations(n: el->statement));
417 // push it also: its needed in endVisit to add the scriptNode to it
418 // do not use pushEl to avoid recreating the already created "bLoc" Map
419 nodeStack.append(t: { .path: bPathFromOwner, .item: *bPtr, .fileLocations: bLoc });
420 }
421 break;
422 }
423 }
424 return true;
425}
426
427void QQmlDomAstCreator::endVisit(AST::UiPublicMember *el)
428{
429 if (auto &lastEl = currentNode(); lastEl.kind == DomType::Binding) {
430 Binding &b = std::get<Binding>(v&: lastEl.value);
431 if (m_enableScriptExpressions && scriptNodeStack.size() != 1)
432 Q_SCRIPTELEMENT_DISABLE();
433 if (m_enableScriptExpressions) {
434 b.scriptExpressionValue()->setScriptElement(finalizeScriptExpression(
435 element: currentScriptNodeEl().takeVariant(), pathFromOwner: Path().field(name: Fields::scriptElement),
436 base: FileLocations::ensure(base: currentNodeEl().fileLocations,
437 basePath: Path().field(name: Fields::value))));
438 removeCurrentScriptNode(expectedType: {});
439 }
440
441 QmlObject &containingObject = current<QmlObject>();
442 Binding *bPtr =
443 valueFromMultimap(mmap&: containingObject.m_bindings, key: b.name(), idx: currentIndex());
444 Q_ASSERT(bPtr);
445 removeCurrentNode(expectedType: {});
446 }
447 Node::accept(node: el->parameters, visitor: this);
448 loadAnnotations(el);
449 if ((el->binding || el->statement)
450 && nodeStack.last().item.kind == DomType::PropertyDefinition) {
451 PropertyDefinition &pDef = std::get<PropertyDefinition>(v&: nodeStack.last().item.value);
452 if (!pDef.annotations.isEmpty()) {
453 QmlObject duplicate;
454 duplicate.setName(QLatin1String("duplicate"));
455 QmlObject &obj = current<QmlObject>();
456 auto it = obj.m_bindings.find(key: pDef.name);
457 if (it != obj.m_bindings.end()) {
458 for (QmlObject ann : pDef.annotations) {
459 ann.addAnnotation(annotation: duplicate);
460 it->addAnnotation(selfPathFromOwner: currentEl<QmlObject>()
461 .path.field(name: Fields::bindings)
462 .key(name: pDef.name)
463 .index(i: obj.m_bindings.values(key: pDef.name).size() - 1),
464 a: ann);
465 }
466 }
467 }
468 }
469 QmlObject &obj = current<QmlObject>();
470 QmlStackElement &sEl = nodeStack.last();
471 switch (sEl.item.kind) {
472 case DomType::PropertyDefinition: {
473 PropertyDefinition pDef = std::get<PropertyDefinition>(v&: sEl.item.value);
474 PropertyDefinition *pDefPtr =
475 valueFromMultimap(mmap&: obj.m_propertyDefs, key: pDef.name, idx: sEl.path.last().headIndex());
476 Q_ASSERT(pDefPtr);
477 *pDefPtr = pDef;
478 } break;
479 case DomType::MethodInfo: {
480 MethodInfo m = std::get<MethodInfo>(v&: sEl.item.value);
481 MethodInfo *mPtr = valueFromMultimap(mmap&: obj.m_methods, key: m.name, idx: sEl.path.last().headIndex());
482 Q_ASSERT(mPtr);
483 *mPtr = m;
484 } break;
485 default:
486 Q_UNREACHABLE();
487 }
488 removeCurrentNode(expectedType: {});
489}
490
491bool QQmlDomAstCreator::visit(AST::UiSourceElement *el)
492{
493 QStringView code(qmlFilePtr->code());
494 if (FunctionDeclaration *fDef = cast<FunctionDeclaration *>(ast: el->sourceElement)) {
495 MethodInfo m;
496 m.name = fDef->name.toString();
497 if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) {
498 if (AST::Type *t = tAnn->type)
499 m.typeName = typeToString(t);
500 }
501 m.access = MethodInfo::Public;
502 m.methodType = MethodInfo::Method;
503
504 SourceLocation bodyLoc = fDef->body
505 ? combineLocations(n: fDef->body)
506 : combineLocations(s1: fDef->lbraceToken, s2: fDef->rbraceToken);
507 SourceLocation methodLoc = combineLocations(n: el);
508 QStringView preCode = code.mid(pos: methodLoc.begin(), n: bodyLoc.begin() - methodLoc.begin());
509 QStringView postCode = code.mid(pos: bodyLoc.end(), n: methodLoc.end() - bodyLoc.end());
510 m.body = std::make_shared<ScriptExpression>(
511 args: code.mid(pos: bodyLoc.offset, n: bodyLoc.length), args: qmlFilePtr->engine(), args&: fDef->body,
512 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::FunctionBody, args&: bodyLoc,
513 args: 0, args&: preCode, args&: postCode);
514
515 if (fDef->typeAnnotation) {
516 SourceLocation typeLoc = combineLocations(n: fDef->typeAnnotation);
517 m.returnType = std::make_shared<ScriptExpression>(
518 args: code.mid(pos: typeLoc.offset, n: typeLoc.length), args: qmlFilePtr->engine(),
519 args&: fDef->typeAnnotation, args: qmlFilePtr->astComments(),
520 args: ScriptExpression::ExpressionType::ReturnType, args&: typeLoc, args: 0, args: u"", args: u"");
521 }
522
523 MethodInfo *mPtr;
524 Path mPathFromOwner = current<QmlObject>().addMethod(functionDef: m, option: AddOption::KeepExisting, mPtr: &mPtr);
525 pushEl(p: mPathFromOwner, it: *mPtr,
526 n: fDef); // add at the start and use the normal recursive visit?
527 FileLocations::Tree &fLoc = nodeStack.last().fileLocations;
528 auto bodyTree = FileLocations::ensure(base: fLoc, basePath: Path::Field(s: Fields::body),
529 pType: AttachedInfo::PathType::Relative);
530 FileLocations::addRegion(fLoc: bodyTree, locName: QString(), loc: bodyLoc);
531 if (fDef->lparenToken.length != 0)
532 FileLocations::addRegion(fLoc, locName: u"leftParen", loc: fDef->lparenToken);
533 if (fDef->rparenToken.length != 0)
534 FileLocations::addRegion(fLoc, locName: u"rightParen", loc: fDef->rparenToken);
535 if (fDef->lbraceToken.length != 0)
536 FileLocations::addRegion(fLoc, locName: u"leftBrace", loc: fDef->lbraceToken);
537 if (fDef->rbraceToken.length != 0)
538 FileLocations::addRegion(fLoc, locName: u"rightBrace", loc: fDef->rbraceToken);
539 loadAnnotations(el);
540 MethodInfo &mInfo = std::get<MethodInfo>(v&: currentNode().value);
541 AST::FormalParameterList *args = fDef->formals;
542 while (args) {
543 MethodParameter param;
544 param.name = args->element->bindingIdentifier.toString();
545 if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) {
546 if (AST::Type *t = tAnn->type)
547 param.typeName = typeToString(t);
548 }
549 if (args->element->initializer) {
550 SourceLocation loc = combineLocations(n: args->element->initializer);
551 auto script = std::make_shared<ScriptExpression>(
552 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(),
553 args&: args->element->initializer, args: qmlFilePtr->astComments(),
554 args: ScriptExpression::ExpressionType::ArgInitializer, args&: loc);
555 param.defaultValue = script;
556 }
557 SourceLocation parameterLoc = combineLocations(n: args->element);
558 param.value = std::make_shared<ScriptExpression>(
559 args: code.mid(pos: parameterLoc.offset, n: parameterLoc.length), args: qmlFilePtr->engine(),
560 args&: args->element, args: qmlFilePtr->astComments(),
561 args: ScriptExpression::ExpressionType::ArgumentStructure, args&: parameterLoc);
562
563 index_type idx = index_type(mInfo.parameters.size());
564 mInfo.parameters.append(t: param);
565 auto argLocs = FileLocations::ensure(base: nodeStack.last().fileLocations,
566 basePath: Path::Field(s: Fields::parameters).index(i: idx),
567 pType: AttachedInfo::PathType::Relative);
568 FileLocations::addRegion(fLoc: argLocs, locName: QString(), loc: combineLocations(n: args));
569 args = args->next;
570 }
571 return true;
572 } else {
573 qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el);
574 Q_UNREACHABLE();
575 }
576 return true;
577}
578
579static void setFormalParameterKind(ScriptElementVariant &variant)
580{
581 if (auto data = variant.data()) {
582 if (auto genericElement =
583 std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(ptr: &*data)) {
584 (*genericElement)->setKind(DomType::ScriptFormalParameter);
585 }
586 }
587}
588
589void QQmlDomAstCreator::endVisit(AST::UiSourceElement *el)
590{
591 MethodInfo &m = std::get<MethodInfo>(v&: currentNode().value);
592 if (FunctionDeclaration *fDef = cast<FunctionDeclaration *>(ast: el->sourceElement)) {
593
594 const FileLocations::Tree bodyTree =
595 FileLocations::ensure(base: currentNodeEl().fileLocations, basePath: Path().field(name: Fields::body));
596 const Path bodyPath = Path().field(name: Fields::scriptElement);
597
598 if (fDef->body) {
599 if (m_enableScriptExpressions && scriptNodeStack.isEmpty())
600 Q_SCRIPTELEMENT_DISABLE();
601 if (m_enableScriptExpressions) {
602 if (currentScriptNodeEl().isList()) {
603 // It is more intuitive to have functions with a block as a body instead of a
604 // list.
605 auto body = makeScriptElement<ScriptElements::BlockStatement>(ast: fDef->body);
606 body->setStatements(currentScriptNodeEl().takeList());
607 if (auto semanticScope = body->statements().semanticScope())
608 body->setSemanticScope(*semanticScope);
609 m.body->setScriptElement(finalizeScriptExpression(
610 element: ScriptElementVariant::fromElement(element: body), pathFromOwner: bodyPath, base: bodyTree));
611 } else {
612 m.body->setScriptElement(finalizeScriptExpression(
613 element: currentScriptNodeEl().takeVariant(), pathFromOwner: bodyPath, base: bodyTree));
614 }
615 removeCurrentScriptNode(expectedType: {});
616 }
617 }
618 if (m_enableScriptExpressions) {
619 if (fDef->typeAnnotation) {
620 auto argLoc = FileLocations::ensure(base: nodeStack.last().fileLocations,
621 basePath: Path().field(name: Fields::returnType),
622 pType: AttachedInfo::PathType::Relative);
623 const Path pathToReturnType = Path().field(name: Fields::scriptElement);
624
625 ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
626 finalizeScriptExpression(element: variant, pathFromOwner: pathToReturnType, base: argLoc);
627 m.returnType->setScriptElement(variant);
628 removeCurrentScriptNode(expectedType: {});
629 }
630 std::vector<FormalParameterList *> reversedInitializerExpressions;
631 for (auto it = fDef->formals; it; it = it->next) {
632 reversedInitializerExpressions.push_back(x: it);
633 }
634 const size_t size = reversedInitializerExpressions.size();
635 for (size_t idx = size - 1; idx < size; --idx) {
636 if (m_enableScriptExpressions && scriptNodeStack.empty()) {
637 Q_SCRIPTELEMENT_DISABLE();
638 break;
639 }
640 auto argLoc = FileLocations::ensure(
641 base: nodeStack.last().fileLocations,
642 basePath: Path().field(name: Fields::parameters).index(i: idx).field(name: Fields::value),
643 pType: AttachedInfo::PathType::Relative);
644 const Path pathToArgument = Path().field(name: Fields::scriptElement);
645
646 ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
647 setFormalParameterKind(variant);
648 finalizeScriptExpression(element: variant, pathFromOwner: pathToArgument, base: argLoc);
649 m.parameters[idx].value->setScriptElement(variant);
650 removeCurrentScriptNode(expectedType: {});
651 }
652
653 // there should be no more uncollected script elements
654 if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
655 Q_SCRIPTELEMENT_DISABLE();
656 }
657 }
658 }
659 QmlObject &obj = current<QmlObject>();
660 MethodInfo *mPtr =
661 valueFromMultimap(mmap&: obj.m_methods, key: m.name, idx: nodeStack.last().path.last().headIndex());
662 Q_ASSERT(mPtr);
663 *mPtr = m;
664 removeCurrentNode(expectedType: DomType::MethodInfo);
665}
666
667bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el)
668{
669 QmlObject scope;
670 scope.setName(toString(qualifiedId: el->qualifiedTypeNameId));
671 scope.addPrototypePath(prototypePath: Paths::lookupTypePath(name: scope.name()));
672 QmlObject *sPtr = nullptr;
673 Path sPathFromOwner;
674 if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last()) {
675 if (currentNode().kind == DomType::Binding) {
676 QList<QmlObject> *vals = std::get<Binding>(v&: currentNode().value).arrayValue();
677 if (vals) {
678 int idx = vals->size();
679 vals->append(t: scope);
680 sPathFromOwner = currentNodeEl().path.field(name: Fields::value).index(i: idx);
681 sPtr = &((*vals)[idx]);
682 sPtr->updatePathFromOwner(newPath: sPathFromOwner);
683 } else {
684 Q_ASSERT_X(false, className,
685 "expected an array binding with a valid QList<QmlScope> as value");
686 }
687 } else {
688 Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
689 }
690 } else {
691 DomValue &containingObject = currentQmlObjectOrComponentEl().item;
692 switch (containingObject.kind) {
693 case DomType::QmlComponent:
694 sPathFromOwner = std::get<QmlComponent>(v&: containingObject.value).addObject(object: scope, oPtr: &sPtr);
695 break;
696 case DomType::QmlObject:
697 sPathFromOwner = std::get<QmlObject>(v&: containingObject.value).addChild(child: scope, cPtr: &sPtr);
698 break;
699 default:
700 Q_UNREACHABLE();
701 }
702 }
703 Q_ASSERT_X(sPtr, className, "could not recover new scope");
704 pushEl(p: sPathFromOwner, it: *sPtr, n: el);
705 loadAnnotations(el);
706 return true;
707}
708
709void QQmlDomAstCreator::endVisit(AST::UiObjectDefinition *)
710{
711 QmlObject &obj = current<QmlObject>();
712 int idx = currentIndex();
713 if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last() + 1) {
714 if (currentNode(i: 1).kind == DomType::Binding) {
715 Binding &b = std::get<Binding>(v&: currentNode(i: 1).value);
716 QList<QmlObject> *vals = b.arrayValue();
717 Q_ASSERT_X(vals, className,
718 "expected an array binding with a valid QList<QmlScope> as value");
719 (*vals)[idx] = obj;
720 } else {
721 Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
722 }
723 } else {
724 DomValue &containingObject = currentNodeEl(i: 1).item;
725 Path p = currentNodeEl().path;
726 switch (containingObject.kind) {
727 case DomType::QmlComponent:
728 if (p[p.length() - 2] == Path::Field(s: Fields::objects))
729 std::get<QmlComponent>(v&: containingObject.value).m_objects[idx] = obj;
730 else
731 Q_UNREACHABLE();
732 break;
733 case DomType::QmlObject:
734 if (p[p.length() - 2] == Path::Field(s: Fields::children))
735 std::get<QmlObject>(v&: containingObject.value).m_children[idx] = obj;
736 else
737 Q_UNREACHABLE();
738 break;
739 default:
740 Q_UNREACHABLE();
741 }
742 }
743 removeCurrentNode(expectedType: DomType::QmlObject);
744}
745
746bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el)
747{
748 BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal);
749 QmlObject value;
750 value.setName(toString(qualifiedId: el->qualifiedTypeNameId));
751 Binding *bPtr;
752 Path bPathFromOwner = current<QmlObject>().addBinding(
753 binding: Binding(toString(qualifiedId: el->qualifiedId), value, bType), option: AddOption::KeepExisting, bPtr: &bPtr);
754 if (bPtr->name() == u"id")
755 qmlFile.addError(msg: astParseErrors()
756 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
757 "followed by letters, numbers or underscore, "
758 "assuming they refer to an id property"))
759 .withPath(bPathFromOwner));
760 pushEl(p: bPathFromOwner, it: *bPtr, n: el);
761 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"colon", loc: el->colonToken);
762 loadAnnotations(el);
763 QmlObject *objValue = bPtr->objectValue();
764 Q_ASSERT_X(objValue, className, "could not recover objectValue");
765 objValue->setName(toString(qualifiedId: el->qualifiedTypeNameId));
766 objValue->addPrototypePath(prototypePath: Paths::lookupTypePath(name: objValue->name()));
767 pushEl(p: bPathFromOwner.field(name: Fields::value), it: *objValue, n: el->initializer);
768 return true;
769}
770
771void QQmlDomAstCreator::endVisit(AST::UiObjectBinding *)
772{
773 QmlObject &objValue = current<QmlObject>();
774 QmlObject &containingObj = current<QmlObject>(idx: 1);
775 Binding &b = std::get<Binding>(v&: currentNode(i: 1).value);
776 QmlObject *objPtr = b.objectValue();
777 Q_ASSERT(objPtr);
778 *objPtr = objValue;
779 index_type idx = currentNodeEl(i: 1).path.last().headIndex();
780 Binding *bPtr = valueFromMultimap(mmap&: containingObj.m_bindings, key: b.name(), idx);
781 Q_ASSERT(bPtr);
782 *bPtr = b;
783 removeCurrentNode(expectedType: DomType::QmlObject);
784 removeCurrentNode(expectedType: DomType::Binding);
785}
786
787bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el)
788{
789 QStringView code = qmlFilePtr->code();
790 SourceLocation loc = combineLocations(n: el->statement);
791 auto script = std::make_shared<ScriptExpression>(
792 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(), args&: el->statement,
793 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::BindingExpression, args&: loc);
794 Binding bindingV(toString(qualifiedId: el->qualifiedId), script, BindingType::Normal);
795 Binding *bindingPtr = nullptr;
796 Id *idPtr = nullptr;
797 Path pathFromOwner;
798 if (bindingV.name() == u"id") {
799 Node *exp = script->ast();
800 if (ExpressionStatement *eStat = cast<ExpressionStatement *>(ast: script->ast()))
801 exp = eStat->expression;
802 if (IdentifierExpression *iExp = cast<IdentifierExpression *>(ast: exp)) {
803 QmlStackElement &containingObjectEl = currentEl<QmlObject>();
804 QmlObject &containingObject = std::get<QmlObject>(v&: containingObjectEl.item.value);
805 QString idName = iExp->name.toString();
806 Id idVal(idName, qmlFile.canonicalPath().path(toAdd: containingObject.pathFromOwner()));
807 idVal.value = script;
808 containingObject.setIdStr(idName);
809 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, locName: u"idToken",
810 loc: combineLocations(n: el->qualifiedId));
811 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, locName: u"idColon", loc: el->colonToken);
812 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, locName: u"id",
813 loc: combineLocations(n: el->statement));
814 QmlComponent &comp = current<QmlComponent>();
815 pathFromOwner = comp.addId(id: idVal, option: AddOption::KeepExisting, idPtr: &idPtr);
816 QRegularExpression idRe(QRegularExpression::anchoredPattern(
817 QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)")));
818 auto m = idRe.matchView(subjectView: iExp->name);
819 if (!m.hasMatch()) {
820 qmlFile.addError(
821 msg: astParseErrors()
822 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
823 "followed by letters, numbers or underscore, not %1")
824 .arg(a: iExp->name))
825 .withPath(pathFromOwner));
826 }
827 } else {
828 pathFromOwner =
829 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
830 Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
831 qmlFile.addError(
832 msg: astParseErrors()
833 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
834 "followed by letters, numbers or underscore, not %1 "
835 "%2, assuming they refer to a property")
836 .arg(args: script->code(), args: script->astRelocatableDump()))
837 .withPath(pathFromOwner));
838 }
839 } else {
840 pathFromOwner =
841 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
842 Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
843 }
844 if (bindingPtr)
845 pushEl(p: pathFromOwner, it: *bindingPtr, n: el);
846 else if (idPtr)
847 pushEl(p: pathFromOwner, it: *idPtr, n: el);
848 else
849 Q_UNREACHABLE();
850 loadAnnotations(el);
851 // avoid duplicate colon location for id?
852 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"colon", loc: el->colonToken);
853 return true;
854}
855
856void QQmlDomAstCreator::setScriptExpression (const std::shared_ptr<ScriptExpression>& value)
857{
858 if (m_enableScriptExpressions
859 && (scriptNodeStack.size() != 1 || currentScriptNodeEl().isList()))
860 Q_SCRIPTELEMENT_DISABLE();
861 if (m_enableScriptExpressions) {
862 FileLocations::Tree valueLoc = FileLocations::ensure(base: currentNodeEl().fileLocations,
863 basePath: Path().field(name: Fields::value));
864 value->setScriptElement(finalizeScriptExpression(element: currentScriptNodeEl().takeVariant(),
865 pathFromOwner: Path().field(name: Fields::scriptElement),
866 base: valueLoc));
867 removeCurrentScriptNode(expectedType: {});
868 }
869};
870
871void QQmlDomAstCreator::endVisit(AST::UiScriptBinding *)
872{
873 DomValue &lastEl = currentNode();
874 index_type idx = currentIndex();
875 if (lastEl.kind == DomType::Binding) {
876 Binding &b = std::get<Binding>(v&: lastEl.value);
877
878 setScriptExpression(b.scriptExpressionValue());
879
880 QmlObject &containingObject = current<QmlObject>();
881 Binding *bPtr = valueFromMultimap(mmap&: containingObject.m_bindings, key: b.name(), idx);
882 Q_ASSERT(bPtr);
883 *bPtr = b;
884 } else if (lastEl.kind == DomType::Id) {
885 Id &id = std::get<Id>(v&: lastEl.value);
886
887 setScriptExpression(id.value);
888
889 QmlComponent &comp = current<QmlComponent>();
890 Id *idPtr = valueFromMultimap(mmap&: comp.m_ids, key: id.name, idx);
891 *idPtr = id;
892 } else {
893 Q_UNREACHABLE();
894 }
895
896 // there should be no more uncollected script elements
897 if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
898 Q_SCRIPTELEMENT_DISABLE();
899 }
900 removeCurrentNode(expectedType: {});
901}
902
903bool QQmlDomAstCreator::visit(AST::UiArrayBinding *el)
904{
905 QList<QmlObject> value;
906 Binding bindingV(toString(qualifiedId: el->qualifiedId), value, BindingType::Normal);
907 Binding *bindingPtr;
908 Path bindingPathFromOwner =
909 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
910 if (bindingV.name() == u"id")
911 qmlFile.addError(
912 msg: astParseErrors()
913 .error(message: tr(sourceText: "id attributes should have only simple strings as values"))
914 .withPath(bindingPathFromOwner));
915 pushEl(p: bindingPathFromOwner, it: *bindingPtr, n: el);
916 FileLocations::addRegion(fLoc: currentNodeEl().fileLocations, locName: u"colon", loc: el->colonToken);
917 loadAnnotations(el);
918 FileLocations::Tree arrayList =
919 createMap(base: currentNodeEl().fileLocations, p: Path::Field(s: Fields::value), n: nullptr);
920 FileLocations::addRegion(fLoc: arrayList, locName: u"leftSquareBrace", loc: el->lbracketToken);
921 FileLocations::addRegion(fLoc: arrayList, locName: u"rightSquareBrace", loc: el->lbracketToken);
922 arrayBindingLevels.append(t: nodeStack.size());
923 return true;
924}
925
926void QQmlDomAstCreator::endVisit(AST::UiArrayBinding *)
927{
928 index_type idx = currentIndex();
929 Binding &b = std::get<Binding>(v&: currentNode().value);
930 Binding *bPtr = valueFromMultimap(mmap&: current<QmlObject>().m_bindings, key: b.name(), idx);
931 *bPtr = b;
932 arrayBindingLevels.removeLast();
933 removeCurrentNode(expectedType: DomType::Binding);
934}
935
936bool QQmlDomAstCreator::visit(AST::ArgumentList *list)
937{
938 if (!m_enableScriptExpressions)
939 return false;
940
941 auto currentList = makeScriptList(ast: list);
942
943 for (auto it = list; it; it = it->next) {
944 Node::accept(node: it->expression, visitor: this);
945 if (!m_enableScriptExpressions)
946 return false;
947
948 if (scriptNodeStack.empty()) {
949 Q_SCRIPTELEMENT_DISABLE();
950 return false;
951 }
952 currentList.append(statement: scriptNodeStack.last().takeVariant());
953 scriptNodeStack.removeLast();
954 }
955
956 pushScriptElement(element: currentList);
957
958 return false; // return false because we already iterated over the children using the custom
959 // iteration above
960}
961
962bool QQmlDomAstCreator::visit(AST::UiParameterList *)
963{
964 return false; // do not create script node for Ui stuff
965}
966
967bool QQmlDomAstCreator::visit(AST::PatternElementList *list)
968{
969 if (!m_enableScriptExpressions)
970 return false;
971
972 auto currentList = makeScriptList(ast: list);
973
974 for (auto it = list; it; it = it->next) {
975 if (it->elision) {
976 Node::accept(node: it->elision, visitor: this);
977 if (scriptNodeStack.empty()) {
978 Q_SCRIPTELEMENT_DISABLE();
979 return false;
980 }
981 currentList.append(list: scriptNodeStack.last().takeList());
982 scriptNodeStack.removeLast();
983 }
984 if (it->element) {
985 Node::accept(node: it->element, visitor: this);
986 if (scriptNodeStack.empty()) {
987 Q_SCRIPTELEMENT_DISABLE();
988 return false;
989 }
990 currentList.append(statement: scriptNodeStack.last().takeVariant());
991 scriptNodeStack.removeLast();
992 }
993 }
994
995 pushScriptElement(element: currentList);
996
997 return false; // return false because we already iterated over the children using the custom
998 // iteration above
999}
1000
1001bool QQmlDomAstCreator::visit(AST::PatternPropertyList *list)
1002{
1003 if (!m_enableScriptExpressions)
1004 return false;
1005
1006 auto currentList = makeScriptList(ast: list);
1007
1008 for (auto it = list; it; it = it->next) {
1009 if (it->property) {
1010 Node::accept(node: it->property, visitor: this);
1011 if (!m_enableScriptExpressions)
1012 return false;
1013 if (scriptNodeStack.empty()) {
1014 Q_SCRIPTELEMENT_DISABLE();
1015 return false;
1016 }
1017 currentList.append(statement: scriptNodeStack.last().takeVariant());
1018 scriptNodeStack.removeLast();
1019 }
1020 }
1021
1022 pushScriptElement(element: currentList);
1023
1024 return false; // return false because we already iterated over the children using the custom
1025 // iteration above
1026}
1027
1028/*!
1029 \internal
1030 Implementing the logic of this method in \c QQmlDomAstCreator::visit(AST::UiQualifiedId *)
1031 would create scriptelements at places where there are not needed. This is mainly because
1032 UiQualifiedId's appears inside and outside of script parts.
1033*/
1034ScriptElementVariant QQmlDomAstCreator::scriptElementForQualifiedId(AST::UiQualifiedId *expression)
1035{
1036 auto id = std::make_shared<ScriptElements::IdentifierExpression>(
1037 args: expression->firstSourceLocation(), args: expression->lastSourceLocation());
1038 id->setName(expression->toString());
1039
1040 return ScriptElementVariant::fromElement(element: id);
1041}
1042
1043bool QQmlDomAstCreator::visit(AST::UiQualifiedId *)
1044{
1045 if (!m_enableScriptExpressions)
1046 return false;
1047
1048 return false;
1049}
1050
1051bool QQmlDomAstCreator::visit(AST::UiEnumDeclaration *el)
1052{
1053 EnumDecl eDecl;
1054 eDecl.setName(el->name.toString());
1055 EnumDecl *ePtr;
1056 Path enumPathFromOwner =
1057 current<QmlComponent>().addEnumeration(enumeration: eDecl, option: AddOption::KeepExisting, ePtr: &ePtr);
1058 pushEl(p: enumPathFromOwner, it: *ePtr, n: el);
1059 loadAnnotations(el);
1060 return true;
1061}
1062
1063void QQmlDomAstCreator::endVisit(AST::UiEnumDeclaration *)
1064{
1065 EnumDecl &e = std::get<EnumDecl>(v&: currentNode().value);
1066 EnumDecl *ePtr =
1067 valueFromMultimap(mmap&: current<QmlComponent>().m_enumerations, key: e.name(), idx: currentIndex());
1068 Q_ASSERT(ePtr);
1069 *ePtr = e;
1070 removeCurrentNode(expectedType: DomType::EnumDecl);
1071}
1072
1073bool QQmlDomAstCreator::visit(AST::UiEnumMemberList *el)
1074{
1075 EnumItem it(el->member.toString(), el->value);
1076 EnumDecl &eDecl = std::get<EnumDecl>(v&: currentNode().value);
1077 Path itPathFromDecl = eDecl.addValue(value: it);
1078 FileLocations::addRegion(fLoc: createMap(k: DomType::EnumItem, p: itPathFromDecl, n: nullptr), locName: QString(),
1079 loc: combine(l1: el->memberToken, l2: el->valueToken));
1080 return true;
1081}
1082
1083void QQmlDomAstCreator::endVisit(AST::UiEnumMemberList *el)
1084{
1085 Node::accept(node: el->next, visitor: this); // put other enum members at the same level as this one...
1086}
1087
1088bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el)
1089{
1090 QStringList els = current<QmlComponent>().name().split(sep: QLatin1Char('.'));
1091 els.append(t: el->name.toString());
1092 QString cName = els.join(sep: QLatin1Char('.'));
1093 QmlComponent *compPtr;
1094 Path p = qmlFilePtr->addComponent(component: QmlComponent(cName), option: AddOption::KeepExisting, cPtr: &compPtr);
1095 pushEl(p, it: *compPtr, n: el);
1096 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, locName: u"component", loc: el->componentToken);
1097 loadAnnotations(el);
1098 return true;
1099}
1100
1101void QQmlDomAstCreator::endVisit(AST::UiInlineComponent *)
1102{
1103 QmlComponent &component = std::get<QmlComponent>(v&: currentNode().value);
1104 QStringList nameEls = component.name().split(sep: QChar::fromLatin1(c: '.'));
1105 QString key = nameEls.mid(pos: 1).join(sep: QChar::fromLatin1(c: '.'));
1106 QmlComponent *cPtr = valueFromMultimap(mmap&: qmlFilePtr->m_components, key, idx: currentIndex());
1107 Q_ASSERT(cPtr);
1108 *cPtr = component;
1109 removeCurrentNode(expectedType: DomType::QmlComponent);
1110}
1111
1112bool QQmlDomAstCreator::visit(UiRequired *el)
1113{
1114 PropertyDefinition pDef;
1115 pDef.name = el->name.toString();
1116 pDef.isRequired = true;
1117 PropertyDefinition *pDefPtr;
1118 Path pathFromOwner =
1119 current<QmlObject>().addPropertyDef(propertyDef: pDef, option: AddOption::KeepExisting, pDef: &pDefPtr);
1120 createMap(k: DomType::PropertyDefinition, p: pathFromOwner, n: el);
1121 return false;
1122}
1123
1124bool QQmlDomAstCreator::visit(AST::UiAnnotation *el)
1125{
1126 QmlObject a;
1127 a.setName(QStringLiteral(u"@") + toString(qualifiedId: el->qualifiedTypeNameId));
1128 // add annotation prototype?
1129 DomValue &containingElement = currentNode();
1130 Path pathFromOwner;
1131 QmlObject *aPtr = nullptr;
1132 switch (containingElement.kind) {
1133 case DomType::QmlObject:
1134 pathFromOwner = std::get<QmlObject>(v&: containingElement.value).addAnnotation(annotation: a, aPtr: &aPtr);
1135 break;
1136 case DomType::Binding:
1137 pathFromOwner = std::get<Binding>(v&: containingElement.value)
1138 .addAnnotation(selfPathFromOwner: currentNodeEl().path, a, aPtr: &aPtr);
1139 break;
1140 case DomType::Id:
1141 pathFromOwner =
1142 std::get<Id>(v&: containingElement.value).addAnnotation(selfPathFromOwner: currentNodeEl().path, ann: a, aPtr: &aPtr);
1143 break;
1144 case DomType::PropertyDefinition:
1145 pathFromOwner = std::get<PropertyDefinition>(v&: containingElement.value)
1146 .addAnnotation(selfPathFromOwner: currentNodeEl().path, annotation: a, aPtr: &aPtr);
1147 break;
1148 case DomType::MethodInfo:
1149 pathFromOwner = std::get<MethodInfo>(v&: containingElement.value)
1150 .addAnnotation(selfPathFromOwner: currentNodeEl().path, annotation: a, aPtr: &aPtr);
1151 break;
1152 default:
1153 qCWarning(domLog) << "Unexpected container object for annotation:"
1154 << domTypeToString(k: containingElement.kind);
1155 Q_UNREACHABLE();
1156 }
1157 pushEl(p: pathFromOwner, it: *aPtr, n: el);
1158 return true;
1159}
1160
1161void QQmlDomAstCreator::endVisit(AST::UiAnnotation *)
1162{
1163 DomValue &containingElement = currentNode(i: 1);
1164 Path pathFromOwner;
1165 QmlObject &a = std::get<QmlObject>(v&: currentNode().value);
1166 switch (containingElement.kind) {
1167 case DomType::QmlObject:
1168 std::get<QmlObject>(v&: containingElement.value).m_annotations[currentIndex()] = a;
1169 break;
1170 case DomType::Binding:
1171 std::get<Binding>(v&: containingElement.value).m_annotations[currentIndex()] = a;
1172 break;
1173 case DomType::Id:
1174 std::get<Id>(v&: containingElement.value).annotations[currentIndex()] = a;
1175 break;
1176 case DomType::PropertyDefinition:
1177 std::get<PropertyDefinition>(v&: containingElement.value).annotations[currentIndex()] = a;
1178 break;
1179 case DomType::MethodInfo:
1180 std::get<MethodInfo>(v&: containingElement.value).annotations[currentIndex()] = a;
1181 break;
1182 default:
1183 Q_UNREACHABLE();
1184 }
1185 removeCurrentNode(expectedType: DomType::QmlObject);
1186}
1187
1188void QQmlDomAstCreator::throwRecursionDepthError()
1189{
1190 qmlFile.addError(msg: astParseErrors().error(
1191 message: tr(sourceText: "Maximum statement or expression depth exceeded in QmlDomAstCreator")));
1192}
1193
1194bool QQmlDomAstCreator::visit(AST::StatementList *)
1195{
1196 if (!m_enableScriptExpressions)
1197 return false;
1198
1199 return true;
1200}
1201
1202void QQmlDomAstCreator::endVisit(AST::StatementList *list)
1203{
1204 if (!m_enableScriptExpressions)
1205 return;
1206
1207 auto current = makeScriptList(ast: list);
1208
1209 for (auto it = list; it; it = it->next) {
1210 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1211 current.append(statement: scriptNodeStack.takeLast().takeVariant());
1212 }
1213
1214 current.reverse();
1215 pushScriptElement(element: current);
1216}
1217
1218bool QQmlDomAstCreator::visit(AST::BinaryExpression *)
1219{
1220 if (!m_enableScriptExpressions)
1221 return false;
1222
1223 return true;
1224}
1225
1226void QQmlDomAstCreator::endVisit(AST::BinaryExpression *exp)
1227{
1228 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.size() < 2);
1229
1230 if (!m_enableScriptExpressions)
1231 return;
1232
1233 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: exp);
1234 current->setRight(currentScriptNodeEl().takeVariant());
1235 removeCurrentScriptNode(expectedType: {});
1236 current->setLeft(currentScriptNodeEl().takeVariant());
1237 removeCurrentScriptNode(expectedType: {});
1238
1239 pushScriptElement(element: current);
1240}
1241
1242bool QQmlDomAstCreator::visit(AST::Block *)
1243{
1244 if (!m_enableScriptExpressions)
1245 return false;
1246
1247 return true;
1248}
1249
1250void QQmlDomAstCreator::endVisit(AST::Block *block)
1251{
1252 if (!m_enableScriptExpressions)
1253 return;
1254
1255 auto current = makeScriptElement<ScriptElements::BlockStatement>(ast: block);
1256
1257 if (block->statements) {
1258 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1259 current->setStatements(currentScriptNodeEl().takeList());
1260 removeCurrentScriptNode(expectedType: DomType::List);
1261 }
1262
1263 pushScriptElement(element: current);
1264}
1265
1266bool QQmlDomAstCreator::visit(AST::ForStatement *)
1267{
1268 if (!m_enableScriptExpressions)
1269 return false;
1270
1271 return true;
1272}
1273
1274void QQmlDomAstCreator::endVisit(AST::ForStatement *forStatement)
1275{
1276 if (!m_enableScriptExpressions)
1277 return;
1278
1279 auto current = makeScriptElement<ScriptElements::ForStatement>(ast: forStatement);
1280
1281 if (forStatement->statement) {
1282 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
1283 current->setBody(currentScriptNodeEl().takeVariant());
1284 removeCurrentScriptNode(expectedType: std::nullopt);
1285 }
1286
1287 if (forStatement->expression) {
1288 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
1289 current->setExpression(currentScriptNodeEl().takeVariant());
1290 removeCurrentScriptNode(expectedType: std::nullopt);
1291 }
1292
1293 if (forStatement->condition) {
1294 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
1295 current->setCondition(currentScriptNodeEl().takeVariant());
1296 removeCurrentScriptNode(expectedType: std::nullopt);
1297 }
1298
1299 if (forStatement->declarations) {
1300 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || !scriptNodeStack.last().isList());
1301 auto variableDeclaration = makeGenericScriptElement(ast: forStatement->declarations,
1302 kind: DomType::ScriptVariableDeclaration);
1303
1304 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
1305 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern,
1306 newType: DomType::ScriptVariableDeclarationEntry);
1307 variableDeclaration->insertChild(name: Fields::declarations, v: std::move(list));
1308 removeCurrentScriptNode(expectedType: {});
1309
1310 current->setDeclarations(ScriptElementVariant::fromElement(element: variableDeclaration));
1311 }
1312
1313 if (forStatement->initialiser) {
1314 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() || scriptNodeStack.last().isList());
1315 current->setInitializer(currentScriptNodeEl().takeVariant());
1316 removeCurrentScriptNode(expectedType: std::nullopt);
1317 }
1318 pushScriptElement(element: current);
1319}
1320
1321bool QQmlDomAstCreator::visit(AST::IdentifierExpression *expression)
1322{
1323 if (!m_enableScriptExpressions)
1324 return false;
1325
1326 auto current = makeScriptElement<ScriptElements::IdentifierExpression>(ast: expression);
1327 current->setName(expression->name);
1328 pushScriptElement(element: current);
1329 return true;
1330}
1331
1332bool QQmlDomAstCreator::visit(AST::NumericLiteral *expression)
1333{
1334 if (!m_enableScriptExpressions)
1335 return false;
1336
1337 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1338 current->setLiteralValue(expression->value);
1339 pushScriptElement(element: current);
1340 return true;
1341}
1342
1343bool QQmlDomAstCreator::visit(AST::StringLiteral *expression)
1344{
1345 if (!m_enableScriptExpressions)
1346 return false;
1347
1348 pushScriptElement(element: makeStringLiteral(value: expression->value, ast: expression));
1349 return true;
1350}
1351
1352bool QQmlDomAstCreator::visit(AST::NullExpression *expression)
1353{
1354 if (!m_enableScriptExpressions)
1355 return false;
1356
1357 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1358 current->setLiteralValue(nullptr);
1359 pushScriptElement(element: current);
1360 return true;
1361}
1362
1363bool QQmlDomAstCreator::visit(AST::TrueLiteral *expression)
1364{
1365 if (!m_enableScriptExpressions)
1366 return false;
1367
1368 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1369 current->setLiteralValue(true);
1370 pushScriptElement(element: current);
1371 return true;
1372}
1373
1374bool QQmlDomAstCreator::visit(AST::FalseLiteral *expression)
1375{
1376 if (!m_enableScriptExpressions)
1377 return false;
1378
1379 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1380 current->setLiteralValue(false);
1381 pushScriptElement(element: current);
1382 return true;
1383}
1384
1385bool QQmlDomAstCreator::visit(AST::IdentifierPropertyName *expression)
1386{
1387 if (!m_enableScriptExpressions)
1388 return false;
1389
1390 auto current = makeScriptElement<ScriptElements::IdentifierExpression>(ast: expression);
1391 current->setName(expression->id);
1392 pushScriptElement(element: current);
1393 return true;
1394}
1395
1396bool QQmlDomAstCreator::visit(AST::StringLiteralPropertyName *expression)
1397{
1398 if (!m_enableScriptExpressions)
1399 return false;
1400
1401 pushScriptElement(element: makeStringLiteral(value: expression->id, ast: expression));
1402 return true;
1403}
1404
1405bool QQmlDomAstCreator::visit(AST::TypeAnnotation *)
1406{
1407 if (!m_enableScriptExpressions)
1408 return false;
1409
1410 // do nothing: the work is done in (end)visit(AST::Type*).
1411 return true;
1412}
1413
1414bool QQmlDomAstCreator::visit(AST::NumericLiteralPropertyName *expression)
1415{
1416 if (!m_enableScriptExpressions)
1417 return false;
1418
1419 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1420 current->setLiteralValue(expression->id);
1421 pushScriptElement(element: current);
1422 return true;
1423}
1424
1425bool QQmlDomAstCreator::visit(AST::ComputedPropertyName *)
1426{
1427 if (!m_enableScriptExpressions)
1428 return false;
1429
1430 // nothing to do, just forward the underlying expression without changing/wrapping it
1431 return true;
1432}
1433
1434bool QQmlDomAstCreator::visit(AST::VariableDeclarationList *list)
1435{
1436 if (!m_enableScriptExpressions)
1437 return false;
1438
1439 auto currentList = makeScriptList(ast: list);
1440
1441 for (auto it = list; it; it = it->next) {
1442 if (it->declaration) {
1443 Node::accept(node: it->declaration, visitor: this);
1444 if (!m_enableScriptExpressions)
1445 return false;
1446 if (scriptNodeStack.empty()) {
1447 Q_SCRIPTELEMENT_DISABLE();
1448 return false;
1449 }
1450 currentList.append(statement: scriptNodeStack.last().takeVariant());
1451 scriptNodeStack.removeLast();
1452 }
1453 }
1454 pushScriptElement(element: currentList);
1455
1456 return false; // return false because we already iterated over the children using the custom
1457 // iteration above
1458}
1459
1460bool QQmlDomAstCreator::visit(AST::Elision *list)
1461{
1462 if (!m_enableScriptExpressions)
1463 return false;
1464
1465 auto currentList = makeScriptList(ast: list);
1466
1467 for (auto it = list; it; it = it->next) {
1468 auto current = makeGenericScriptElement(location: it->commaToken, kind: DomType::ScriptElision);
1469 currentList.append(statement: ScriptElementVariant::fromElement(element: current));
1470 }
1471 pushScriptElement(element: currentList);
1472
1473 return false; // return false because we already iterated over the children using the custom
1474 // iteration above
1475}
1476
1477bool QQmlDomAstCreator::visit(AST::PatternElement *)
1478{
1479 if (!m_enableScriptExpressions)
1480 return false;
1481
1482 return true;
1483}
1484
1485/*!
1486 \internal
1487 Avoid code-duplication, reuse this code when doing endVisit on types inheriting from
1488 AST::PatternElement.
1489*/
1490void QQmlDomAstCreator::endVisitHelper(
1491 AST::PatternElement *pe,
1492 const std::shared_ptr<ScriptElements::GenericScriptElement> &current)
1493{
1494 if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) {
1495 auto identifier =
1496 std::make_shared<ScriptElements::IdentifierExpression>(args&: pe->identifierToken);
1497 identifier->setName(pe->bindingIdentifier);
1498 current->insertChild(name: Fields::identifier, v: ScriptElementVariant::fromElement(element: identifier));
1499 }
1500 if (pe->initializer) {
1501 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1502 current->insertChild(name: Fields::initializer, v: scriptNodeStack.last().takeVariant());
1503 scriptNodeStack.removeLast();
1504 }
1505 if (pe->typeAnnotation) {
1506 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1507 current->insertChild(name: Fields::type, v: scriptNodeStack.last().takeVariant());
1508 scriptNodeStack.removeLast();
1509 }
1510 if (pe->bindingTarget) {
1511 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1512 current->insertChild(name: Fields::bindingElement, v: scriptNodeStack.last().takeVariant());
1513 scriptNodeStack.removeLast();
1514 }
1515}
1516
1517void QQmlDomAstCreator::endVisit(AST::PatternElement *pe)
1518{
1519 if (!m_enableScriptExpressions)
1520 return;
1521
1522 auto element = makeGenericScriptElement(ast: pe, kind: DomType::ScriptPattern);
1523 endVisitHelper(pe, current: element);
1524 // check if helper disabled scriptexpressions
1525 if (!m_enableScriptExpressions)
1526 return;
1527
1528 pushScriptElement(element);
1529}
1530
1531bool QQmlDomAstCreator::visit(AST::IfStatement *)
1532{
1533 if (!m_enableScriptExpressions)
1534 return false;
1535
1536 return true;
1537}
1538
1539void QQmlDomAstCreator::endVisit(AST::IfStatement *ifStatement)
1540{
1541 if (!m_enableScriptExpressions)
1542 return;
1543
1544 auto current = makeScriptElement<ScriptElements::IfStatement>(ast: ifStatement);
1545
1546 if (ifStatement->ko) {
1547 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1548 current->setAlternative(scriptNodeStack.last().takeVariant());
1549 scriptNodeStack.removeLast();
1550 }
1551
1552 if (ifStatement->ok) {
1553 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1554 current->setConsequence(scriptNodeStack.last().takeVariant());
1555 scriptNodeStack.removeLast();
1556 }
1557 if (ifStatement->expression) {
1558 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1559 current->setCondition(scriptNodeStack.last().takeVariant());
1560 scriptNodeStack.removeLast();
1561 }
1562
1563 pushScriptElement(element: current);
1564}
1565
1566bool QQmlDomAstCreator::visit(AST::ReturnStatement *)
1567{
1568 if (!m_enableScriptExpressions)
1569 return false;
1570
1571 return true;
1572}
1573
1574void QQmlDomAstCreator::endVisit(AST::ReturnStatement *returnStatement)
1575{
1576 if (!m_enableScriptExpressions)
1577 return;
1578
1579 auto current = makeScriptElement<ScriptElements::ReturnStatement>(ast: returnStatement);
1580
1581 if (returnStatement->expression) {
1582 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1583 current->setExpression(currentScriptNodeEl().takeVariant());
1584 removeCurrentScriptNode(expectedType: {});
1585 }
1586
1587 pushScriptElement(element: current);
1588}
1589
1590bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *)
1591{
1592 if (!m_enableScriptExpressions)
1593 return false;
1594
1595 return true;
1596}
1597
1598void QQmlDomAstCreator::endVisit(AST::FieldMemberExpression *expression)
1599{
1600 if (!m_enableScriptExpressions)
1601 return;
1602
1603 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: expression);
1604 current->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
1605
1606 if (expression->base) {
1607 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1608 current->setLeft(currentScriptNodeEl().takeVariant());
1609 removeCurrentScriptNode(expectedType: {});
1610 }
1611
1612 if (!expression->name.empty()) {
1613 auto scriptIdentifier =
1614 std::make_shared<ScriptElements::IdentifierExpression>(args&: expression->identifierToken);
1615 scriptIdentifier->setName(expression->name);
1616 current->setRight(ScriptElementVariant::fromElement(element: scriptIdentifier));
1617 }
1618
1619 pushScriptElement(element: current);
1620}
1621
1622bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *)
1623{
1624 if (!m_enableScriptExpressions)
1625 return false;
1626
1627 return true;
1628}
1629
1630void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression)
1631{
1632 if (!m_enableScriptExpressions)
1633 return;
1634
1635 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: expression);
1636 current->setOp(ScriptElements::BinaryExpression::ArrayMemberAccess);
1637
1638 if (expression->expression) {
1639 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1640 // if scriptNodeStack.last() is fieldmember expression, add expression to it instead of
1641 // creating new one
1642 current->setRight(currentScriptNodeEl().takeVariant());
1643 removeCurrentScriptNode(expectedType: {});
1644 }
1645
1646 if (expression->base) {
1647 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1648 current->setLeft(currentScriptNodeEl().takeVariant());
1649 removeCurrentScriptNode(expectedType: {});
1650 }
1651
1652 pushScriptElement(element: current);
1653}
1654
1655bool QQmlDomAstCreator::visit(AST::CallExpression *)
1656{
1657 if (!m_enableScriptExpressions)
1658 return false;
1659
1660 return true;
1661}
1662
1663void QQmlDomAstCreator::endVisit(AST::CallExpression *exp)
1664{
1665 if (!m_enableScriptExpressions)
1666 return;
1667
1668 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptCallExpression);
1669
1670 if (exp->arguments) {
1671 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1672 current->insertChild(name: Fields::arguments, v: currentScriptNodeEl().takeList());
1673 removeCurrentScriptNode(expectedType: {});
1674 } else {
1675 // insert empty list
1676 current->insertChild(name: Fields::arguments,
1677 v: ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken));
1678 }
1679
1680 if (exp->base) {
1681 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1682 current->insertChild(name: Fields::callee, v: currentScriptNodeEl().takeVariant());
1683 removeCurrentScriptNode(expectedType: {});
1684 }
1685
1686 pushScriptElement(element: current);
1687}
1688
1689bool QQmlDomAstCreator::visit(AST::ArrayPattern *)
1690{
1691 if (!m_enableScriptExpressions)
1692 return false;
1693
1694 return true;
1695}
1696
1697void QQmlDomAstCreator::endVisit(AST::ArrayPattern *exp)
1698{
1699 if (!m_enableScriptExpressions)
1700 return;
1701
1702 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptArray);
1703
1704 if (exp->elements) {
1705 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1706 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
1707 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern, newType: DomType::ScriptArrayEntry);
1708 current->insertChild(name: Fields::elements, v: std::move(list));
1709
1710 removeCurrentScriptNode(expectedType: {});
1711 } else {
1712 // insert empty list
1713 current->insertChild(name: Fields::elements,
1714 v: ScriptElements::ScriptList(exp->lbracketToken, exp->rbracketToken));
1715 }
1716
1717 pushScriptElement(element: current);
1718}
1719
1720bool QQmlDomAstCreator::visit(AST::ObjectPattern *)
1721{
1722 if (!m_enableScriptExpressions)
1723 return false;
1724
1725 return true;
1726}
1727
1728void QQmlDomAstCreator::endVisit(AST::ObjectPattern *exp)
1729{
1730 if (!m_enableScriptExpressions)
1731 return;
1732
1733 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptObject);
1734
1735 if (exp->properties) {
1736 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1737 current->insertChild(name: Fields::properties, v: currentScriptNodeEl().takeList());
1738 removeCurrentScriptNode(expectedType: {});
1739 } else {
1740 // insert empty list
1741 current->insertChild(name: Fields::properties,
1742 v: ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken));
1743 }
1744
1745 pushScriptElement(element: current);
1746}
1747
1748bool QQmlDomAstCreator::visit(AST::PatternProperty *)
1749{
1750 if (!m_enableScriptExpressions)
1751 return false;
1752
1753 return true;
1754}
1755
1756void QQmlDomAstCreator::endVisit(AST::PatternProperty *exp)
1757{
1758 if (!m_enableScriptExpressions)
1759 return;
1760
1761 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptProperty);
1762
1763 // handle the stuff from PatternProperty's base class PatternElement
1764 endVisitHelper(pe: static_cast<PatternElement *>(exp), current);
1765
1766 // check if helper disabled scriptexpressions
1767 if (!m_enableScriptExpressions)
1768 return;
1769
1770 if (exp->name) {
1771 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1772 current->insertChild(name: Fields::name, v: currentScriptNodeEl().takeVariant());
1773 removeCurrentScriptNode(expectedType: {});
1774 }
1775
1776 pushScriptElement(element: current);
1777}
1778
1779bool QQmlDomAstCreator::visit(AST::VariableStatement *)
1780{
1781 if (!m_enableScriptExpressions)
1782 return false;
1783
1784 return true;
1785}
1786
1787void QQmlDomAstCreator::endVisit(AST::VariableStatement *statement)
1788{
1789 if (!m_enableScriptExpressions)
1790 return;
1791
1792 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptVariableDeclaration);
1793
1794 if (statement->declarations) {
1795 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1796
1797 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
1798 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern,
1799 newType: DomType::ScriptVariableDeclarationEntry);
1800 current->insertChild(name: Fields::declarations, v: std::move(list));
1801
1802 removeCurrentScriptNode(expectedType: {});
1803 }
1804
1805 pushScriptElement(element: current);
1806}
1807
1808bool QQmlDomAstCreator::visit(AST::Type *)
1809{
1810 if (!m_enableScriptExpressions)
1811 return false;
1812
1813 return true;
1814}
1815
1816void QQmlDomAstCreator::endVisit(AST::Type *exp)
1817{
1818 if (!m_enableScriptExpressions)
1819 return;
1820
1821 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptType);
1822
1823 if (exp->typeArgument) {
1824 auto currentChild = scriptElementForQualifiedId(expression: exp->typeArgument);
1825 current->insertChild(name: Fields::typeArgument, v: currentChild);
1826 }
1827
1828 if (exp->typeId) {
1829 auto currentChild = scriptElementForQualifiedId(expression: exp->typeId);
1830 current->insertChild(name: Fields::typeName, v: currentChild);
1831 }
1832
1833 pushScriptElement(element: current);
1834}
1835
1836static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile)
1837{
1838 auto top = qmlFile.top();
1839 if (!top) {
1840 return {};
1841 }
1842 auto domEnvironment = top.as<DomEnvironment>();
1843 if (!domEnvironment) {
1844 return {};
1845 }
1846 return domEnvironment;
1847}
1848
1849static QStringList importPathsFrom(MutableDomItem &qmlFile)
1850{
1851 if (auto env = environmentFrom(qmlFile))
1852 return env->loadPaths();
1853
1854 return {};
1855}
1856
1857static QStringList qmldirFilesFrom(MutableDomItem &qmlFile)
1858{
1859 if (auto env = environmentFrom(qmlFile))
1860 return env->qmldirFiles();
1861
1862 return {};
1863}
1864
1865QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile,
1866 QQmlJSLogger *logger)
1867 : m_root(QQmlJSScope::create()),
1868 m_logger(logger),
1869 m_importer(importPathsFrom(qmlFile), nullptr, true),
1870 m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
1871 localFile: m_logger->fileName(), mapper: m_importer.resourceFileMapper())),
1872 m_scopeCreator(m_root, &m_importer, m_logger, m_implicitImportDirectory,
1873 qmldirFilesFrom(qmlFile)),
1874 m_domCreator(qmlFile)
1875{
1876}
1877
1878#define X(name) \
1879 bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \
1880 { \
1881 return visitT(node); \
1882 } \
1883 void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \
1884 { \
1885 endVisitT(node); \
1886 }
1887QQmlJSASTClassListToVisit
1888#undef X
1889
1890 void
1891 QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit()
1892{
1893 QQmlJSScope::Ptr scope = m_scopeCreator.m_currentScope;
1894 if (!m_domCreator.scriptNodeStack.isEmpty()) {
1895 auto topOfStack = m_domCreator.currentScriptNodeEl();
1896 switch (topOfStack.kind) {
1897 case DomType::ScriptBlockStatement:
1898 case DomType::ScriptForStatement:
1899 case DomType::List:
1900 m_domCreator.currentScriptNodeEl().setSemanticScope(scope);
1901 break;
1902 // TODO: find which script elements also have a scope and implement them here
1903 default:
1904 break;
1905 };
1906 } else if (!m_domCreator.nodeStack.isEmpty()) {
1907 std::visit(
1908 visitor: [&scope](auto &&e) {
1909 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
1910 // TODO: find which dom elements also have a scope and implement them here
1911 if constexpr (std::is_same_v<U, QmlObject>) {
1912 e.setSemanticScope(scope);
1913 } else if constexpr (std::is_same_v<U, QmlComponent>) {
1914 e.setSemanticScope(scope);
1915 } else if constexpr (std::is_same_v<U, MethodInfo>) {
1916 if (e.body) {
1917 if (auto scriptElement = e.body->scriptElement())
1918 scriptElement.base()->setSemanticScope(scope);
1919 }
1920 e.setSemanticScope(scope);
1921 }
1922 },
1923 variants&: m_domCreator.currentNodeEl().item.value);
1924 }
1925}
1926
1927void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit()
1928{
1929 QQmlJSScope::Ptr scope = m_scopeCreator.m_currentScope;
1930
1931 auto visitPropertyDefinition = [&scope](auto &&e) {
1932 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
1933 if constexpr (std::is_same_v<U, PropertyDefinition>) {
1934 e.scope = scope;
1935 Q_ASSERT(e.scope);
1936 }
1937 };
1938
1939 // depending whether the property definition has a binding, the property definition might be
1940 // either at the last position in the stack or at the position before the last position.
1941 if (m_domCreator.nodeStack.size() > 1
1942 && m_domCreator.nodeStack.last().item.kind == DomType::Binding) {
1943 std::visit(visitor: [&visitPropertyDefinition](auto &&e) { visitPropertyDefinition(e); },
1944 variants&: m_domCreator.currentNodeEl(i: 1).item.value);
1945 }
1946 if (m_domCreator.nodeStack.size() > 0) {
1947 std::visit(
1948 visitor: [&visitPropertyDefinition](auto &&e) {
1949 visitPropertyDefinition(e);
1950 // TODO: find which dom elements also have a scope and implement them here
1951 },
1952 variants&: m_domCreator.currentNodeEl().item.value);
1953 }
1954}
1955
1956void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError()
1957{
1958}
1959
1960void createDom(MutableDomItem qmlFile, DomCreationOptions options)
1961{
1962 if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
1963 QQmlJSLogger logger; // TODO
1964 // the logger filename is used to populate the QQmlJSScope filepath.
1965 logger.setFileName(qmlFile.canonicalFilePath());
1966 if (options.testFlag(flag: DomCreationOption::WithSemanticAnalysis)) {
1967 auto v = std::make_unique<QQmlDomAstCreatorWithQQmlJSScope>(args&: qmlFile, args: &logger);
1968 v->enableScriptExpressions(enable: options.testFlag(flag: DomCreationOption::WithScriptExpressions));
1969
1970 AST::Node::accept(node: qmlFilePtr->ast(), visitor: v.get());
1971 AstComments::collectComments(item&: qmlFile);
1972
1973 auto typeResolver = std::make_shared<QQmlJSTypeResolver>(args: &v->importer());
1974 typeResolver->init(visitor: &v->scopeCreator(), program: nullptr);
1975 qmlFilePtr->setTypeResolver(typeResolver);
1976 } else {
1977 auto v = std::make_unique<QQmlDomAstCreator>(args&: qmlFile);
1978 v->enableScriptExpressions(enable: options.testFlag(flag: DomCreationOption::WithScriptExpressions));
1979
1980 AST::Node::accept(node: qmlFilePtr->ast(), visitor: v.get());
1981 AstComments::collectComments(item&: qmlFile);
1982 }
1983 } else {
1984 qCWarning(creatorLog) << "createDom called on non qmlFile";
1985 }
1986}
1987
1988} // end namespace Dom
1989} // end namespace QQmlJS
1990
1991#undef Q_SCRIPTELEMENT_DISABLE
1992#undef Q_SCRIPTELEMENT_EXIT_IF
1993
1994QT_END_NAMESPACE
1995

source code of qtdeclarative/src/qmldom/qqmldomastcreator.cpp