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#include "qqmldom_utils_p.h"
16
17#include <QtQml/private/qqmljsast_p.h>
18#include <QtQmlCompiler/private/qqmljsutils_p.h>
19
20#include <QtCore/QDir>
21#include <QtCore/QFileInfo>
22#include <QtCore/QScopeGuard>
23#include <QtCore/QLoggingCategory>
24
25#include <memory>
26#include <optional>
27#include <type_traits>
28#include <variant>
29#include <vector>
30
31static Q_LOGGING_CATEGORY(creatorLog, "qt.qmldom.astcreator", QtWarningMsg);
32
33/*
34 Avoid crashing on files with JS-elements that are not implemented yet.
35 Might be removed (definition + usages) once all script elements are implemented.
36*/
37#define Q_SCRIPTELEMENT_DISABLE() \
38 do { \
39 qDebug() << "Could not construct the JS DOM at" << __FILE__ << ":" << __LINE__ \
40 << ", skipping JS elements..."; \
41 disableScriptElements(); \
42 } while (false)
43
44#define Q_SCRIPTELEMENT_EXIT_IF(check) \
45 do { \
46 if (m_enableScriptExpressions && (check)) { \
47 Q_SCRIPTELEMENT_DISABLE(); \
48 return; \
49 } \
50 } while (false)
51
52QT_BEGIN_NAMESPACE
53namespace QQmlJS {
54namespace Dom {
55
56using namespace AST;
57
58template<typename K, typename V>
59V *valueFromMultimap(QMultiMap<K, V> &mmap, const K &key, index_type idx)
60{
61 if (idx < 0)
62 return nullptr;
63 auto it = mmap.find(key);
64 auto end = mmap.end();
65 if (it == end)
66 return nullptr;
67 auto it2 = it;
68 index_type nEl = 0;
69 while (it2 != end && it2.key() == key) {
70 ++it2;
71 ++nEl;
72 }
73 if (nEl <= idx)
74 return nullptr;
75 for (index_type i = idx + 1; i < nEl; ++i)
76 ++it;
77 return &(*it);
78}
79
80static ErrorGroups astParseErrors()
81{
82 static ErrorGroups errs = { .groups: { NewErrorGroup("Dom"), NewErrorGroup("QmlFile"),
83 NewErrorGroup("Parsing") } };
84 return errs;
85}
86
87static QString toString(const UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.'))
88{
89 QString result;
90
91 for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) {
92 if (iter != qualifiedId)
93 result += delimiter;
94
95 result += iter->name;
96 }
97
98 return result;
99}
100
101static QString typeToString(AST::Type *t)
102{
103 Q_ASSERT(t);
104 QString res = toString(qualifiedId: t->typeId);
105
106 if (UiQualifiedId *arg = t->typeArgument)
107 res += u'<' + toString(qualifiedId: arg) + u'>';
108
109 return res;
110}
111
112SourceLocation combineLocations(SourceLocation s1, SourceLocation s2)
113{
114 return combine(l1: s1, l2: s2);
115}
116
117SourceLocation combineLocations(Node *n)
118{
119 return combineLocations(s1: n->firstSourceLocation(), s2: n->lastSourceLocation());
120}
121
122static ScriptElementVariant wrapIntoFieldMemberExpression(const ScriptElementVariant &left,
123 const SourceLocation &dotToken,
124 const ScriptElementVariant &right)
125{
126 SourceLocation s1, s2;
127 left.visitConst(visitor: [&s1](auto &&el) { s1 = el->mainRegionLocation(); });
128 right.visitConst(visitor: [&s2](auto &&el) { s2 = el->mainRegionLocation(); });
129
130 auto result = std::make_shared<ScriptElements::BinaryExpression>(args&: s1, args&: s2);
131 result->addLocation(region: OperatorTokenRegion, location: dotToken);
132 result->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
133 result->setLeft(left);
134 result->setRight(right);
135 return ScriptElementVariant::fromElement(element: result);
136};
137
138/*!
139 \internal
140 Creates a FieldMemberExpression if the qualified id has dots.
141*/
142static ScriptElementVariant
143fieldMemberExpressionForQualifiedId(const AST::UiQualifiedId *qualifiedId)
144{
145 ScriptElementVariant bindable;
146 bool first = true;
147 for (auto exp = qualifiedId; exp; exp = exp->next) {
148 const SourceLocation identifierLoc = exp->identifierToken;
149 auto id = std::make_shared<ScriptElements::IdentifierExpression>(args: identifierLoc);
150 id->setName(exp->name);
151 if (first) {
152 first = false;
153 bindable = ScriptElementVariant::fromElement(element: id);
154 continue;
155 }
156 bindable = wrapIntoFieldMemberExpression(left: bindable, dotToken: exp->dotToken,
157 right: ScriptElementVariant::fromElement(element: id));
158 }
159
160 return bindable;
161}
162
163QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentQmlObjectOrComponentEl(int idx)
164{
165 Q_ASSERT_X(idx < nodeStack.size() && idx >= 0, "currentQmlObjectOrComponentEl",
166 "Stack does not contain enough elements!");
167 int i = nodeStack.size() - idx;
168 while (i-- > 0) {
169 DomType k = nodeStack.at(i).item.kind;
170 if (k == DomType::QmlObject || k == DomType::QmlComponent)
171 return nodeStack[i];
172 }
173 Q_ASSERT_X(false, "currentQmlObjectEl", "No QmlObject or component in stack");
174 return nodeStack.last();
175}
176
177QQmlDomAstCreator::QmlStackElement &QQmlDomAstCreator::currentNodeEl(int i)
178{
179 Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode", "Stack does not contain element!");
180 return nodeStack[nodeStack.size() - i - 1];
181}
182
183QQmlDomAstCreator::ScriptStackElement &QQmlDomAstCreator::currentScriptNodeEl(int i)
184{
185 Q_ASSERT_X(i < scriptNodeStack.size() && i >= 0, "currentNode",
186 "Stack does not contain element!");
187 return scriptNodeStack[scriptNodeStack.size() - i - 1];
188}
189
190QQmlDomAstCreator::DomValue &QQmlDomAstCreator::currentNode(int i)
191{
192 Q_ASSERT_X(i < nodeStack.size() && i >= 0, "currentNode",
193 "Stack does not contain element!");
194 return nodeStack[nodeStack.size() - i - 1].item;
195}
196
197void QQmlDomAstCreator::removeCurrentNode(std::optional<DomType> expectedType)
198{
199 Q_ASSERT_X(!nodeStack.isEmpty(), className, "popCurrentNode() without any node");
200 if (expectedType)
201 Q_ASSERT(nodeStack.last().item.kind == *expectedType);
202 nodeStack.removeLast();
203}
204
205void QQmlDomAstCreator::removeCurrentScriptNode(std::optional<DomType> expectedType)
206{
207 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
208 Q_ASSERT_X(!scriptNodeStack.isEmpty(), className,
209 "popCurrentScriptNode() without any node");
210 if (expectedType)
211 Q_ASSERT(scriptNodeStack.last().kind == *expectedType);
212 scriptNodeStack.removeLast();
213}
214
215/*!
216 \internal
217 Prepares a script element DOM representation such that it can be used inside a QML DOM element.
218 This recursively sets the pathFromOwner and creates the FileLocations::Tree for all children of
219 element.
220
221 Beware that pathFromOwner is appended to ownerFileLocations when creating the FileLocations!
222
223 Make sure to add, for each of its use, a test in tst_qmldomitem:finalizeScriptExpressions, as
224 using a wrong pathFromOwner and/or a wrong base might lead to bugs hard to debug and spurious
225 crashes.
226 */
227const ScriptElementVariant &
228QQmlDomAstCreator::finalizeScriptExpression(const ScriptElementVariant &element, const Path &pathFromOwner,
229 const FileLocations::Tree &ownerFileLocations)
230{
231 auto e = element.base();
232 Q_ASSERT(e);
233
234 qCDebug(creatorLog) << "Finalizing script expression with path:"
235 << ownerFileLocations->canonicalPathForTesting().append(
236 s: pathFromOwner.toString());
237 e->updatePathFromOwner(newPath: pathFromOwner);
238 e->createFileLocations(fileLocationOfOwner: ownerFileLocations);
239 return element;
240}
241
242FileLocations::Tree QQmlDomAstCreator::createMap(const FileLocations::Tree &base, const Path &p, AST::Node *n)
243{
244 FileLocations::Tree res = FileLocations::ensure(base, basePath: p, pType: AttachedInfo::PathType::Relative);
245 if (n)
246 FileLocations::addRegion(fLoc: res, region: MainRegion, loc: combineLocations(n));
247 return res;
248}
249
250FileLocations::Tree QQmlDomAstCreator::createMap(DomType k, const Path &p, AST::Node *n)
251{
252 Path relative;
253 FileLocations::Tree base;
254 switch (k) {
255 case DomType::QmlObject:
256 switch (currentNode().kind) {
257 case DomType::QmlObject:
258 case DomType::QmlComponent:
259 case DomType::PropertyDefinition:
260 case DomType::Binding:
261 case DomType::Id:
262 case DomType::MethodInfo:
263 break;
264 default:
265 qCWarning(domLog) << "unexpected type" << domTypeToString(k: currentNode().kind);
266 Q_UNREACHABLE();
267 }
268 base = currentNodeEl().fileLocations;
269 if (p.length() > 2) {
270 Path p2 = p[p.length() - 2];
271 if (p2.headKind() == Path::Kind::Field
272 && (p2.checkHeadName(name: Fields::children) || p2.checkHeadName(name: Fields::objects)
273 || p2.checkHeadName(name: Fields::value) || p2.checkHeadName(name: Fields::annotations)
274 || p2.checkHeadName(name: Fields::children)))
275 relative = p.mid(offset: p.length() - 2, length: 2);
276 else if (p.last().checkHeadName(name: Fields::value)
277 && p.last().headKind() == Path::Kind::Field)
278 relative = p.last();
279 else {
280 qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
281 Q_UNREACHABLE();
282 }
283 } else {
284 qCWarning(domLog) << "unexpected path to QmlObject in createMap" << p;
285 Q_UNREACHABLE();
286 }
287 break;
288 case DomType::EnumItem:
289 relative = p;
290 base = currentNodeEl().fileLocations;
291 break;
292 case DomType::QmlComponent:
293 case DomType::Pragma:
294 case DomType::Import:
295 case DomType::Id:
296 case DomType::EnumDecl:
297 relative = p;
298 base = rootMap;
299 break;
300 case DomType::Binding:
301 case DomType::PropertyDefinition:
302 case DomType::MethodInfo:
303 base = currentEl<QmlObject>().fileLocations;
304 if (p.length() > 3)
305 relative = p.mid(offset: p.length() - 3, length: 3);
306 else
307 relative = p;
308 break;
309
310 default:
311 qCWarning(domLog) << "Unexpected type in createMap:" << domTypeToString(k);
312 Q_UNREACHABLE();
313 break;
314 }
315 return createMap(base, p: relative, n);
316}
317
318QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile)
319 : qmlFile(qmlFile),
320 qmlFilePtr(qmlFile.ownerAs<QmlFile>()),
321 rootMap(qmlFilePtr->fileLocationsTree())
322{
323}
324
325bool QQmlDomAstCreator::visit(UiProgram *program)
326{
327 QFileInfo fInfo(qmlFile.canonicalFilePath());
328 QString componentName = fInfo.baseName();
329 QmlComponent *cPtr;
330 Path p = qmlFilePtr->addComponent(component: QmlComponent(componentName), option: AddOption::KeepExisting,
331 cPtr: &cPtr);
332 MutableDomItem newC(qmlFile.item(), p);
333 Q_ASSERT_X(newC.item(), className, "could not recover component added with addComponent");
334 // QmlFile region == Component region == program span
335 // we hide the component span because the component s written after the imports
336 FileLocations::addRegion(fLoc: rootMap, region: MainRegion, loc: combineLocations(n: program));
337 pushEl(p, it: *cPtr, n: program);
338
339 auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>();
340 const bool loadDependencies =
341 !envPtr->options().testFlag(flag: DomEnvironment::Option::NoDependencies);
342 // add implicit directory import and load them in the Dom
343 if (!fInfo.canonicalPath().isEmpty()) {
344 Import selfDirImport(QmlUri::fromDirectoryString(importStr: fInfo.canonicalPath()));
345 selfDirImport.implicit = true;
346 qmlFilePtr->addImport(i: selfDirImport);
347
348 if (loadDependencies) {
349 const QString currentFile = envPtr->domCreationOptions().testFlag(flag: WithSemanticAnalysis)
350 ? QQmlJSUtils::qmlBuildPathFromSourcePath(
351 mapper: envPtr->semanticAnalysis().m_mapper.get(),
352 pathInBuildFolder: qmlFile.canonicalFilePath())
353 : qmlFile.canonicalFilePath();
354
355 const QDir implicitImportDir = QFileInfo(currentFile).dir();
356 const QString implicitImportDirPath = implicitImportDir.canonicalPath();
357 envPtr->loadFile(file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: implicitImportDirPath),
358 callback: DomItem::Callback(), fileType: DomType::QmlDirectory);
359
360 // also load the qmldir from the implicit directory, if existing
361 if (implicitImportDir.exists(name: u"qmldir"_s)) {
362 const QString implicitImportQmldir = implicitImportDirPath + u"/qmldir"_s;
363 envPtr->loadFile(file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: implicitImportQmldir),
364 callback: DomItem::Callback(), fileType: DomType::QmldirFile);
365 }
366 }
367 }
368 // add implicit imports from the environment (QML, QtQml for example) and load them in the Dom
369 for (Import i : qmlFile.environment().ownerAs<DomEnvironment>()->implicitImports()) {
370 i.implicit = true;
371 qmlFilePtr->addImport(i);
372
373 if (loadDependencies)
374 envPtr->loadModuleDependency(uri: i.uri.moduleUri(), v: i.version, callback: DomItem::Callback());
375 }
376 if (m_loadFileLazily && loadDependencies) {
377 envPtr->loadPendingDependencies();
378 envPtr->commitToBase(self: qmlFile.environment().item());
379 }
380
381 return true;
382}
383
384void QQmlDomAstCreator::endVisit(AST::UiProgram *)
385{
386 MutableDomItem newC = qmlFile.path(p: currentNodeEl().path);
387 QmlComponent &comp = current<QmlComponent>();
388 for (const Pragma &p : qmlFilePtr->pragmas()) {
389 if (p.name.compare(s: u"singleton", cs: Qt::CaseInsensitive) == 0) {
390 comp.setIsSingleton(true);
391 comp.setIsCreatable(false); // correct?
392 }
393 }
394 *newC.mutableAs<QmlComponent>() = comp;
395 removeCurrentNode(expectedType: DomType::QmlComponent);
396 Q_ASSERT_X(nodeStack.isEmpty(), className, "ui program did not finish node stack");
397}
398
399bool QQmlDomAstCreator::visit(UiPragma *el)
400{
401 QStringList valueList;
402 for (auto t = el->values; t; t = t->next)
403 valueList << t->value.toString();
404
405 auto fileLocation = createMap(
406 k: DomType::Pragma, p: qmlFilePtr->addPragma(pragma: Pragma(el->name.toString(), valueList)), n: el);
407 FileLocations::addRegion(fLoc: fileLocation, region: PragmaKeywordRegion, loc: el->pragmaToken);
408 FileLocations::addRegion(fLoc: fileLocation, region: IdentifierRegion, loc: el->pragmaIdToken);
409 if (el->colonToken.isValid()) {
410 FileLocations::addRegion(fLoc: fileLocation, region: ColonTokenRegion, loc: el->colonToken);
411 }
412 int i = 0;
413 for (auto t = el->values; t; t = t->next) {
414 auto subMap = createMap(base: fileLocation, p: Path().field(name: Fields::values).index(i), n: t);
415 FileLocations::addRegion(fLoc: subMap, region: PragmaValuesRegion, loc: t->location);
416 ++i;
417 }
418
419 return true;
420}
421
422bool QQmlDomAstCreator::visit(UiImport *el)
423{
424 Version v(Version::Latest, Version::Latest);
425 if (el->version && el->version->version.hasMajorVersion())
426 v.majorVersion = el->version->version.majorVersion();
427 if (el->version && el->version->version.hasMinorVersion())
428 v.minorVersion = el->version->version.minorVersion();
429
430 auto envPtr = qmlFile.environment().ownerAs<DomEnvironment>();
431 const bool loadDependencies =
432 !envPtr->options().testFlag(flag: DomEnvironment::Option::NoDependencies);
433 FileLocations::Tree fileLocation;
434 if (el->importUri != nullptr) {
435 const Import import =
436 Import::fromUriString(importStr: toString(qualifiedId: el->importUri), v, importId: el->importId.toString());
437 fileLocation = createMap(k: DomType::Import, p: qmlFilePtr->addImport(i: import), n: el);
438
439 if (loadDependencies) {
440 envPtr->loadModuleDependency(uri: import.uri.moduleUri(), v: import.version,
441 callback: DomItem::Callback());
442 }
443 FileLocations::addRegion(fLoc: fileLocation, region: ImportUriRegion, loc: combineLocations(n: el->importUri));
444 } else {
445 const Import import =
446 Import::fromFileString(importStr: el->fileName.toString(), importId: el->importId.toString());
447 fileLocation = createMap(k: DomType::Import, p: qmlFilePtr->addImport(i: import), n: el);
448
449 if (loadDependencies) {
450 const QString currentFileDir =
451 QFileInfo(qmlFile.canonicalFilePath()).dir().canonicalPath();
452 envPtr->loadFile(file: FileToLoad::fromFileSystem(
453 environment: envPtr, canonicalPath: import.uri.absoluteLocalPath(basePath: currentFileDir)),
454 callback: DomItem::Callback(), fileType: DomType::QmlDirectory);
455 }
456 FileLocations::addRegion(fLoc: fileLocation, region: ImportUriRegion, loc: el->fileNameToken);
457 }
458 if (m_loadFileLazily && loadDependencies) {
459 envPtr->loadPendingDependencies();
460 envPtr->commitToBase(self: qmlFile.environment().item());
461 }
462
463 if (el->importToken.isValid())
464 FileLocations::addRegion(fLoc: fileLocation, region: ImportTokenRegion, loc: el->importToken);
465
466 if (el->asToken.isValid())
467 FileLocations::addRegion(fLoc: fileLocation, region: AsTokenRegion, loc: el->asToken);
468
469 if (el->importIdToken.isValid())
470 FileLocations::addRegion(fLoc: fileLocation, region: IdNameRegion, loc: el->importIdToken);
471
472 if (el->version)
473 FileLocations::addRegion(fLoc: fileLocation, region: VersionRegion, loc: combineLocations(n: el->version));
474
475
476 return true;
477}
478
479bool QQmlDomAstCreator::visit(AST::UiPublicMember *el)
480{
481 switch (el->type) {
482 case AST::UiPublicMember::Signal: {
483 MethodInfo m;
484 m.name = el->name.toString();
485 m.typeName = toString(qualifiedId: el->memberType);
486 m.isReadonly = el->isReadonly();
487 m.access = MethodInfo::Public;
488 m.methodType = MethodInfo::Signal;
489 m.isList = el->typeModifier == QLatin1String("list");
490 MethodInfo *mPtr;
491 Path p = current<QmlObject>().addMethod(functionDef: m, option: AddOption::KeepExisting, mPtr: &mPtr);
492 pushEl(p, it: *mPtr, n: el);
493 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: SignalKeywordRegion,
494 loc: el->propertyToken());
495 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: IdentifierRegion,
496 loc: el->identifierToken);
497 MethodInfo &mInfo = std::get<MethodInfo>(v&: currentNode().value);
498 AST::UiParameterList *args = el->parameters;
499 while (args) {
500 MethodParameter param;
501 param.name = args->name.toString();
502 param.typeName = args->type ? args->type->toString() : QString();
503 index_type idx = index_type(mInfo.parameters.size());
504 if (!args->colonToken.isValid())
505 param.typeAnnotationStyle = MethodParameter::TypeAnnotationStyle::Prefix;
506 mInfo.parameters.append(t: param);
507 auto argLocs = FileLocations::ensure(base: nodeStack.last().fileLocations,
508 basePath: Path::Field(s: Fields::parameters).index(i: idx),
509 pType: AttachedInfo::PathType::Relative);
510 FileLocations::addRegion(fLoc: argLocs, region: MainRegion, loc: combineLocations(n: args));
511 FileLocations::addRegion(fLoc: argLocs, region: IdentifierRegion, loc: args->identifierToken);
512 if (args->type)
513 FileLocations::addRegion(fLoc: argLocs, region: TypeIdentifierRegion, loc: args->propertyTypeToken);
514 args = args->next;
515 }
516 break;
517 }
518 case AST::UiPublicMember::Property: {
519 PropertyDefinition p;
520 p.name = el->name.toString();
521 p.typeName = toString(qualifiedId: el->memberType);
522 p.isReadonly = el->isReadonly();
523 p.isDefaultMember = el->isDefaultMember();
524 p.isRequired = el->isRequired();
525 p.isList = el->typeModifier == QLatin1String("list");
526 if (!el->typeModifier.isEmpty())
527 p.typeName = el->typeModifier.toString() + QChar(u'<') + p.typeName + QChar(u'>');
528 PropertyDefinition *pPtr;
529 Path pPathFromOwner =
530 current<QmlObject>().addPropertyDef(propertyDef: p, option: AddOption::KeepExisting, pDef: &pPtr);
531 if (m_enableScriptExpressions) {
532 auto qmlObjectType = makeGenericScriptElement(ast: el->memberType, kind: DomType::ScriptType);
533 qmlObjectType->insertChild(name: Fields::typeName,
534 v: fieldMemberExpressionForQualifiedId(qualifiedId: el->memberType));
535 pPtr->setNameIdentifiers(finalizeScriptExpression(
536 element: ScriptElementVariant::fromElement(element: qmlObjectType),
537 pathFromOwner: pPathFromOwner.field(name: Fields::nameIdentifiers), ownerFileLocations: rootMap));
538 // skip binding identifiers of the binding inside the property definition, if there is
539 // one
540 m_skipBindingIdentifiers = el->binding;
541 }
542 pushEl(p: pPathFromOwner, it: *pPtr, n: el);
543 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: PropertyKeywordRegion,
544 loc: el->propertyToken());
545 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: IdentifierRegion,
546 loc: el->identifierToken);
547 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: TypeIdentifierRegion,
548 loc: el->typeToken);
549 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: ColonTokenRegion, loc: el->colonToken);
550 if (el->typeModifierToken.isValid())
551 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: TypeModifierRegion, loc: el->typeModifierToken);
552 if (p.name == u"id")
553 qmlFile.addError(msg: std::move(astParseErrors()
554 .warning(message: tr(sourceText: "id is a special attribute, that should not be "
555 "used as property name"))
556 .withPath(currentNodeEl().path)));
557 if (p.isDefaultMember) {
558 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: DefaultKeywordRegion,
559 loc: el->defaultToken());
560 }
561 if (p.isRequired) {
562 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: RequiredKeywordRegion,
563 loc: el->requiredToken());
564 }
565 if (p.isReadonly) {
566 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: ReadonlyKeywordRegion,
567 loc: el->readonlyToken());
568 }
569 if (el->statement) {
570 BindingType bType = BindingType::Normal;
571 SourceLocation loc = combineLocations(n: el->statement);
572 QStringView code = qmlFilePtr->code();
573
574 auto script = std::make_shared<ScriptExpression>(
575 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(), args&: el->statement,
576 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::BindingExpression,
577 args&: loc);
578 Binding *bPtr;
579 Path bPathFromOwner = current<QmlObject>().addBinding(binding: Binding(p.name, script, bType),
580 option: AddOption::KeepExisting, bPtr: &bPtr);
581 FileLocations::Tree bLoc = createMap(k: DomType::Binding, p: bPathFromOwner, n: el);
582 FileLocations::addRegion(fLoc: bLoc, region: ColonTokenRegion, loc: el->colonToken);
583 FileLocations::Tree valueLoc = FileLocations::ensure(base: bLoc, basePath: Path::Field(s: Fields::value),
584 pType: AttachedInfo::PathType::Relative);
585 FileLocations::addRegion(fLoc: valueLoc, region: MainRegion, loc: combineLocations(n: el->statement));
586 // push it also: its needed in endVisit to add the scriptNode to it
587 // do not use pushEl to avoid recreating the already created "bLoc" Map
588 nodeStack.append(t: { .path: bPathFromOwner, .item: *bPtr, .fileLocations: bLoc });
589 }
590 break;
591 }
592 }
593 return true;
594}
595
596void QQmlDomAstCreator::endVisit(AST::UiPublicMember *el)
597{
598 if (auto &lastEl = currentNode(); lastEl.kind == DomType::Binding) {
599 Binding &b = std::get<Binding>(v&: lastEl.value);
600 if (m_enableScriptExpressions
601 && (scriptNodeStack.size() != 1 || scriptNodeStack.last().isList())) {
602 Q_SCRIPTELEMENT_DISABLE();
603 }
604 if (m_enableScriptExpressions) {
605 b.scriptExpressionValue()->setScriptElement(finalizeScriptExpression(
606 element: currentScriptNodeEl().takeVariant(), pathFromOwner: Path().field(name: Fields::scriptElement),
607 ownerFileLocations: FileLocations::ensure(base: currentNodeEl().fileLocations,
608 basePath: Path().field(name: Fields::value))));
609 removeCurrentScriptNode(expectedType: {});
610 }
611
612 QmlObject &containingObject = current<QmlObject>();
613 Binding *bPtr =
614 valueFromMultimap(mmap&: containingObject.m_bindings, key: b.name(), idx: currentIndex());
615 Q_ASSERT(bPtr);
616 removeCurrentNode(expectedType: {});
617 }
618 Node::accept(node: el->parameters, visitor: this);
619 loadAnnotations(el);
620 if ((el->binding || el->statement)
621 && nodeStack.last().item.kind == DomType::PropertyDefinition) {
622 PropertyDefinition &pDef = std::get<PropertyDefinition>(v&: nodeStack.last().item.value);
623 if (!pDef.annotations.isEmpty()) {
624 QmlObject duplicate;
625 duplicate.setName(QLatin1String("duplicate"));
626 QmlObject &obj = current<QmlObject>();
627 auto it = obj.m_bindings.find(key: pDef.name);
628 if (it != obj.m_bindings.end()) {
629 for (QmlObject ann : pDef.annotations) {
630 ann.addAnnotation(annotation: duplicate);
631 it->addAnnotation(selfPathFromOwner: currentEl<QmlObject>()
632 .path.field(name: Fields::bindings)
633 .key(name: pDef.name)
634 .index(i: obj.m_bindings.values(key: pDef.name).size() - 1),
635 a: ann);
636 }
637 }
638 }
639 }
640 QmlObject &obj = current<QmlObject>();
641 QmlStackElement &sEl = nodeStack.last();
642 switch (sEl.item.kind) {
643 case DomType::PropertyDefinition: {
644 PropertyDefinition pDef = std::get<PropertyDefinition>(v&: sEl.item.value);
645 PropertyDefinition *pDefPtr =
646 valueFromMultimap(mmap&: obj.m_propertyDefs, key: pDef.name, idx: sEl.path.last().headIndex());
647 Q_ASSERT(pDefPtr);
648 *pDefPtr = pDef;
649 } break;
650 case DomType::MethodInfo: {
651 MethodInfo m = std::get<MethodInfo>(v&: sEl.item.value);
652 MethodInfo *mPtr = valueFromMultimap(mmap&: obj.m_methods, key: m.name, idx: sEl.path.last().headIndex());
653 Q_ASSERT(mPtr);
654 *mPtr = m;
655 } break;
656 default:
657 Q_UNREACHABLE();
658 }
659 removeCurrentNode(expectedType: {});
660}
661
662void QQmlDomAstCreator::endVisit(AST::FormalParameterList *list)
663{
664 endVisitForLists(list);
665}
666
667bool QQmlDomAstCreator::visit(AST::FunctionExpression *)
668{
669 ++m_nestedFunctionDepth;
670 if (!m_enableScriptExpressions)
671 return false;
672
673 return true;
674}
675
676ScriptElementVariant QQmlDomAstCreator::prepareBodyForFunction(AST::FunctionExpression *fExpression)
677{
678 Q_ASSERT(!scriptNodeStack.isEmpty() || !fExpression->body);
679
680 if (fExpression->body) {
681 if (currentScriptNodeEl().isList()) {
682 // It is more intuitive to have functions with a block as a body instead of a
683 // list.
684 auto body = std::make_shared<ScriptElements::BlockStatement>(
685 args: combineLocations(s1: fExpression->lbraceToken, s2: fExpression->rbraceToken));
686 body->setStatements(currentScriptNodeEl().takeList());
687 if (auto semanticScope = body->statements().semanticScope())
688 body->setSemanticScope(semanticScope);
689 auto result = ScriptElementVariant::fromElement(element: body);
690 removeCurrentScriptNode(expectedType: {});
691 return result;
692 } else {
693 auto result = currentScriptNodeEl().takeVariant();
694 removeCurrentScriptNode(expectedType: {});
695 return result;
696 }
697 Q_UNREACHABLE_RETURN({});
698 }
699
700 // for convenience purposes: insert an empty BlockStatement
701 auto body = std::make_shared<ScriptElements::BlockStatement>(
702 args: combineLocations(s1: fExpression->lbraceToken, s2: fExpression->rbraceToken));
703 return ScriptElementVariant::fromElement(element: body);
704}
705
706void QQmlDomAstCreator::endVisit(AST::FunctionExpression *fExpression)
707{
708 --m_nestedFunctionDepth;
709 if (!m_enableScriptExpressions)
710 return;
711
712 auto current = makeGenericScriptElement(ast: fExpression, kind: DomType::ScriptFunctionExpression);
713 if (fExpression->identifierToken.isValid())
714 current->addLocation(region: IdentifierRegion, location: fExpression->identifierToken);
715 if (fExpression->functionToken.isValid())
716 current->addLocation(region: FunctionKeywordRegion, location: fExpression->functionToken);
717 if (fExpression->starToken.isValid())
718 current->addLocation(region: StarTokenRegion, location: fExpression->starToken);
719 if (fExpression->lparenToken.isValid())
720 current->addLocation(region: LeftParenthesisRegion, location: fExpression->lparenToken);
721 if (fExpression->rparenToken.isValid())
722 current->addLocation(region: RightParenthesisRegion, location: fExpression->rparenToken);
723 if (fExpression->lbraceToken.isValid())
724 current->addLocation(region: LeftBraceRegion, location: fExpression->lbraceToken);
725 if (fExpression->rbraceToken.isValid())
726 current->addLocation(region: RightBraceRegion, location: fExpression->rbraceToken);
727 if (fExpression->typeAnnotation) {
728 current->addLocation(region: TypeIdentifierRegion,
729 location: combineLocations(n: fExpression->typeAnnotation->type));
730 }
731
732 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fExpression->body);
733 current->insertChild(name: Fields::body, v: prepareBodyForFunction(fExpression));
734
735 if (fExpression->typeAnnotation) {
736 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
737 current->insertChild(name: Fields::returnType, v: currentScriptNodeEl().takeVariant());
738 scriptNodeStack.removeLast();
739 }
740 if (fExpression->formals) {
741 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
742 current->insertChild(name: Fields::parameters, v: currentScriptNodeEl().takeList());
743 scriptNodeStack.removeLast();
744 }
745
746 if (!fExpression->name.isEmpty())
747 current->insertValue(name: Fields::name, v: fExpression->name);
748
749 pushScriptElement(element: current);
750}
751
752bool QQmlDomAstCreator::visit(AST::FunctionDeclaration *fDef)
753{
754 // Treat nested functions as (named) lambdas instead of Qml Object methods.
755 if (m_nestedFunctionDepth > 0) {
756 return visit(static_cast<FunctionExpression *>(fDef));
757 }
758 ++m_nestedFunctionDepth;
759 const QStringView code(qmlFilePtr->code());
760 MethodInfo m;
761 m.name = fDef->name.toString();
762 if (AST::TypeAnnotation *tAnn = fDef->typeAnnotation) {
763 if (AST::Type *t = tAnn->type)
764 m.typeName = typeToString(t);
765 }
766 m.access = MethodInfo::Public;
767 m.methodType = MethodInfo::Method;
768
769 SourceLocation bodyLoc = fDef->body ? combineLocations(n: fDef->body)
770 : combineLocations(s1: fDef->lbraceToken, s2: fDef->rbraceToken);
771 SourceLocation methodLoc = combineLocations(n: fDef);
772 QStringView preCode = code.mid(pos: methodLoc.begin(), n: bodyLoc.begin() - methodLoc.begin());
773 QStringView postCode = code.mid(pos: bodyLoc.end(), n: methodLoc.end() - bodyLoc.end());
774 m.body = std::make_shared<ScriptExpression>(
775 args: code.mid(pos: bodyLoc.offset, n: bodyLoc.length), args: qmlFilePtr->engine(), args&: fDef->body,
776 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::FunctionBody, args&: bodyLoc, args: 0,
777 args&: preCode, args&: postCode);
778
779 if (fDef->typeAnnotation) {
780 SourceLocation typeLoc = combineLocations(n: fDef->typeAnnotation);
781 m.returnType = std::make_shared<ScriptExpression>(
782 args: code.mid(pos: typeLoc.offset, n: typeLoc.length), args: qmlFilePtr->engine(),
783 args&: fDef->typeAnnotation, args: qmlFilePtr->astComments(),
784 args: ScriptExpression::ExpressionType::ReturnType, args&: typeLoc, args: 0, args: u"", args: u"");
785 }
786
787 MethodInfo *mPtr;
788 Path mPathFromOwner = current<QmlObject>().addMethod(functionDef: m, option: AddOption::KeepExisting, mPtr: &mPtr);
789 pushEl(p: mPathFromOwner, it: *mPtr,
790 n: fDef); // add at the start and use the normal recursive visit?
791 FileLocations::Tree &fLoc = nodeStack.last().fileLocations;
792 if (fDef->identifierToken.isValid())
793 FileLocations::addRegion(fLoc, region: IdentifierRegion, loc: fDef->identifierToken);
794 auto bodyTree = FileLocations::ensure(base: fLoc, basePath: Path::Field(s: Fields::body),
795 pType: AttachedInfo::PathType::Relative);
796 FileLocations::addRegion(fLoc: bodyTree, region: MainRegion, loc: bodyLoc);
797 if (fDef->functionToken.isValid())
798 FileLocations::addRegion(fLoc, region: FunctionKeywordRegion, loc: fDef->functionToken);
799 if (fDef->starToken.isValid())
800 FileLocations::addRegion(fLoc, region: StarTokenRegion, loc: fDef->starToken);
801 if (fDef->lparenToken.length != 0)
802 FileLocations::addRegion(fLoc, region: LeftParenthesisRegion, loc: fDef->lparenToken);
803 if (fDef->rparenToken.length != 0)
804 FileLocations::addRegion(fLoc, region: RightParenthesisRegion, loc: fDef->rparenToken);
805 if (fDef->lbraceToken.length != 0)
806 FileLocations::addRegion(fLoc, region: LeftBraceRegion, loc: fDef->lbraceToken);
807 if (fDef->rbraceToken.length != 0)
808 FileLocations::addRegion(fLoc, region: RightBraceRegion, loc: fDef->rbraceToken);
809 if (fDef->typeAnnotation)
810 FileLocations::addRegion(fLoc, region: TypeIdentifierRegion, loc: combineLocations(n: fDef->typeAnnotation->type));
811 MethodInfo &mInfo = std::get<MethodInfo>(v&: currentNode().value);
812 AST::FormalParameterList *args = fDef->formals;
813 while (args) {
814 MethodParameter param;
815 param.name = args->element->bindingIdentifier.toString();
816 if (AST::TypeAnnotation *tAnn = args->element->typeAnnotation) {
817 if (AST::Type *t = tAnn->type)
818 param.typeName = typeToString(t);
819 }
820 if (args->element->initializer) {
821 SourceLocation loc = combineLocations(n: args->element->initializer);
822 auto script = std::make_shared<ScriptExpression>(
823 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(),
824 args&: args->element->initializer, args: qmlFilePtr->astComments(),
825 args: ScriptExpression::ExpressionType::ArgInitializer, args&: loc);
826 param.defaultValue = script;
827 }
828 if (args->element->type == AST::PatternElement::SpreadElement)
829 param.isRestElement = true;
830 SourceLocation parameterLoc = combineLocations(n: args->element);
831 param.value = std::make_shared<ScriptExpression>(
832 args: code.mid(pos: parameterLoc.offset, n: parameterLoc.length), args: qmlFilePtr->engine(),
833 args&: args->element, args: qmlFilePtr->astComments(),
834 args: ScriptExpression::ExpressionType::ArgumentStructure, args&: parameterLoc);
835
836 index_type idx = index_type(mInfo.parameters.size());
837 mInfo.parameters.append(t: param);
838 auto argLocs = FileLocations::ensure(base: nodeStack.last().fileLocations,
839 basePath: Path::Field(s: Fields::parameters).index(i: idx),
840 pType: AttachedInfo::PathType::Relative);
841 FileLocations::addRegion(fLoc: argLocs, region: MainRegion, loc: combineLocations(n: args));
842 if (args->element->identifierToken.isValid())
843 FileLocations::addRegion(fLoc: argLocs, region: IdentifierRegion, loc: args->element->identifierToken);
844 if (args->element->typeAnnotation)
845 FileLocations::addRegion(fLoc: argLocs, region: TypeIdentifierRegion, loc: combineLocations(n: args->element->typeAnnotation->type));
846 args = args->next;
847 }
848 return true;
849}
850
851bool QQmlDomAstCreator::visit(AST::UiSourceElement *el)
852{
853 if (!cast<FunctionDeclaration *>(ast: el->sourceElement)) {
854 qCWarning(creatorLog) << "unhandled source el:" << static_cast<AST::Node *>(el);
855 Q_UNREACHABLE();
856 }
857 return true;
858}
859
860static void setFormalParameterKind(ScriptElementVariant &variant)
861{
862 if (auto data = variant.data()) {
863 if (auto genericElement =
864 std::get_if<std::shared_ptr<ScriptElements::GenericScriptElement>>(ptr: &*data)) {
865 (*genericElement)->setKind(DomType::ScriptFormalParameter);
866 }
867 }
868}
869
870void QQmlDomAstCreator::endVisit(AST::FunctionDeclaration *fDef)
871{
872 // Treat nested functions as (named) lambdas instead of Qml Object methods.
873 if (m_nestedFunctionDepth > 1) {
874 endVisit(fExpression: static_cast<FunctionExpression *>(fDef));
875 return;
876 }
877 --m_nestedFunctionDepth;
878 MethodInfo &m = std::get<MethodInfo>(v&: currentNode().value);
879 const FileLocations::Tree bodyTree =
880 FileLocations::ensure(base: currentNodeEl().fileLocations, basePath: Path().field(name: Fields::body));
881 const Path bodyPath = Path().field(name: Fields::scriptElement);
882
883 if (!m_enableScriptExpressions)
884 return;
885
886 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty() && fDef->body);
887 m.body->setScriptElement(
888 finalizeScriptExpression(element: prepareBodyForFunction(fExpression: fDef), pathFromOwner: bodyPath, ownerFileLocations: bodyTree));
889
890 if (fDef->typeAnnotation) {
891 auto argLoc = FileLocations::ensure(base: nodeStack.last().fileLocations,
892 basePath: Path().field(name: Fields::returnType),
893 pType: AttachedInfo::PathType::Relative);
894 const Path pathToReturnType = Path().field(name: Fields::scriptElement);
895
896 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
897 ScriptElementVariant variant = currentScriptNodeEl().takeVariant();
898 finalizeScriptExpression(element: variant, pathFromOwner: pathToReturnType, ownerFileLocations: argLoc);
899 m.returnType->setScriptElement(variant);
900 removeCurrentScriptNode(expectedType: {});
901 }
902 if (fDef->formals) {
903 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
904 auto parameterList = scriptNodeStack.takeLast().takeList();
905 const auto parameterQList = parameterList.qList();
906 size_t size = (size_t)parameterQList.size();
907 for (size_t idx = size - 1; idx < size; --idx) {
908 auto argLoc = FileLocations::ensure(
909 base: nodeStack.last().fileLocations,
910 basePath: Path().field(name: Fields::parameters).index(i: idx).field(name: Fields::value),
911 pType: AttachedInfo::PathType::Relative);
912 const Path pathToArgument = Path().field(name: Fields::scriptElement);
913
914 ScriptElementVariant variant = parameterQList[idx];
915 setFormalParameterKind(variant);
916 finalizeScriptExpression(element: variant, pathFromOwner: pathToArgument, ownerFileLocations: argLoc);
917 m.parameters[idx].value->setScriptElement(variant);
918 }
919 }
920
921 // there should be no more uncollected script elements
922 if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
923 Q_SCRIPTELEMENT_DISABLE();
924 }
925}
926
927void QQmlDomAstCreator::endVisit(AST::UiSourceElement *el)
928{
929 MethodInfo &m = std::get<MethodInfo>(v&: currentNode().value);
930 loadAnnotations(el);
931 QmlObject &obj = current<QmlObject>();
932 MethodInfo *mPtr =
933 valueFromMultimap(mmap&: obj.m_methods, key: m.name, idx: nodeStack.last().path.last().headIndex());
934 Q_ASSERT(mPtr);
935 *mPtr = m;
936 removeCurrentNode(expectedType: DomType::MethodInfo);
937}
938
939bool QQmlDomAstCreator::visit(AST::UiObjectDefinition *el)
940{
941 QmlObject scope;
942 scope.setName(toString(qualifiedId: el->qualifiedTypeNameId));
943 scope.addPrototypePath(prototypePath: Paths::lookupTypePath(name: scope.name()));
944 QmlObject *sPtr = nullptr;
945 Path sPathFromOwner;
946 if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last()) {
947 if (currentNode().kind == DomType::Binding) {
948 QList<QmlObject> *vals = std::get<Binding>(v&: currentNode().value).arrayValue();
949 if (vals) {
950 int idx = vals->size();
951 vals->append(t: scope);
952 sPathFromOwner = currentNodeEl().path.field(name: Fields::value).index(i: idx);
953 sPtr = &((*vals)[idx]);
954 sPtr->updatePathFromOwner(newPath: sPathFromOwner);
955 } else {
956 Q_ASSERT_X(false, className,
957 "expected an array binding with a valid QList<QmlScope> as value");
958 }
959 } else {
960 Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
961 }
962 } else {
963 DomValue &containingObject = currentQmlObjectOrComponentEl().item;
964 switch (containingObject.kind) {
965 case DomType::QmlComponent:
966 sPathFromOwner = std::get<QmlComponent>(v&: containingObject.value).addObject(object: scope, oPtr: &sPtr);
967 break;
968 case DomType::QmlObject:
969 sPathFromOwner = std::get<QmlObject>(v&: containingObject.value).addChild(child: scope, cPtr: &sPtr);
970 break;
971 default:
972 Q_UNREACHABLE();
973 }
974 Path pathFromContainingObject = sPathFromOwner.mid(offset: currentNodeEl().path.length());
975 FileLocations::Tree fLoc =
976 FileLocations::ensure(base: currentNodeEl().fileLocations, basePath: pathFromContainingObject,
977 pType: AttachedInfo::PathType::Relative);
978 FileLocations::addRegion(fLoc, region: IdentifierRegion,
979 loc: el->qualifiedTypeNameId->identifierToken);
980 }
981 Q_ASSERT_X(sPtr, className, "could not recover new scope");
982
983 if (m_enableScriptExpressions) {
984 auto qmlObjectType = makeGenericScriptElement(ast: el->qualifiedTypeNameId, kind: DomType::ScriptType);
985 qmlObjectType->insertChild(name: Fields::typeName,
986 v: fieldMemberExpressionForQualifiedId(qualifiedId: el->qualifiedTypeNameId));
987 sPtr->setNameIdentifiers(
988 finalizeScriptExpression(element: ScriptElementVariant::fromElement(element: qmlObjectType),
989 pathFromOwner: sPathFromOwner.field(name: Fields::nameIdentifiers), ownerFileLocations: rootMap));
990 }
991 pushEl(p: sPathFromOwner, it: *sPtr, n: el);
992
993 if (m_enableScriptExpressions && el->initializer) {
994 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: LeftBraceRegion,
995 loc: el->initializer->lbraceToken);
996 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: RightBraceRegion,
997 loc: el->initializer->rbraceToken);
998 }
999 loadAnnotations(el);
1000 return true;
1001}
1002
1003void QQmlDomAstCreator::endVisit(AST::UiObjectDefinition *)
1004{
1005 QmlObject &obj = current<QmlObject>();
1006 int idx = currentIndex();
1007 if (!arrayBindingLevels.isEmpty() && nodeStack.size() == arrayBindingLevels.last() + 1) {
1008 if (currentNode(i: 1).kind == DomType::Binding) {
1009 Binding &b = std::get<Binding>(v&: currentNode(i: 1).value);
1010 QList<QmlObject> *vals = b.arrayValue();
1011 Q_ASSERT_X(vals, className,
1012 "expected an array binding with a valid QList<QmlScope> as value");
1013 (*vals)[idx] = obj;
1014 } else {
1015 Q_ASSERT_X(false, className, "expected an array binding as last node on the stack");
1016 }
1017 } else {
1018 DomValue &containingObject = currentNodeEl(i: 1).item;
1019 Path p = currentNodeEl().path;
1020 switch (containingObject.kind) {
1021 case DomType::QmlComponent:
1022 if (p[p.length() - 2] == Path::Field(s: Fields::objects))
1023 std::get<QmlComponent>(v&: containingObject.value).m_objects[idx] = obj;
1024 else
1025 Q_UNREACHABLE();
1026 break;
1027 case DomType::QmlObject:
1028 if (p[p.length() - 2] == Path::Field(s: Fields::children))
1029 std::get<QmlObject>(v&: containingObject.value).m_children[idx] = obj;
1030 else
1031 Q_UNREACHABLE();
1032 break;
1033 default:
1034 Q_UNREACHABLE();
1035 }
1036 }
1037 removeCurrentNode(expectedType: DomType::QmlObject);
1038}
1039
1040void QQmlDomAstCreator::setBindingIdentifiers(const Path &pathFromOwner,
1041 const UiQualifiedId *identifiers, Binding *bindingPtr)
1042{
1043 const bool skipBindingIdentifiers = std::exchange(obj&: m_skipBindingIdentifiers, new_val: false);
1044 if (!m_enableScriptExpressions || skipBindingIdentifiers)
1045 return;
1046
1047 ScriptElementVariant bindable = fieldMemberExpressionForQualifiedId(qualifiedId: identifiers);
1048 bindingPtr->setBindingIdentifiers(finalizeScriptExpression(
1049 element: bindable, pathFromOwner: pathFromOwner.field(name: Fields::bindingIdentifiers), ownerFileLocations: rootMap));
1050}
1051
1052bool QQmlDomAstCreator::visit(AST::UiObjectBinding *el)
1053{
1054 BindingType bType = (el->hasOnToken ? BindingType::OnBinding : BindingType::Normal);
1055 QmlObject value;
1056 value.setName(toString(qualifiedId: el->qualifiedTypeNameId));
1057 Binding *bPtr;
1058 Path bPathFromOwner = current<QmlObject>().addBinding(
1059 binding: Binding(toString(qualifiedId: el->qualifiedId), value, bType), option: AddOption::KeepExisting, bPtr: &bPtr);
1060 if (bPtr->name() == u"id")
1061 qmlFile.addError(msg: std::move(astParseErrors()
1062 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
1063 "followed by letters, numbers or underscore, "
1064 "assuming they refer to an id property"))
1065 .withPath(bPathFromOwner)));
1066 setBindingIdentifiers(pathFromOwner: bPathFromOwner, identifiers: el->qualifiedId, bindingPtr: bPtr);
1067
1068 pushEl(p: bPathFromOwner, it: *bPtr, n: el);
1069 if (el->hasOnToken)
1070 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: OnTokenRegion, loc: el->colonToken);
1071 else
1072 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: ColonTokenRegion, loc: el->colonToken);
1073 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: IdentifierRegion, loc: combineLocations(n: el->qualifiedId));
1074 loadAnnotations(el);
1075 QmlObject *objValue = bPtr->objectValue();
1076 Q_ASSERT_X(objValue, className, "could not recover objectValue");
1077 objValue->setName(toString(qualifiedId: el->qualifiedTypeNameId));
1078
1079 if (m_enableScriptExpressions) {
1080 auto qmlObjectType = makeGenericScriptElement(ast: el->qualifiedTypeNameId, kind: DomType::ScriptType);
1081 qmlObjectType->insertChild(name: Fields::typeName,
1082 v: fieldMemberExpressionForQualifiedId(qualifiedId: el->qualifiedTypeNameId));
1083 objValue->setNameIdentifiers(finalizeScriptExpression(
1084 element: ScriptElementVariant::fromElement(element: qmlObjectType),
1085 pathFromOwner: bPathFromOwner.field(name: Fields::value).field(name: Fields::nameIdentifiers), ownerFileLocations: rootMap));
1086 }
1087
1088 objValue->addPrototypePath(prototypePath: Paths::lookupTypePath(name: objValue->name()));
1089 pushEl(p: bPathFromOwner.field(name: Fields::value), it: *objValue, n: el->initializer);
1090 if (m_enableScriptExpressions && el->initializer) {
1091 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: LeftBraceRegion,
1092 loc: el->initializer->lbraceToken);
1093 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: RightBraceRegion,
1094 loc: el->initializer->rbraceToken);
1095 }
1096 return true;
1097}
1098
1099void QQmlDomAstCreator::endVisit(AST::UiObjectBinding *)
1100{
1101 QmlObject &objValue = current<QmlObject>();
1102 QmlObject &containingObj = current<QmlObject>(idx: 1);
1103 Binding &b = std::get<Binding>(v&: currentNode(i: 1).value);
1104 QmlObject *objPtr = b.objectValue();
1105 Q_ASSERT(objPtr);
1106 *objPtr = objValue;
1107 index_type idx = currentNodeEl(i: 1).path.last().headIndex();
1108 Binding *bPtr = valueFromMultimap(mmap&: containingObj.m_bindings, key: b.name(), idx);
1109 Q_ASSERT(bPtr);
1110 *bPtr = b;
1111 removeCurrentNode(expectedType: DomType::QmlObject);
1112 removeCurrentNode(expectedType: DomType::Binding);
1113}
1114
1115bool QQmlDomAstCreator::visit(AST::UiScriptBinding *el)
1116{
1117 ++m_nestedFunctionDepth;
1118 QStringView code = qmlFilePtr->code();
1119 SourceLocation loc = combineLocations(n: el->statement);
1120 auto script = std::make_shared<ScriptExpression>(
1121 args: code.mid(pos: loc.offset, n: loc.length), args: qmlFilePtr->engine(), args&: el->statement,
1122 args: qmlFilePtr->astComments(), args: ScriptExpression::ExpressionType::BindingExpression, args&: loc);
1123 Binding bindingV(toString(qualifiedId: el->qualifiedId), script, BindingType::Normal);
1124 Binding *bindingPtr = nullptr;
1125 Id *idPtr = nullptr;
1126 Path pathFromOwner;
1127 if (bindingV.name() == u"id") {
1128 Node *exp = script->ast();
1129 if (ExpressionStatement *eStat = cast<ExpressionStatement *>(ast: script->ast()))
1130 exp = eStat->expression;
1131 if (IdentifierExpression *iExp = cast<IdentifierExpression *>(ast: exp)) {
1132 QmlStackElement &containingObjectEl = currentEl<QmlObject>();
1133 QmlObject &containingObject = std::get<QmlObject>(v&: containingObjectEl.item.value);
1134 QString idName = iExp->name.toString();
1135 Id idVal(idName, qmlFile.canonicalPath().path(toAdd: containingObject.pathFromOwner()));
1136 idVal.value = script;
1137 containingObject.setIdStr(idName);
1138 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, region: IdTokenRegion,
1139 loc: combineLocations(n: el->qualifiedId));
1140 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, region: IdColonTokenRegion,
1141 loc: el->colonToken);
1142 FileLocations::addRegion(fLoc: containingObjectEl.fileLocations, region: IdNameRegion,
1143 loc: combineLocations(n: el->statement));
1144 QmlComponent &comp = current<QmlComponent>();
1145 pathFromOwner = comp.addId(id: idVal, option: AddOption::KeepExisting, idPtr: &idPtr);
1146 QRegularExpression idRe(QRegularExpression::anchoredPattern(
1147 QStringLiteral(uR"([[:lower:]][[:lower:][:upper:]0-9_]*)")));
1148 auto m = idRe.matchView(subjectView: iExp->name);
1149 if (!m.hasMatch()) {
1150 qmlFile.addError(msg: std::move(
1151 astParseErrors()
1152 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
1153 "followed by letters, numbers or underscore, not %1")
1154 .arg(a: iExp->name))
1155 .withPath(pathFromOwner)));
1156 }
1157 } else {
1158 pathFromOwner =
1159 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
1160 Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
1161 qmlFile.addError(msg: std::move(
1162 astParseErrors()
1163 .warning(message: tr(sourceText: "id attributes should only be a lower case letter "
1164 "followed by letters, numbers or underscore, not %1 "
1165 "%2, assuming they refer to a property")
1166 .arg(args: script->code(), args: script->astRelocatableDump()))
1167 .withPath(pathFromOwner)));
1168 }
1169 } else {
1170 pathFromOwner =
1171 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
1172 QmlStackElement &containingObjectEl = currentEl<QmlObject>();
1173 // remove the containingObjectEl.path prefix from pathFromOwner
1174 Path pathFromContainingObject = pathFromOwner.mid(offset: containingObjectEl.path.length());
1175 auto bindingFileLocation =
1176 FileLocations::ensure(base: containingObjectEl.fileLocations, basePath: pathFromContainingObject);
1177 FileLocations::addRegion(fLoc: bindingFileLocation, region: IdentifierRegion,
1178 loc: el->qualifiedId->identifierToken);
1179 FileLocations::addRegion(fLoc: bindingFileLocation, region: ColonTokenRegion, loc: el->colonToken);
1180
1181 setBindingIdentifiers(pathFromOwner, identifiers: el->qualifiedId, bindingPtr);
1182
1183 Q_ASSERT_X(bindingPtr, className, "binding could not be retrieved");
1184 }
1185 if (bindingPtr)
1186 pushEl(p: pathFromOwner, it: *bindingPtr, n: el);
1187 else if (idPtr)
1188 pushEl(p: pathFromOwner, it: *idPtr, n: el);
1189 else
1190 Q_UNREACHABLE();
1191 loadAnnotations(el);
1192 // avoid duplicate colon location for id?
1193 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: ColonTokenRegion, loc: el->colonToken);
1194 return true;
1195}
1196
1197void QQmlDomAstCreator::setScriptExpression (const std::shared_ptr<ScriptExpression>& value)
1198{
1199 if (m_enableScriptExpressions
1200 && (scriptNodeStack.size() != 1 || currentScriptNodeEl().isList()))
1201 Q_SCRIPTELEMENT_DISABLE();
1202 if (m_enableScriptExpressions) {
1203 FileLocations::Tree valueLoc = FileLocations::ensure(base: currentNodeEl().fileLocations,
1204 basePath: Path().field(name: Fields::value));
1205 value->setScriptElement(finalizeScriptExpression(element: currentScriptNodeEl().takeVariant(),
1206 pathFromOwner: Path().field(name: Fields::scriptElement),
1207 ownerFileLocations: valueLoc));
1208 removeCurrentScriptNode(expectedType: {});
1209 }
1210};
1211
1212void QQmlDomAstCreator::endVisit(AST::UiScriptBinding *)
1213{
1214 --m_nestedFunctionDepth;
1215 DomValue &lastEl = currentNode();
1216 index_type idx = currentIndex();
1217 if (lastEl.kind == DomType::Binding) {
1218 Binding &b = std::get<Binding>(v&: lastEl.value);
1219
1220 setScriptExpression(b.scriptExpressionValue());
1221
1222 QmlObject &containingObject = current<QmlObject>();
1223 Binding *bPtr = valueFromMultimap(mmap&: containingObject.m_bindings, key: b.name(), idx);
1224 Q_ASSERT(bPtr);
1225 *bPtr = b;
1226 } else if (lastEl.kind == DomType::Id) {
1227 Id &id = std::get<Id>(v&: lastEl.value);
1228
1229 setScriptExpression(id.value);
1230
1231 QmlComponent &comp = current<QmlComponent>();
1232 Id *idPtr = valueFromMultimap(mmap&: comp.m_ids, key: id.name, idx);
1233 *idPtr = id;
1234 } else {
1235 Q_UNREACHABLE();
1236 }
1237
1238 // there should be no more uncollected script elements
1239 if (m_enableScriptExpressions && !scriptNodeStack.empty()) {
1240 Q_SCRIPTELEMENT_DISABLE();
1241 }
1242 removeCurrentNode(expectedType: {});
1243}
1244
1245bool QQmlDomAstCreator::visit(AST::UiArrayBinding *el)
1246{
1247 QList<QmlObject> value;
1248 Binding bindingV(toString(qualifiedId: el->qualifiedId), value, BindingType::Normal);
1249 Binding *bindingPtr;
1250 Path bindingPathFromOwner =
1251 current<QmlObject>().addBinding(binding: bindingV, option: AddOption::KeepExisting, bPtr: &bindingPtr);
1252 if (bindingV.name() == u"id")
1253 qmlFile.addError(msg: std::move(
1254 astParseErrors()
1255 .error(message: tr(sourceText: "id attributes should have only simple strings as values"))
1256 .withPath(bindingPathFromOwner)));
1257
1258 setBindingIdentifiers(pathFromOwner: bindingPathFromOwner, identifiers: el->qualifiedId, bindingPtr);
1259
1260 pushEl(p: bindingPathFromOwner, it: *bindingPtr, n: el);
1261 FileLocations::addRegion(fLoc: currentNodeEl().fileLocations, region: ColonTokenRegion, loc: el->colonToken);
1262 loadAnnotations(el);
1263 FileLocations::Tree arrayList =
1264 createMap(base: currentNodeEl().fileLocations, p: Path::Field(s: Fields::value), n: nullptr);
1265 FileLocations::addRegion(fLoc: arrayList, region: LeftBracketRegion, loc: el->lbracketToken);
1266 FileLocations::addRegion(fLoc: arrayList, region: RightBracketRegion, loc: el->rbracketToken);
1267 arrayBindingLevels.append(t: nodeStack.size());
1268 return true;
1269}
1270
1271void QQmlDomAstCreator::endVisit(AST::UiArrayBinding *)
1272{
1273 index_type idx = currentIndex();
1274 Binding &b = std::get<Binding>(v&: currentNode().value);
1275 Binding *bPtr = valueFromMultimap(mmap&: current<QmlObject>().m_bindings, key: b.name(), idx);
1276 *bPtr = b;
1277 arrayBindingLevels.removeLast();
1278 removeCurrentNode(expectedType: DomType::Binding);
1279}
1280
1281void QQmlDomAstCreator::endVisit(AST::ArgumentList *list)
1282{
1283 endVisitForLists(list);
1284}
1285
1286bool QQmlDomAstCreator::visit(AST::UiParameterList *)
1287{
1288 return false; // do not create script node for Ui stuff
1289}
1290
1291void QQmlDomAstCreator::endVisit(AST::PatternElementList *list)
1292{
1293 endVisitForLists<AST::PatternElementList>(list, scriptElementsPerEntry: [](AST::PatternElementList *current) {
1294 int toCollect = 0;
1295 toCollect += bool(current->elision);
1296 toCollect += bool(current->element);
1297 return toCollect;
1298 });
1299}
1300
1301void QQmlDomAstCreator::endVisit(AST::PatternPropertyList *list)
1302{
1303 endVisitForLists(list);
1304}
1305
1306/*!
1307 \internal
1308 Implementing the logic of this method in \c QQmlDomAstCreator::visit(AST::UiQualifiedId *)
1309 would create scriptelements at places where there are not needed. This is mainly because
1310 UiQualifiedId's appears inside and outside of script parts.
1311*/
1312ScriptElementVariant QQmlDomAstCreator::scriptElementForQualifiedId(AST::UiQualifiedId *expression)
1313{
1314 auto id = std::make_shared<ScriptElements::IdentifierExpression>(
1315 args: expression->firstSourceLocation(), args: expression->lastSourceLocation());
1316 id->setName(expression->toString());
1317
1318 return ScriptElementVariant::fromElement(element: id);
1319}
1320
1321bool QQmlDomAstCreator::visit(AST::UiQualifiedId *)
1322{
1323 if (!m_enableScriptExpressions)
1324 return false;
1325
1326 return false;
1327}
1328
1329bool QQmlDomAstCreator::visit(AST::UiEnumDeclaration *el)
1330{
1331 EnumDecl eDecl;
1332 eDecl.setName(el->name.toString());
1333 EnumDecl *ePtr;
1334 Path enumPathFromOwner =
1335 current<QmlComponent>().addEnumeration(enumeration: eDecl, option: AddOption::KeepExisting, ePtr: &ePtr);
1336 pushEl(p: enumPathFromOwner, it: *ePtr, n: el);
1337 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: EnumKeywordRegion, loc: el->enumToken);
1338 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: IdentifierRegion, loc: el->identifierToken);
1339 loadAnnotations(el);
1340 return true;
1341}
1342
1343void QQmlDomAstCreator::endVisit(AST::UiEnumDeclaration *)
1344{
1345 EnumDecl &e = std::get<EnumDecl>(v&: currentNode().value);
1346 EnumDecl *ePtr =
1347 valueFromMultimap(mmap&: current<QmlComponent>().m_enumerations, key: e.name(), idx: currentIndex());
1348 Q_ASSERT(ePtr);
1349 *ePtr = e;
1350 removeCurrentNode(expectedType: DomType::EnumDecl);
1351}
1352
1353bool QQmlDomAstCreator::visit(AST::UiEnumMemberList *el)
1354{
1355 EnumItem it(el->member.toString(), el->value,
1356 el->valueToken.isValid() ? EnumItem::ValueKind::ExplicitValue
1357 : EnumItem::ValueKind::ImplicitValue);
1358 EnumDecl &eDecl = std::get<EnumDecl>(v&: currentNode().value);
1359 Path itPathFromDecl = eDecl.addValue(value: it);
1360 const auto map = createMap(k: DomType::EnumItem, p: itPathFromDecl, n: nullptr);
1361 FileLocations::addRegion(fLoc: map, region: MainRegion, loc: combine(l1: el->memberToken, l2: el->valueToken));
1362 if (el->memberToken.isValid())
1363 FileLocations::addRegion(fLoc: map, region: IdentifierRegion, loc: el->memberToken);
1364 if (el->valueToken.isValid())
1365 FileLocations::addRegion(fLoc: map, region: EnumValueRegion, loc: el->valueToken);
1366 return true;
1367}
1368
1369void QQmlDomAstCreator::endVisit(AST::UiEnumMemberList *el)
1370{
1371 Node::accept(node: el->next, visitor: this); // put other enum members at the same level as this one...
1372}
1373
1374bool QQmlDomAstCreator::visit(AST::UiInlineComponent *el)
1375{
1376 QStringList els = current<QmlComponent>().name().split(sep: QLatin1Char('.'));
1377 els.append(t: el->name.toString());
1378 QString cName = els.join(sep: QLatin1Char('.'));
1379 QmlComponent *compPtr;
1380 Path p = qmlFilePtr->addComponent(component: QmlComponent(cName), option: AddOption::KeepExisting, cPtr: &compPtr);
1381
1382 if (m_enableScriptExpressions) {
1383 auto inlineComponentType =
1384 makeGenericScriptElement(location: el->identifierToken, kind: DomType::ScriptType);
1385
1386 auto typeName = std::make_shared<ScriptElements::IdentifierExpression>(args&: el->identifierToken);
1387 typeName->setName(el->name);
1388 inlineComponentType->insertChild(name: Fields::typeName,
1389 v: ScriptElementVariant::fromElement(element: typeName));
1390 compPtr->setNameIdentifiers(
1391 finalizeScriptExpression(element: ScriptElementVariant::fromElement(element: inlineComponentType),
1392 pathFromOwner: p.field(name: Fields::nameIdentifiers), ownerFileLocations: rootMap));
1393 }
1394
1395 pushEl(p, it: *compPtr, n: el);
1396 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: ComponentKeywordRegion,
1397 loc: el->componentToken);
1398 FileLocations::addRegion(fLoc: nodeStack.last().fileLocations, region: IdentifierRegion, loc: el->identifierToken);
1399 loadAnnotations(el);
1400 return true;
1401}
1402
1403void QQmlDomAstCreator::endVisit(AST::UiInlineComponent *)
1404{
1405 QmlComponent &component = std::get<QmlComponent>(v&: currentNode().value);
1406 QStringList nameEls = component.name().split(sep: QChar::fromLatin1(c: '.'));
1407 QString key = nameEls.mid(pos: 1).join(sep: QChar::fromLatin1(c: '.'));
1408 QmlComponent *cPtr = valueFromMultimap(mmap&: qmlFilePtr->lazyMembers().m_components, key, idx: currentIndex());
1409 Q_ASSERT(cPtr);
1410 *cPtr = component;
1411 removeCurrentNode(expectedType: DomType::QmlComponent);
1412}
1413
1414bool QQmlDomAstCreator::visit(UiRequired *el)
1415{
1416 PropertyDefinition pDef;
1417 pDef.name = el->name.toString();
1418 pDef.isRequired = true;
1419 PropertyDefinition *pDefPtr;
1420 Path pathFromOwner =
1421 current<QmlObject>().addPropertyDef(propertyDef: pDef, option: AddOption::KeepExisting, pDef: &pDefPtr);
1422 createMap(k: DomType::PropertyDefinition, p: pathFromOwner, n: el);
1423 return false;
1424}
1425
1426bool QQmlDomAstCreator::visit(AST::UiAnnotation *el)
1427{
1428 QmlObject a;
1429 a.setName(QStringLiteral(u"@") + toString(qualifiedId: el->qualifiedTypeNameId));
1430 // add annotation prototype?
1431 DomValue &containingElement = currentNode();
1432 Path pathFromOwner;
1433 QmlObject *aPtr = nullptr;
1434 switch (containingElement.kind) {
1435 case DomType::QmlObject:
1436 pathFromOwner = std::get<QmlObject>(v&: containingElement.value).addAnnotation(annotation: a, aPtr: &aPtr);
1437 break;
1438 case DomType::Binding:
1439 pathFromOwner = std::get<Binding>(v&: containingElement.value)
1440 .addAnnotation(selfPathFromOwner: currentNodeEl().path, a, aPtr: &aPtr);
1441 break;
1442 case DomType::Id:
1443 pathFromOwner =
1444 std::get<Id>(v&: containingElement.value).addAnnotation(selfPathFromOwner: currentNodeEl().path, ann: a, aPtr: &aPtr);
1445 break;
1446 case DomType::PropertyDefinition:
1447 pathFromOwner = std::get<PropertyDefinition>(v&: containingElement.value)
1448 .addAnnotation(selfPathFromOwner: currentNodeEl().path, annotation: a, aPtr: &aPtr);
1449 break;
1450 case DomType::MethodInfo:
1451 pathFromOwner = std::get<MethodInfo>(v&: containingElement.value)
1452 .addAnnotation(selfPathFromOwner: currentNodeEl().path, annotation: a, aPtr: &aPtr);
1453 break;
1454 default:
1455 qCWarning(domLog) << "Unexpected container object for annotation:"
1456 << domTypeToString(k: containingElement.kind);
1457 Q_UNREACHABLE();
1458 }
1459 pushEl(p: pathFromOwner, it: *aPtr, n: el);
1460 return true;
1461}
1462
1463void QQmlDomAstCreator::endVisit(AST::UiAnnotation *)
1464{
1465 DomValue &containingElement = currentNode(i: 1);
1466 Path pathFromOwner;
1467 QmlObject &a = std::get<QmlObject>(v&: currentNode().value);
1468 switch (containingElement.kind) {
1469 case DomType::QmlObject:
1470 std::get<QmlObject>(v&: containingElement.value).m_annotations[currentIndex()] = a;
1471 break;
1472 case DomType::Binding:
1473 std::get<Binding>(v&: containingElement.value).m_annotations[currentIndex()] = a;
1474 break;
1475 case DomType::Id:
1476 std::get<Id>(v&: containingElement.value).annotations[currentIndex()] = a;
1477 break;
1478 case DomType::PropertyDefinition:
1479 std::get<PropertyDefinition>(v&: containingElement.value).annotations[currentIndex()] = a;
1480 break;
1481 case DomType::MethodInfo:
1482 std::get<MethodInfo>(v&: containingElement.value).annotations[currentIndex()] = a;
1483 break;
1484 default:
1485 Q_UNREACHABLE();
1486 }
1487 removeCurrentNode(expectedType: DomType::QmlObject);
1488}
1489
1490void QQmlDomAstCreator::throwRecursionDepthError()
1491{
1492 qmlFile.addError(msg: astParseErrors().error(
1493 message: tr(sourceText: "Maximum statement or expression depth exceeded in QmlDomAstCreator")));
1494}
1495
1496void QQmlDomAstCreator::endVisit(AST::StatementList *list)
1497{
1498 endVisitForLists(list);
1499}
1500
1501bool QQmlDomAstCreator::visit(AST::BinaryExpression *)
1502{
1503 if (!m_enableScriptExpressions)
1504 return false;
1505
1506 return true;
1507}
1508
1509void QQmlDomAstCreator::endVisit(AST::BinaryExpression *exp)
1510{
1511 if (!m_enableScriptExpressions)
1512 return;
1513
1514 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: exp);
1515 current->addLocation(region: OperatorTokenRegion, location: exp->operatorToken);
1516 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1517 current->setRight(currentScriptNodeEl().takeVariant());
1518 removeCurrentScriptNode(expectedType: {});
1519 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1520 current->setLeft(currentScriptNodeEl().takeVariant());
1521 removeCurrentScriptNode(expectedType: {});
1522
1523 pushScriptElement(element: current);
1524}
1525
1526bool QQmlDomAstCreator::visit(AST::Block *)
1527{
1528 if (!m_enableScriptExpressions)
1529 return false;
1530
1531 return true;
1532}
1533
1534void QQmlDomAstCreator::endVisit(AST::Block *block)
1535{
1536 if (!m_enableScriptExpressions)
1537 return;
1538
1539 auto current = makeScriptElement<ScriptElements::BlockStatement>(ast: block);
1540
1541 if (block->statements) {
1542 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
1543 current->setStatements(currentScriptNodeEl().takeList());
1544 removeCurrentScriptNode(expectedType: DomType::List);
1545 }
1546
1547 pushScriptElement(element: current);
1548}
1549
1550bool QQmlDomAstCreator::visit(AST::ForStatement *)
1551{
1552 if (!m_enableScriptExpressions)
1553 return false;
1554
1555 return true;
1556}
1557
1558void QQmlDomAstCreator::endVisit(AST::ForStatement *forStatement)
1559{
1560 if (!m_enableScriptExpressions)
1561 return;
1562
1563 auto current = makeScriptElement<ScriptElements::ForStatement>(ast: forStatement);
1564 current->addLocation(region: FileLocationRegion::ForKeywordRegion, location: forStatement->forToken);
1565 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: forStatement->lparenToken);
1566 current->addLocation(region: FileLocationRegion::FirstSemicolonTokenRegion,
1567 location: forStatement->firstSemicolonToken);
1568 current->addLocation(region: FileLocationRegion::SecondSemicolonRegion,
1569 location: forStatement->secondSemicolonToken);
1570 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: forStatement->rparenToken);
1571
1572 if (forStatement->statement) {
1573 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1574 current->setBody(currentScriptNodeEl().takeVariant());
1575 removeCurrentScriptNode(expectedType: std::nullopt);
1576 }
1577
1578 if (forStatement->expression) {
1579 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1580 current->setExpression(currentScriptNodeEl().takeVariant());
1581 removeCurrentScriptNode(expectedType: std::nullopt);
1582 }
1583
1584 if (forStatement->condition) {
1585 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1586 current->setCondition(currentScriptNodeEl().takeVariant());
1587 removeCurrentScriptNode(expectedType: std::nullopt);
1588 }
1589
1590 if (forStatement->declarations) {
1591 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
1592 auto variableDeclaration = makeGenericScriptElement(ast: forStatement->declarations,
1593 kind: DomType::ScriptVariableDeclaration);
1594
1595 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
1596 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern,
1597 newType: DomType::ScriptVariableDeclarationEntry);
1598 variableDeclaration->insertChild(name: Fields::declarations, v: std::move(list));
1599 removeCurrentScriptNode(expectedType: {});
1600
1601 current->setDeclarations(ScriptElementVariant::fromElement(element: variableDeclaration));
1602
1603 if (auto pe = forStatement->declarations->declaration;
1604 pe && pe->declarationKindToken.isValid()) {
1605 current->addLocation(region: FileLocationRegion::TypeIdentifierRegion,
1606 location: pe->declarationKindToken);
1607 }
1608 }
1609
1610 if (forStatement->initialiser) {
1611 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1612 current->setInitializer(currentScriptNodeEl().takeVariant());
1613 removeCurrentScriptNode(expectedType: std::nullopt);
1614 }
1615 pushScriptElement(element: current);
1616}
1617
1618bool QQmlDomAstCreator::visit(AST::IdentifierExpression *expression)
1619{
1620 if (!m_enableScriptExpressions)
1621 return false;
1622
1623 auto current = makeScriptElement<ScriptElements::IdentifierExpression>(ast: expression);
1624 current->setName(expression->name);
1625 pushScriptElement(element: current);
1626 return true;
1627}
1628
1629bool QQmlDomAstCreator::visit(AST::NumericLiteral *expression)
1630{
1631 if (!m_enableScriptExpressions)
1632 return false;
1633
1634 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1635 current->setLiteralValue(expression->value);
1636 pushScriptElement(element: current);
1637 return true;
1638}
1639
1640bool QQmlDomAstCreator::visit(AST::StringLiteral *expression)
1641{
1642 if (!m_enableScriptExpressions)
1643 return false;
1644
1645 pushScriptElement(element: makeStringLiteral(value: expression->value, ast: expression));
1646 return true;
1647}
1648
1649bool QQmlDomAstCreator::visit(AST::NullExpression *expression)
1650{
1651 if (!m_enableScriptExpressions)
1652 return false;
1653
1654 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1655 current->setLiteralValue(nullptr);
1656 pushScriptElement(element: current);
1657 return true;
1658}
1659
1660bool QQmlDomAstCreator::visit(AST::TrueLiteral *expression)
1661{
1662 if (!m_enableScriptExpressions)
1663 return false;
1664
1665 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1666 current->setLiteralValue(true);
1667 pushScriptElement(element: current);
1668 return true;
1669}
1670
1671bool QQmlDomAstCreator::visit(AST::FalseLiteral *expression)
1672{
1673 if (!m_enableScriptExpressions)
1674 return false;
1675
1676 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1677 current->setLiteralValue(false);
1678 pushScriptElement(element: current);
1679 return true;
1680}
1681
1682bool QQmlDomAstCreator::visit(AST::IdentifierPropertyName *expression)
1683{
1684 if (!m_enableScriptExpressions)
1685 return false;
1686
1687 auto current = makeScriptElement<ScriptElements::IdentifierExpression>(ast: expression);
1688 current->setName(expression->id);
1689 pushScriptElement(element: current);
1690 return true;
1691}
1692
1693bool QQmlDomAstCreator::visit(AST::StringLiteralPropertyName *expression)
1694{
1695 if (!m_enableScriptExpressions)
1696 return false;
1697
1698 pushScriptElement(element: makeStringLiteral(value: expression->id, ast: expression));
1699 return true;
1700}
1701
1702bool QQmlDomAstCreator::visit(AST::TypeAnnotation *)
1703{
1704 if (!m_enableScriptExpressions)
1705 return false;
1706
1707 // do nothing: the work is done in (end)visit(AST::Type*).
1708 return true;
1709}
1710
1711bool QQmlDomAstCreator::visit(AST::RegExpLiteral *literal)
1712{
1713 if (!m_enableScriptExpressions)
1714 return false;
1715
1716 auto current = makeGenericScriptElement(ast: literal, kind: DomType::ScriptRegExpLiteral);
1717 current->insertValue(name: Fields::regExpPattern, v: literal->pattern);
1718 current->insertValue(name: Fields::regExpFlags, v: literal->flags);
1719 pushScriptElement(element: current);
1720
1721 return true;
1722}
1723
1724bool QQmlDomAstCreator::visit(AST::ThisExpression *expression)
1725{
1726 if (!m_enableScriptExpressions)
1727 return false;
1728
1729 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptThisExpression);
1730 if (expression->thisToken.isValid())
1731 current->addLocation(region: ThisKeywordRegion, location: expression->thisToken);
1732 pushScriptElement(element: current);
1733 return true;
1734}
1735
1736bool QQmlDomAstCreator::visit(AST::SuperLiteral *expression)
1737{
1738 if (!m_enableScriptExpressions)
1739 return false;
1740
1741 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptSuperLiteral);
1742 if (expression->superToken.isValid())
1743 current->addLocation(region: SuperKeywordRegion, location: expression->superToken);
1744 pushScriptElement(element: current);
1745 return true;
1746}
1747
1748bool QQmlDomAstCreator::visit(AST::NumericLiteralPropertyName *expression)
1749{
1750 if (!m_enableScriptExpressions)
1751 return false;
1752
1753 auto current = makeScriptElement<ScriptElements::Literal>(ast: expression);
1754 current->setLiteralValue(expression->id);
1755 pushScriptElement(element: current);
1756 return true;
1757}
1758
1759bool QQmlDomAstCreator::visit(AST::ComputedPropertyName *)
1760{
1761 if (!m_enableScriptExpressions)
1762 return false;
1763
1764 // nothing to do, just forward the underlying expression without changing/wrapping it
1765 return true;
1766}
1767
1768template<typename T>
1769void QQmlDomAstCreator::endVisitForLists(T *list,
1770 const std::function<int(T *)> &scriptElementsPerEntry)
1771{
1772 if (!m_enableScriptExpressions)
1773 return;
1774
1775 auto current = makeScriptList(list);
1776 for (auto it = list; it; it = it->next) {
1777 const int entriesToCollect = scriptElementsPerEntry ? scriptElementsPerEntry(it) : 1;
1778 for (int i = 0; i < entriesToCollect; ++i) {
1779 Q_SCRIPTELEMENT_EXIT_IF(scriptNodeStack.isEmpty());
1780 auto last = scriptNodeStack.takeLast();
1781 if (last.isList())
1782 current.append(last.takeList());
1783 else
1784 current.append(last.takeVariant());
1785 }
1786 }
1787
1788 current.reverse();
1789 pushScriptElement(current);
1790}
1791
1792void QQmlDomAstCreator::endVisit(AST::VariableDeclarationList *list)
1793{
1794 endVisitForLists(list);
1795}
1796
1797bool QQmlDomAstCreator::visit(AST::Elision *list)
1798{
1799 if (!m_enableScriptExpressions)
1800 return false;
1801
1802 auto currentList = makeScriptList(ast: list);
1803
1804 for (auto it = list; it; it = it->next) {
1805 auto current = makeGenericScriptElement(location: it->commaToken, kind: DomType::ScriptElision);
1806 currentList.append(statement: ScriptElementVariant::fromElement(element: current));
1807 }
1808 pushScriptElement(element: currentList);
1809
1810 return false; // return false because we already iterated over the children using the custom
1811 // iteration above
1812}
1813
1814bool QQmlDomAstCreator::visit(AST::PatternElement *)
1815{
1816 if (!m_enableScriptExpressions)
1817 return false;
1818
1819 return true;
1820}
1821
1822/*!
1823 \internal
1824 Avoid code-duplication, reuse this code when doing endVisit on types inheriting from
1825 AST::PatternElement.
1826*/
1827void QQmlDomAstCreator::endVisitHelper(
1828 AST::PatternElement *pe,
1829 const std::shared_ptr<ScriptElements::GenericScriptElement> &current)
1830{
1831 if (pe->equalToken.isValid())
1832 current->addLocation(region: FileLocationRegion::EqualTokenRegion, location: pe->equalToken);
1833
1834 if (pe->identifierToken.isValid() && !pe->bindingIdentifier.isEmpty()) {
1835 auto identifier =
1836 std::make_shared<ScriptElements::IdentifierExpression>(args&: pe->identifierToken);
1837 identifier->setName(pe->bindingIdentifier);
1838 current->insertChild(name: Fields::identifier, v: ScriptElementVariant::fromElement(element: identifier));
1839 }
1840 if (pe->initializer) {
1841 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1842 current->insertChild(name: Fields::initializer, v: scriptNodeStack.last().takeVariant());
1843 scriptNodeStack.removeLast();
1844 }
1845 if (pe->typeAnnotation) {
1846 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1847 current->insertChild(name: Fields::type, v: scriptNodeStack.last().takeVariant());
1848 scriptNodeStack.removeLast();
1849 }
1850 if (pe->bindingTarget) {
1851 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1852 current->insertChild(name: Fields::bindingElement, v: scriptNodeStack.last().takeVariant());
1853 scriptNodeStack.removeLast();
1854 }
1855}
1856
1857void QQmlDomAstCreator::endVisit(AST::PatternElement *pe)
1858{
1859 if (!m_enableScriptExpressions)
1860 return;
1861
1862 auto element = makeGenericScriptElement(ast: pe, kind: DomType::ScriptPattern);
1863 endVisitHelper(pe, current: element);
1864 // check if helper disabled scriptexpressions
1865 if (!m_enableScriptExpressions)
1866 return;
1867
1868 pushScriptElement(element);
1869}
1870
1871bool QQmlDomAstCreator::visit(AST::IfStatement *)
1872{
1873 if (!m_enableScriptExpressions)
1874 return false;
1875
1876 return true;
1877}
1878
1879void QQmlDomAstCreator::endVisit(AST::IfStatement *ifStatement)
1880{
1881 if (!m_enableScriptExpressions)
1882 return;
1883
1884 auto current = makeScriptElement<ScriptElements::IfStatement>(ast: ifStatement);
1885 current->addLocation(region: LeftParenthesisRegion, location: ifStatement->lparenToken);
1886 current->addLocation(region: RightParenthesisRegion, location: ifStatement->rparenToken);
1887 current->addLocation(region: ElseKeywordRegion, location: ifStatement->elseToken);
1888 current->addLocation(region: IfKeywordRegion, location: ifStatement->ifToken);
1889
1890 if (ifStatement->ko) {
1891 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1892 current->setAlternative(scriptNodeStack.last().takeVariant());
1893 scriptNodeStack.removeLast();
1894 }
1895
1896 if (ifStatement->ok) {
1897 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1898 current->setConsequence(scriptNodeStack.last().takeVariant());
1899 scriptNodeStack.removeLast();
1900 }
1901 if (ifStatement->expression) {
1902 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1903 current->setCondition(scriptNodeStack.last().takeVariant());
1904 scriptNodeStack.removeLast();
1905 }
1906
1907 pushScriptElement(element: current);
1908}
1909
1910bool QQmlDomAstCreator::visit(AST::ReturnStatement *)
1911{
1912 if (!m_enableScriptExpressions)
1913 return false;
1914
1915 return true;
1916}
1917
1918void QQmlDomAstCreator::endVisit(AST::ReturnStatement *returnStatement)
1919{
1920 if (!m_enableScriptExpressions)
1921 return;
1922
1923 auto current = makeScriptElement<ScriptElements::ReturnStatement>(ast: returnStatement);
1924 current->addLocation(region: ReturnKeywordRegion, location: returnStatement->returnToken);
1925
1926 if (returnStatement->expression) {
1927 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1928 current->setExpression(currentScriptNodeEl().takeVariant());
1929 removeCurrentScriptNode(expectedType: {});
1930 }
1931
1932 pushScriptElement(element: current);
1933}
1934
1935bool QQmlDomAstCreator::visit(AST::YieldExpression *)
1936{
1937 if (!m_enableScriptExpressions)
1938 return false;
1939
1940 return true;
1941}
1942
1943void QQmlDomAstCreator::endVisit(AST::YieldExpression *yExpression)
1944{
1945 if (!m_enableScriptExpressions)
1946 return;
1947
1948 auto current = makeGenericScriptElement(ast: yExpression, kind: DomType::ScriptYieldExpression);
1949 current->addLocation(region: YieldKeywordRegion, location: yExpression->yieldToken);
1950
1951 if (yExpression->expression) {
1952 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1953 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
1954 removeCurrentScriptNode(expectedType: {});
1955 }
1956
1957 pushScriptElement(element: current);
1958}
1959
1960bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *)
1961{
1962 if (!m_enableScriptExpressions)
1963 return false;
1964
1965 return true;
1966}
1967
1968void QQmlDomAstCreator::endVisit(AST::FieldMemberExpression *expression)
1969{
1970 if (!m_enableScriptExpressions)
1971 return;
1972
1973 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: expression);
1974 current->setOp(ScriptElements::BinaryExpression::FieldMemberAccess);
1975 current->addLocation(region: FileLocationRegion::OperatorTokenRegion, location: expression->dotToken);
1976
1977 if (expression->base) {
1978 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
1979 current->setLeft(currentScriptNodeEl().takeVariant());
1980 removeCurrentScriptNode(expectedType: {});
1981 }
1982
1983 auto scriptIdentifier =
1984 std::make_shared<ScriptElements::IdentifierExpression>(args&: expression->identifierToken);
1985 scriptIdentifier->setName(expression->name);
1986 current->setRight(ScriptElementVariant::fromElement(element: scriptIdentifier));
1987
1988 pushScriptElement(element: current);
1989}
1990
1991bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *)
1992{
1993 if (!m_enableScriptExpressions)
1994 return false;
1995
1996 return true;
1997}
1998
1999void QQmlDomAstCreator::endVisit(AST::ArrayMemberExpression *expression)
2000{
2001 if (!m_enableScriptExpressions)
2002 return;
2003
2004 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: expression);
2005 current->setOp(ScriptElements::BinaryExpression::ArrayMemberAccess);
2006 current->addLocation(region: FileLocationRegion::OperatorTokenRegion, location: expression->lbracketToken);
2007
2008 if (expression->expression) {
2009 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2010 // if scriptNodeStack.last() is fieldmember expression, add expression to it instead of
2011 // creating new one
2012 current->setRight(currentScriptNodeEl().takeVariant());
2013 removeCurrentScriptNode(expectedType: {});
2014 }
2015
2016 if (expression->base) {
2017 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2018 current->setLeft(currentScriptNodeEl().takeVariant());
2019 removeCurrentScriptNode(expectedType: {});
2020 }
2021
2022 pushScriptElement(element: current);
2023}
2024
2025bool QQmlDomAstCreator::visit(AST::CallExpression *)
2026{
2027 if (!m_enableScriptExpressions)
2028 return false;
2029
2030 return true;
2031}
2032
2033void QQmlDomAstCreator::endVisit(AST::CallExpression *exp)
2034{
2035 if (!m_enableScriptExpressions)
2036 return;
2037
2038 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptCallExpression);
2039 current->addLocation(region: LeftParenthesisRegion, location: exp->lparenToken);
2040 current->addLocation(region: RightParenthesisRegion, location: exp->rparenToken);
2041
2042 if (exp->arguments) {
2043 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2044 current->insertChild(name: Fields::arguments, v: currentScriptNodeEl().takeList());
2045 removeCurrentScriptNode(expectedType: {});
2046 } else {
2047 // insert empty list
2048 current->insertChild(name: Fields::arguments,
2049 v: ScriptElements::ScriptList(exp->lparenToken, exp->rparenToken));
2050 }
2051
2052 if (exp->base) {
2053 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2054 current->insertChild(name: Fields::callee, v: currentScriptNodeEl().takeVariant());
2055 removeCurrentScriptNode(expectedType: {});
2056 }
2057
2058 pushScriptElement(element: current);
2059}
2060
2061bool QQmlDomAstCreator::visit(AST::ArrayPattern *)
2062{
2063 if (!m_enableScriptExpressions)
2064 return false;
2065
2066 return true;
2067}
2068
2069void QQmlDomAstCreator::endVisit(AST::ArrayPattern *exp)
2070{
2071 if (!m_enableScriptExpressions)
2072 return;
2073
2074 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptArray);
2075
2076 if (exp->elements) {
2077 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2078 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
2079 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern, newType: DomType::ScriptArrayEntry);
2080 current->insertChild(name: Fields::elements, v: std::move(list));
2081
2082 removeCurrentScriptNode(expectedType: {});
2083 } else {
2084 // insert empty list
2085 current->insertChild(name: Fields::elements,
2086 v: ScriptElements::ScriptList(exp->lbracketToken, exp->rbracketToken));
2087 }
2088
2089 pushScriptElement(element: current);
2090}
2091
2092bool QQmlDomAstCreator::visit(AST::ObjectPattern *)
2093{
2094 if (!m_enableScriptExpressions)
2095 return false;
2096
2097 return true;
2098}
2099
2100void QQmlDomAstCreator::endVisit(AST::ObjectPattern *exp)
2101{
2102 if (!m_enableScriptExpressions)
2103 return;
2104
2105 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptObject);
2106
2107 if (exp->properties) {
2108 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2109 current->insertChild(name: Fields::properties, v: currentScriptNodeEl().takeList());
2110 removeCurrentScriptNode(expectedType: {});
2111 } else {
2112 // insert empty list
2113 current->insertChild(name: Fields::properties,
2114 v: ScriptElements::ScriptList(exp->lbraceToken, exp->rbraceToken));
2115 }
2116
2117 pushScriptElement(element: current);
2118}
2119
2120bool QQmlDomAstCreator::visit(AST::PatternProperty *)
2121{
2122 if (!m_enableScriptExpressions)
2123 return false;
2124
2125 return true;
2126}
2127
2128void QQmlDomAstCreator::endVisit(AST::PatternProperty *exp)
2129{
2130 if (!m_enableScriptExpressions)
2131 return;
2132
2133 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptProperty);
2134
2135 // handle the stuff from PatternProperty's base class PatternElement
2136 endVisitHelper(pe: static_cast<PatternElement *>(exp), current);
2137
2138 // check if helper disabled scriptexpressions
2139 if (!m_enableScriptExpressions)
2140 return;
2141
2142 if (exp->name) {
2143 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2144 current->insertChild(name: Fields::name, v: currentScriptNodeEl().takeVariant());
2145 removeCurrentScriptNode(expectedType: {});
2146 }
2147
2148 pushScriptElement(element: current);
2149}
2150
2151bool QQmlDomAstCreator::visit(AST::VariableStatement *)
2152{
2153 if (!m_enableScriptExpressions)
2154 return false;
2155
2156 return true;
2157}
2158
2159void QQmlDomAstCreator::endVisit(AST::VariableStatement *statement)
2160{
2161 if (!m_enableScriptExpressions)
2162 return;
2163
2164 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptVariableDeclaration);
2165 current->addLocation(region: FileLocationRegion::TypeIdentifierRegion, location: statement->declarationKindToken);
2166
2167 if (statement->declarations) {
2168 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2169
2170 ScriptElements::ScriptList list = currentScriptNodeEl().takeList();
2171 list.replaceKindForGenericChildren(oldType: DomType::ScriptPattern,
2172 newType: DomType::ScriptVariableDeclarationEntry);
2173 current->insertChild(name: Fields::declarations, v: std::move(list));
2174
2175 removeCurrentScriptNode(expectedType: {});
2176 }
2177
2178 pushScriptElement(element: current);
2179}
2180
2181bool QQmlDomAstCreator::visit(AST::Type *)
2182{
2183 if (!m_enableScriptExpressions)
2184 return false;
2185
2186 return true;
2187}
2188
2189void QQmlDomAstCreator::endVisit(AST::Type *exp)
2190{
2191 if (!m_enableScriptExpressions)
2192 return;
2193
2194 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptType);
2195
2196 if (exp->typeArgument) {
2197 current->insertChild(name: Fields::typeArgumentName,
2198 v: fieldMemberExpressionForQualifiedId(qualifiedId: exp->typeArgument));
2199 current->addLocation(region: FileLocationRegion::IdentifierRegion, location: combineLocations(n: exp->typeArgument));
2200 }
2201
2202 if (exp->typeId) {
2203 current->insertChild(name: Fields::typeName, v: fieldMemberExpressionForQualifiedId(qualifiedId: exp->typeId));
2204 current->addLocation(region: FileLocationRegion::TypeIdentifierRegion, location: combineLocations(n: exp->typeId));
2205 }
2206
2207 pushScriptElement(element: current);
2208}
2209
2210bool QQmlDomAstCreator::visit(AST::DefaultClause *)
2211{
2212 if (!m_enableScriptExpressions)
2213 return false;
2214
2215 return true;
2216}
2217
2218void QQmlDomAstCreator::endVisit(AST::DefaultClause *exp)
2219{
2220 if (!m_enableScriptExpressions)
2221 return;
2222
2223 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptDefaultClause);
2224 current->addLocation(region: DefaultKeywordRegion, location: exp->defaultToken);
2225 current->addLocation(region: ColonTokenRegion, location: exp->colonToken);
2226
2227 if (exp->statements) {
2228 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2229 current->insertChild(name: Fields::statements, v: currentScriptNodeEl().takeList());
2230 removeCurrentScriptNode(expectedType: {});
2231 }
2232
2233 pushScriptElement(element: current);
2234}
2235
2236bool QQmlDomAstCreator::visit(AST::CaseClause *)
2237{
2238 if (!m_enableScriptExpressions)
2239 return false;
2240
2241 return true;
2242}
2243
2244void QQmlDomAstCreator::endVisit(AST::CaseClause *exp)
2245{
2246 if (!m_enableScriptExpressions)
2247 return;
2248
2249 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptCaseClause);
2250 current->addLocation(region: FileLocationRegion::CaseKeywordRegion, location: exp->caseToken);
2251 current->addLocation(region: FileLocationRegion::ColonTokenRegion, location: exp->colonToken);
2252
2253 if (exp->statements) {
2254 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2255 current->insertChild(name: Fields::statements, v: currentScriptNodeEl().takeList());
2256 removeCurrentScriptNode(expectedType: {});
2257 }
2258
2259 if (exp->expression) {
2260 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2261 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2262 removeCurrentScriptNode(expectedType: {});
2263 }
2264
2265 pushScriptElement(element: current);
2266}
2267
2268bool QQmlDomAstCreator::visit(AST::CaseClauses *)
2269{
2270 if (!m_enableScriptExpressions)
2271 return false;
2272
2273 return true;
2274}
2275
2276void QQmlDomAstCreator::endVisit(AST::CaseClauses *list)
2277{
2278 if (!m_enableScriptExpressions)
2279 return;
2280
2281 auto current = makeScriptList(ast: list);
2282
2283 for (auto it = list; it; it = it->next) {
2284 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2285 current.append(statement: scriptNodeStack.takeLast().takeVariant());
2286 }
2287
2288 current.reverse();
2289 pushScriptElement(element: current);
2290}
2291
2292bool QQmlDomAstCreator::visit(AST::CaseBlock *)
2293{
2294 if (!m_enableScriptExpressions)
2295 return false;
2296
2297 return true;
2298}
2299
2300void QQmlDomAstCreator::endVisit(AST::CaseBlock *exp)
2301{
2302 if (!m_enableScriptExpressions)
2303 return;
2304
2305 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptCaseBlock);
2306 current->addLocation(region: FileLocationRegion::LeftBraceRegion, location: exp->lbraceToken);
2307 current->addLocation(region: FileLocationRegion::RightBraceRegion, location: exp->rbraceToken);
2308
2309 if (exp->moreClauses) {
2310 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2311 current->insertChild(name: Fields::moreCaseClauses, v: currentScriptNodeEl().takeList());
2312 removeCurrentScriptNode(expectedType: {});
2313 }
2314
2315 if (exp->defaultClause) {
2316 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2317 current->insertChild(name: Fields::defaultClause, v: currentScriptNodeEl().takeVariant());
2318 removeCurrentScriptNode(expectedType: {});
2319 }
2320
2321 if (exp->clauses) {
2322 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
2323 current->insertChild(name: Fields::caseClauses, v: currentScriptNodeEl().takeList());
2324 removeCurrentScriptNode(expectedType: {});
2325 }
2326 pushScriptElement(element: current);
2327}
2328
2329bool QQmlDomAstCreator::visit(AST::SwitchStatement *)
2330{
2331 if (!m_enableScriptExpressions)
2332 return false;
2333
2334 return true;
2335}
2336
2337void QQmlDomAstCreator::endVisit(AST::SwitchStatement *exp)
2338{
2339 if (!m_enableScriptExpressions)
2340 return;
2341
2342 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptSwitchStatement);
2343 current->addLocation(region: FileLocationRegion::SwitchKeywordRegion, location: exp->switchToken);
2344 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken);
2345 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken);
2346
2347 if (exp->block) {
2348 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2349 current->insertChild(name: Fields::caseBlock, v: currentScriptNodeEl().takeVariant());
2350 removeCurrentScriptNode(expectedType: {});
2351 }
2352 if (exp->expression) {
2353 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2354 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2355 removeCurrentScriptNode(expectedType: {});
2356 }
2357
2358 pushScriptElement(element: current);
2359}
2360
2361bool QQmlDomAstCreator::visit(AST::WhileStatement *)
2362{
2363 if (!m_enableScriptExpressions)
2364 return false;
2365
2366 return true;
2367}
2368
2369void QQmlDomAstCreator::endVisit(AST::WhileStatement *exp)
2370{
2371 if (!m_enableScriptExpressions)
2372 return;
2373
2374 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptWhileStatement);
2375 current->addLocation(region: FileLocationRegion::WhileKeywordRegion, location: exp->whileToken);
2376 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken);
2377 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken);
2378
2379 if (exp->statement) {
2380 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2381 current->insertChild(name: Fields::body, v: currentScriptNodeEl().takeVariant());
2382 removeCurrentScriptNode(expectedType: {});
2383 }
2384
2385 if (exp->expression) {
2386 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2387 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2388 removeCurrentScriptNode(expectedType: {});
2389 }
2390
2391 pushScriptElement(element: current);
2392}
2393
2394bool QQmlDomAstCreator::visit(AST::DoWhileStatement *)
2395{
2396 if (!m_enableScriptExpressions)
2397 return false;
2398
2399 return true;
2400}
2401
2402void QQmlDomAstCreator::endVisit(AST::DoWhileStatement *exp)
2403{
2404 if (!m_enableScriptExpressions)
2405 return;
2406
2407 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptDoWhileStatement);
2408 current->addLocation(region: FileLocationRegion::DoKeywordRegion, location: exp->doToken);
2409 current->addLocation(region: FileLocationRegion::WhileKeywordRegion, location: exp->whileToken);
2410 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken);
2411 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken);
2412
2413 if (exp->expression) {
2414 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2415 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2416 removeCurrentScriptNode(expectedType: {});
2417 }
2418
2419 if (exp->statement) {
2420 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2421 current->insertChild(name: Fields::body, v: currentScriptNodeEl().takeVariant());
2422 removeCurrentScriptNode(expectedType: {});
2423 }
2424
2425 pushScriptElement(element: current);
2426}
2427
2428bool QQmlDomAstCreator::visit(AST::ForEachStatement *)
2429{
2430 if (!m_enableScriptExpressions)
2431 return false;
2432
2433 return true;
2434}
2435
2436void QQmlDomAstCreator::endVisit(AST::ForEachStatement *exp)
2437{
2438 if (!m_enableScriptExpressions)
2439 return;
2440
2441 auto current = makeGenericScriptElement(ast: exp, kind: DomType::ScriptForEachStatement);
2442 current->addLocation(region: FileLocationRegion::ForKeywordRegion, location: exp->forToken);
2443 current->addLocation(region: FileLocationRegion::InOfTokenRegion, location: exp->inOfToken);
2444 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken);
2445 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken);
2446
2447 if (exp->statement) {
2448 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2449 current->insertChild(name: Fields::body, v: currentScriptNodeEl().takeVariant());
2450 removeCurrentScriptNode(expectedType: {});
2451 }
2452 if (exp->expression) {
2453 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2454 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2455 removeCurrentScriptNode(expectedType: {});
2456 }
2457
2458 if (exp->lhs) {
2459 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2460 current->insertChild(name: Fields::bindingElement, v: currentScriptNodeEl().takeVariant());
2461 removeCurrentScriptNode(expectedType: {});
2462
2463 if (auto pe = AST::cast<PatternElement *>(ast: exp->lhs);
2464 pe && pe->declarationKindToken.isValid()) {
2465 current->addLocation(region: FileLocationRegion::TypeIdentifierRegion,
2466 location: pe->declarationKindToken);
2467 }
2468 }
2469
2470 pushScriptElement(element: current);
2471}
2472
2473
2474bool QQmlDomAstCreator::visit(AST::ClassExpression *)
2475{
2476 // TODO: Add support for js expressions in classes
2477 // For now, turning off explicitly to avoid unwanted problems
2478 if (m_enableScriptExpressions)
2479 Q_SCRIPTELEMENT_DISABLE();
2480 return true;
2481}
2482
2483void QQmlDomAstCreator::endVisit(AST::ClassExpression *)
2484{
2485}
2486
2487void QQmlDomAstCreator::endVisit(AST::TaggedTemplate *literal)
2488{
2489 if (!m_enableScriptExpressions)
2490 return;
2491 auto current = makeGenericScriptElement(ast: literal, kind: DomType::ScriptTaggedTemplate);
2492 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2493 current->insertChild(name: Fields::templateLiteral, v: scriptNodeStack.takeLast().takeVariant());
2494 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2495 current->insertChild(name: Fields::callee, v: scriptNodeStack.takeLast().takeVariant());
2496 pushScriptElement(element: current);
2497}
2498
2499void QQmlDomAstCreator::endVisit(AST::TemplateLiteral *literal)
2500{
2501 if (!m_enableScriptExpressions)
2502 return;
2503
2504 // AST::TemplateLiteral is a list and a TemplateLiteral at the same time:
2505 // in the Dom representation wrap the list into a separate TemplateLiteral Item.
2506 auto currentList = makeScriptList(ast: literal);
2507
2508 const auto children = [&literal]() {
2509 std::vector<AST::TemplateLiteral *> result;
2510 for (auto it = literal; it; it = it->next) {
2511 result.push_back(x: it);
2512 }
2513 return result;
2514 }();
2515 for (auto it = children.crbegin(); it != children.crend(); ++it) {
2516 if ((*it)->expression) {
2517 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2518 currentList.append(statement: scriptNodeStack.takeLast().takeVariant());
2519 }
2520 if (!(*it)->rawValue.isEmpty()) {
2521 auto currentExpression = makeGenericScriptElement(
2522 location: (*it)->literalToken, kind: DomType::ScriptTemplateStringPart);
2523 currentExpression->insertValue(name: Fields::value, v: (*it)->rawValue);
2524 currentList.append(statement: ScriptElementVariant::fromElement(element: currentExpression));
2525 }
2526 }
2527 currentList.reverse();
2528
2529 auto current = makeGenericScriptElement(ast: literal, kind: DomType::ScriptTemplateLiteral);
2530 current->insertChild(name: Fields::components, v: currentList);
2531 pushScriptElement(element: current);
2532}
2533
2534bool QQmlDomAstCreator::visit(AST::TryStatement *)
2535{
2536 return m_enableScriptExpressions;
2537}
2538
2539void QQmlDomAstCreator::endVisit(AST::TryStatement *statement)
2540{
2541 if (!m_enableScriptExpressions)
2542 return;
2543
2544 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptTryCatchStatement);
2545 current->addLocation(region: FileLocationRegion::TryKeywordRegion, location: statement->tryToken);
2546
2547 if (auto exp = statement->finallyExpression) {
2548 current->addLocation(region: FileLocationRegion::FinallyKeywordRegion, location: exp->finallyToken);
2549
2550 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2551 current->insertChild(name: Fields::finallyBlock, v: currentScriptNodeEl().takeVariant());
2552 removeCurrentScriptNode(expectedType: {});
2553 }
2554
2555 if (auto exp = statement->catchExpression) {
2556 current->addLocation(region: FileLocationRegion::CatchKeywordRegion, location: exp->catchToken);
2557 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken);
2558 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken);
2559
2560 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2561 current->insertChild(name: Fields::catchBlock, v: currentScriptNodeEl().takeVariant());
2562 removeCurrentScriptNode(expectedType: {});
2563 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2564 current->insertChild(name: Fields::catchParameter, v: currentScriptNodeEl().takeVariant());
2565 removeCurrentScriptNode(expectedType: {});
2566 }
2567
2568 if (statement->statement) {
2569 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2570 current->insertChild(name: Fields::block, v: currentScriptNodeEl().takeVariant());
2571 removeCurrentScriptNode(expectedType: {});
2572 }
2573
2574 pushScriptElement(element: current);
2575}
2576
2577bool QQmlDomAstCreator::visit(AST::Catch *)
2578{
2579 // handled in visit(AST::TryStatement* )
2580 return m_enableScriptExpressions;
2581}
2582
2583void QQmlDomAstCreator::endVisit(AST::Catch *)
2584{
2585 // handled in endVisit(AST::TryStatement* )
2586}
2587
2588bool QQmlDomAstCreator::visit(AST::Finally *)
2589{
2590 // handled in visit(AST::TryStatement* )
2591 return m_enableScriptExpressions;
2592}
2593
2594void QQmlDomAstCreator::endVisit(AST::Finally *)
2595{
2596 // handled in endVisit(AST::TryStatement* )
2597}
2598
2599bool QQmlDomAstCreator::visit(AST::ThrowStatement *)
2600{
2601 return m_enableScriptExpressions;
2602}
2603
2604void QQmlDomAstCreator::endVisit(AST::ThrowStatement *statement)
2605{
2606 if (!m_enableScriptExpressions)
2607 return;
2608
2609 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptThrowStatement);
2610 current->addLocation(region: FileLocationRegion::ThrowKeywordRegion, location: statement->throwToken);
2611
2612 if (statement->expression) {
2613 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2614 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2615 removeCurrentScriptNode(expectedType: {});
2616 }
2617
2618 pushScriptElement(element: current);
2619}
2620
2621bool QQmlDomAstCreator::visit(AST::LabelledStatement *)
2622{
2623 return m_enableScriptExpressions;
2624}
2625
2626void QQmlDomAstCreator::endVisit(AST::LabelledStatement *statement)
2627{
2628 if (!m_enableScriptExpressions)
2629 return;
2630
2631 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptLabelledStatement);
2632 current->addLocation(region: FileLocationRegion::ColonTokenRegion, location: statement->colonToken);
2633
2634 auto label = std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken);
2635 label->setName(statement->label);
2636 current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label));
2637
2638
2639 if (statement->statement) {
2640 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2641 current->insertChild(name: Fields::statement, v: currentScriptNodeEl().takeVariant());
2642 removeCurrentScriptNode(expectedType: {});
2643 }
2644
2645 pushScriptElement(element: current);
2646}
2647
2648bool QQmlDomAstCreator::visit(AST::BreakStatement *)
2649{
2650 return m_enableScriptExpressions;
2651}
2652
2653void QQmlDomAstCreator::endVisit(AST::BreakStatement *statement)
2654{
2655 if (!m_enableScriptExpressions)
2656 return;
2657
2658 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptBreakStatement);
2659 current->addLocation(region: FileLocationRegion::BreakKeywordRegion, location: statement->breakToken);
2660
2661 if (!statement->label.isEmpty()) {
2662 auto label =
2663 std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken);
2664 label->setName(statement->label);
2665 current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label));
2666 }
2667
2668 pushScriptElement(element: current);
2669}
2670
2671// note: thats for comma expressions
2672bool QQmlDomAstCreator::visit(AST::Expression *)
2673{
2674 return m_enableScriptExpressions;
2675}
2676
2677// note: thats for comma expressions
2678void QQmlDomAstCreator::endVisit(AST::Expression *commaExpression)
2679{
2680 if (!m_enableScriptExpressions)
2681 return;
2682
2683 auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: commaExpression);
2684 current->addLocation(region: OperatorTokenRegion, location: commaExpression->commaToken);
2685
2686 if (commaExpression->right) {
2687 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2688 current->setRight(currentScriptNodeEl().takeVariant());
2689 removeCurrentScriptNode(expectedType: {});
2690 }
2691
2692 if (commaExpression->left) {
2693 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2694 current->setLeft(currentScriptNodeEl().takeVariant());
2695 removeCurrentScriptNode(expectedType: {});
2696 }
2697
2698 pushScriptElement(element: current);
2699}
2700
2701bool QQmlDomAstCreator::visit(AST::ConditionalExpression *)
2702{
2703 return m_enableScriptExpressions;
2704}
2705
2706void QQmlDomAstCreator::endVisit(AST::ConditionalExpression *expression)
2707{
2708 if (!m_enableScriptExpressions)
2709 return;
2710
2711 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptConditionalExpression);
2712 current->addLocation(region: FileLocationRegion::QuestionMarkTokenRegion, location: expression->questionToken);
2713 current->addLocation(region: FileLocationRegion::ColonTokenRegion, location: expression->colonToken);
2714
2715 if (expression->ko) {
2716 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2717 current->insertChild(name: Fields::alternative, v: currentScriptNodeEl().takeVariant());
2718 removeCurrentScriptNode(expectedType: {});
2719 }
2720
2721 if (expression->ok) {
2722 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2723 current->insertChild(name: Fields::consequence, v: currentScriptNodeEl().takeVariant());
2724 removeCurrentScriptNode(expectedType: {});
2725 }
2726
2727 if (expression->expression) {
2728 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
2729 current->insertChild(name: Fields::condition, v: currentScriptNodeEl().takeVariant());
2730 removeCurrentScriptNode(expectedType: {});
2731 }
2732
2733 pushScriptElement(element: current);
2734}
2735
2736bool QQmlDomAstCreator::visit(AST::ContinueStatement *)
2737{
2738 return m_enableScriptExpressions;
2739}
2740
2741void QQmlDomAstCreator::endVisit(AST::ContinueStatement *statement)
2742{
2743 if (!m_enableScriptExpressions)
2744 return;
2745
2746 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptContinueStatement);
2747 current->addLocation(region: FileLocationRegion::ContinueKeywordRegion, location: statement->continueToken);
2748
2749 if (!statement->label.isEmpty()) {
2750 auto label =
2751 std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken);
2752 label->setName(statement->label);
2753 current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label));
2754 }
2755
2756 pushScriptElement(element: current);
2757}
2758
2759/*!
2760 \internal
2761 Helper to create unary expressions from AST nodes.
2762 \sa makeGenericScriptElement
2763 */
2764std::shared_ptr<ScriptElements::GenericScriptElement>
2765QQmlDomAstCreator::makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken,
2766 bool hasExpression, UnaryExpressionKind kind)
2767{
2768 const DomType type = [&kind]() {
2769 switch (kind) {
2770 case Prefix:
2771 return DomType::ScriptUnaryExpression;
2772 case Postfix:
2773 return DomType::ScriptPostExpression;
2774 }
2775 Q_UNREACHABLE_RETURN(DomType::ScriptUnaryExpression);
2776 }();
2777
2778 auto current = makeGenericScriptElement(ast: expression, kind: type);
2779 current->addLocation(region: FileLocationRegion::OperatorTokenRegion, location: operatorToken);
2780
2781 if (hasExpression) {
2782 if (!stackHasScriptVariant()) {
2783 Q_SCRIPTELEMENT_DISABLE();
2784 return {};
2785 }
2786 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
2787 removeCurrentScriptNode(expectedType: {});
2788 }
2789
2790 return current;
2791}
2792
2793bool QQmlDomAstCreator::visit(AST::UnaryMinusExpression *)
2794{
2795 return m_enableScriptExpressions;
2796}
2797
2798void QQmlDomAstCreator::endVisit(AST::UnaryMinusExpression *statement)
2799{
2800 if (!m_enableScriptExpressions)
2801 return;
2802
2803 auto current =
2804 makeUnaryExpression(expression: statement, operatorToken: statement->minusToken, hasExpression: statement->expression, kind: Prefix);
2805 if (!current)
2806 return;
2807
2808 pushScriptElement(element: current);
2809}
2810
2811bool QQmlDomAstCreator::visit(AST::UnaryPlusExpression *)
2812{
2813 return m_enableScriptExpressions;
2814}
2815
2816void QQmlDomAstCreator::endVisit(AST::UnaryPlusExpression *statement)
2817{
2818 if (!m_enableScriptExpressions)
2819 return;
2820
2821 auto current =
2822 makeUnaryExpression(expression: statement, operatorToken: statement->plusToken, hasExpression: statement->expression, kind: Prefix);
2823 if (!current)
2824 return;
2825
2826 pushScriptElement(element: current);
2827}
2828
2829bool QQmlDomAstCreator::visit(AST::TildeExpression *)
2830{
2831 return m_enableScriptExpressions;
2832}
2833
2834void QQmlDomAstCreator::endVisit(AST::TildeExpression *statement)
2835{
2836 if (!m_enableScriptExpressions)
2837 return;
2838
2839 auto current =
2840 makeUnaryExpression(expression: statement, operatorToken: statement->tildeToken, hasExpression: statement->expression, kind: Prefix);
2841 if (!current)
2842 return;
2843
2844 pushScriptElement(element: current);
2845}
2846
2847bool QQmlDomAstCreator::visit(AST::NotExpression *)
2848{
2849 return m_enableScriptExpressions;
2850}
2851
2852void QQmlDomAstCreator::endVisit(AST::NotExpression *statement)
2853{
2854 if (!m_enableScriptExpressions)
2855 return;
2856
2857 auto current =
2858 makeUnaryExpression(expression: statement, operatorToken: statement->notToken, hasExpression: statement->expression, kind: Prefix);
2859 if (!current)
2860 return;
2861
2862 pushScriptElement(element: current);
2863}
2864
2865bool QQmlDomAstCreator::visit(AST::TypeOfExpression *)
2866{
2867 return m_enableScriptExpressions;
2868}
2869
2870void QQmlDomAstCreator::endVisit(AST::TypeOfExpression *statement)
2871{
2872 if (!m_enableScriptExpressions)
2873 return;
2874
2875 auto current =
2876 makeUnaryExpression(expression: statement, operatorToken: statement->typeofToken, hasExpression: statement->expression, kind: Prefix);
2877 if (!current)
2878 return;
2879
2880 pushScriptElement(element: current);
2881}
2882
2883bool QQmlDomAstCreator::visit(AST::DeleteExpression *)
2884{
2885 return m_enableScriptExpressions;
2886}
2887
2888void QQmlDomAstCreator::endVisit(AST::DeleteExpression *statement)
2889{
2890 if (!m_enableScriptExpressions)
2891 return;
2892
2893 auto current =
2894 makeUnaryExpression(expression: statement, operatorToken: statement->deleteToken, hasExpression: statement->expression, kind: Prefix);
2895 if (!current)
2896 return;
2897
2898 pushScriptElement(element: current);
2899}
2900
2901bool QQmlDomAstCreator::visit(AST::VoidExpression *)
2902{
2903 return m_enableScriptExpressions;
2904}
2905
2906void QQmlDomAstCreator::endVisit(AST::VoidExpression *statement)
2907{
2908 if (!m_enableScriptExpressions)
2909 return;
2910
2911 auto current =
2912 makeUnaryExpression(expression: statement, operatorToken: statement->voidToken, hasExpression: statement->expression, kind: Prefix);
2913 if (!current)
2914 return;
2915
2916 pushScriptElement(element: current);
2917}
2918
2919bool QQmlDomAstCreator::visit(AST::PostDecrementExpression *)
2920{
2921 return m_enableScriptExpressions;
2922}
2923
2924void QQmlDomAstCreator::endVisit(AST::PostDecrementExpression *statement)
2925{
2926 if (!m_enableScriptExpressions)
2927 return;
2928
2929 auto current =
2930 makeUnaryExpression(expression: statement, operatorToken: statement->decrementToken, hasExpression: statement->base, kind: Postfix);
2931 if (!current)
2932 return;
2933
2934 pushScriptElement(element: current);
2935}
2936
2937bool QQmlDomAstCreator::visit(AST::PostIncrementExpression *)
2938{
2939 return m_enableScriptExpressions;
2940}
2941
2942void QQmlDomAstCreator::endVisit(AST::PostIncrementExpression *statement)
2943{
2944 if (!m_enableScriptExpressions)
2945 return;
2946
2947 auto current =
2948 makeUnaryExpression(expression: statement, operatorToken: statement->incrementToken, hasExpression: statement->base, kind: Postfix);
2949 if (!current)
2950 return;
2951
2952 pushScriptElement(element: current);
2953}
2954
2955bool QQmlDomAstCreator::visit(AST::PreIncrementExpression *)
2956{
2957 return m_enableScriptExpressions;
2958}
2959
2960void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement)
2961{
2962 if (!m_enableScriptExpressions)
2963 return;
2964
2965 auto current = makeUnaryExpression(expression: statement, operatorToken: statement->incrementToken, hasExpression: statement->expression,
2966 kind: Prefix);
2967 if (!current)
2968 return;
2969
2970 pushScriptElement(element: current);
2971}
2972
2973bool QQmlDomAstCreator::visit(AST::EmptyStatement *)
2974{
2975 return m_enableScriptExpressions;
2976}
2977
2978void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement)
2979{
2980 if (!m_enableScriptExpressions)
2981 return;
2982
2983 auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptEmptyStatement);
2984 current->addLocation(region: FileLocationRegion::SemicolonTokenRegion, location: statement->semicolonToken);
2985 pushScriptElement(element: current);
2986}
2987
2988bool QQmlDomAstCreator::visit(AST::NestedExpression *)
2989{
2990 return m_enableScriptExpressions;
2991}
2992
2993void QQmlDomAstCreator::endVisit(AST::NestedExpression *expression)
2994{
2995 if (!m_enableScriptExpressions)
2996 return;
2997
2998 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptParenthesizedExpression);
2999 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: expression->lparenToken);
3000 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: expression->rparenToken);
3001
3002 if (expression->expression) {
3003 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
3004 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
3005 removeCurrentScriptNode(expectedType: {});
3006 }
3007
3008 pushScriptElement(element: current);
3009}
3010
3011bool QQmlDomAstCreator::visit(AST::NewExpression *)
3012{
3013 return m_enableScriptExpressions;
3014}
3015
3016void QQmlDomAstCreator::endVisit(AST::NewExpression *expression)
3017{
3018 if (!m_enableScriptExpressions)
3019 return;
3020
3021 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptNewExpression);
3022 current->addLocation(region: FileLocationRegion::NewKeywordRegion, location: expression->newToken);
3023
3024 if (expression->expression) {
3025 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
3026 current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant());
3027 removeCurrentScriptNode(expectedType: {});
3028 }
3029
3030 pushScriptElement(element: current);
3031}
3032
3033bool QQmlDomAstCreator::visit(AST::NewMemberExpression *)
3034{
3035 return m_enableScriptExpressions;
3036}
3037
3038void QQmlDomAstCreator::endVisit(AST::NewMemberExpression *expression)
3039{
3040 if (!m_enableScriptExpressions)
3041 return;
3042
3043 auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptNewMemberExpression);
3044 current->addLocation(region: FileLocationRegion::NewKeywordRegion, location: expression->newToken);
3045 current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: expression->lparenToken);
3046 current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: expression->rparenToken);
3047
3048 if (expression->arguments) {
3049 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList());
3050 current->insertChild(name: Fields::arguments, v: scriptNodeStack.takeLast().takeList());
3051 }
3052 if (expression->base) {
3053 Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant());
3054 current->insertChild(name: Fields::base, v: scriptNodeStack.takeLast().takeVariant());
3055 }
3056
3057 pushScriptElement(element: current);
3058}
3059
3060bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *)
3061{
3062 return m_enableScriptExpressions;
3063}
3064
3065void QQmlDomAstCreator::endVisit(AST::PreDecrementExpression *statement)
3066{
3067 if (!m_enableScriptExpressions)
3068 return;
3069
3070 auto current = makeUnaryExpression(expression: statement, operatorToken: statement->decrementToken, hasExpression: statement->expression,
3071 kind: Prefix);
3072 if (!current)
3073 return;
3074
3075 pushScriptElement(element: current);
3076}
3077
3078static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile)
3079{
3080 auto top = qmlFile.top();
3081 if (!top) {
3082 return {};
3083 }
3084 auto domEnvironment = top.as<DomEnvironment>();
3085 if (!domEnvironment) {
3086 return {};
3087 }
3088 return domEnvironment;
3089}
3090
3091static QStringList qmldirFilesFrom(MutableDomItem &qmlFile)
3092{
3093 if (auto env = environmentFrom(qmlFile))
3094 return env->qmldirFiles();
3095
3096 return {};
3097}
3098
3099QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr &current,
3100 MutableDomItem &qmlFile,
3101 QQmlJSLogger *logger,
3102 QQmlJSImporter *importer)
3103 : m_root(current),
3104 m_logger(logger),
3105 m_importer(importer),
3106 m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory(
3107 localFile: m_logger->filePath(), mapper: m_importer->resourceFileMapper())),
3108 m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory,
3109 qmldirFilesFrom(qmlFile)),
3110 m_domCreator(qmlFile)
3111{
3112}
3113
3114#define X(name) \
3115 bool QQmlDomAstCreatorWithQQmlJSScope::visit(name *node) \
3116 { \
3117 return visitT(node); \
3118 } \
3119 void QQmlDomAstCreatorWithQQmlJSScope::endVisit(name *node) \
3120 { \
3121 endVisitT(node); \
3122 }
3123QQmlJSASTClassListToVisit
3124#undef X
3125
3126void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit()
3127{
3128 const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope;
3129 if (!m_domCreator.scriptNodeStack.isEmpty()) {
3130 auto topOfStack = m_domCreator.currentScriptNodeEl();
3131 switch (topOfStack.kind) {
3132 case DomType::ScriptBlockStatement:
3133 case DomType::ScriptForStatement:
3134 case DomType::ScriptForEachStatement:
3135 case DomType::ScriptDoWhileStatement:
3136 case DomType::ScriptWhileStatement:
3137 case DomType::List:
3138 m_domCreator.currentScriptNodeEl().setSemanticScope(scope);
3139 break;
3140 case DomType::ScriptFunctionExpression: {
3141 // Put the body's scope into the function expression: function expressions will contain
3142 // their parents scope instead of their own without this
3143 auto element = m_domCreator.currentScriptNodeEl().value;
3144 auto scriptElementVariant = std::get_if<ScriptElementVariant>(ptr: &element);
3145 if (!scriptElementVariant || !scriptElementVariant->data())
3146 break;
3147 scriptElementVariant->visit(visitor: [](auto &&e) {
3148 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
3149 if (e->kind() != DomType::ScriptFunctionExpression)
3150 return;
3151
3152 if constexpr (std::is_same_v<U,
3153 ScriptElement::PointerType<
3154 ScriptElements::GenericScriptElement>>) {
3155 if (auto bodyPtr = e->elementChild(Fields::body)) {
3156 const auto bodyScope = bodyPtr.base()->semanticScope();
3157 e->setSemanticScope(bodyScope);
3158 }
3159 }
3160 });
3161 break;
3162 }
3163
3164 // TODO: find which script elements also have a scope and implement them here
3165 default:
3166 break;
3167 };
3168 } else if (!m_domCreator.nodeStack.isEmpty()) {
3169 std::visit(
3170 visitor: [&scope](auto &&e) {
3171 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
3172 // TODO: find which dom elements also have a scope and implement them here
3173 if constexpr (std::is_same_v<U, QmlObject>) {
3174 e.setSemanticScope(scope);
3175 } else if constexpr (std::is_same_v<U, QmlComponent>) {
3176 e.setSemanticScope(scope);
3177 } else if constexpr (std::is_same_v<U, MethodInfo>) {
3178 if (e.body) {
3179 if (auto scriptElement = e.body->scriptElement()) {
3180 scriptElement.base()->setSemanticScope(scope);
3181 }
3182 }
3183 e.setSemanticScope(scope);
3184 }
3185 },
3186 variants&: m_domCreator.currentNodeEl().item.value);
3187 }
3188}
3189
3190void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit()
3191{
3192 const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope;
3193
3194 // depending whether the property definition has a binding, the property definition might be
3195 // either at the last position in the stack or at the position before the last position.
3196 if (m_domCreator.nodeStack.size() > 1
3197 && m_domCreator.nodeStack.last().item.kind == DomType::Binding) {
3198 std::visit(
3199 visitor: [&scope](auto &&e) {
3200 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
3201 if constexpr (std::is_same_v<U, PropertyDefinition>) {
3202 // Make sure to use the property definition scope instead of the binding
3203 // scope. If the current scope is a binding scope (this happens when the
3204 // property definition has a binding, like `property int i: 45` for
3205 // example), then the property definition scope is the parent of the current
3206 // scope.
3207 const bool usePropertyDefinitionScopeInsteadOfTheBindingScope =
3208 scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
3209 && scope->parentScope()
3210 && scope->parentScope()->scopeType() == QQmlSA::ScopeType::QMLScope;
3211 e.setSemanticScope(usePropertyDefinitionScopeInsteadOfTheBindingScope
3212 ? scope->parentScope()
3213 : scope);
3214 }
3215 },
3216 variants&: m_domCreator.currentNodeEl(i: 1).item.value);
3217 }
3218 if (m_domCreator.nodeStack.size() > 0) {
3219 std::visit(
3220 visitor: [&scope](auto &&e) {
3221 using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>;
3222 if constexpr (std::is_same_v<U, PropertyDefinition>) {
3223 e.setSemanticScope(scope);
3224 Q_ASSERT(e.semanticScope());
3225 } else if constexpr (std::is_same_v<U, MethodInfo>) {
3226 if (e.methodType == MethodInfo::Signal) {
3227 e.setSemanticScope(scope);
3228 }
3229 }
3230 },
3231 variants&: m_domCreator.currentNodeEl().item.value);
3232 }
3233}
3234
3235void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError()
3236{
3237}
3238
3239} // end namespace Dom
3240} // end namespace QQmlJS
3241
3242#undef Q_SCRIPTELEMENT_DISABLE
3243#undef Q_SCRIPTELEMENT_EXIT_IF
3244
3245QT_END_NAMESPACE
3246

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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