| 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 | |
| 31 | static 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 | |
| 52 | QT_BEGIN_NAMESPACE |
| 53 | namespace QQmlJS { |
| 54 | namespace Dom { |
| 55 | |
| 56 | using namespace AST; |
| 57 | |
| 58 | template<typename K, typename V> |
| 59 | V *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 | |
| 80 | static ErrorGroups astParseErrors() |
| 81 | { |
| 82 | static ErrorGroups errs = { .groups: { NewErrorGroup("Dom" ), NewErrorGroup("QmlFile" ), |
| 83 | NewErrorGroup("Parsing" ) } }; |
| 84 | return errs; |
| 85 | } |
| 86 | |
| 87 | static 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 | |
| 101 | static 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 | |
| 112 | SourceLocation combineLocations(SourceLocation s1, SourceLocation s2) |
| 113 | { |
| 114 | return combine(l1: s1, l2: s2); |
| 115 | } |
| 116 | |
| 117 | SourceLocation combineLocations(Node *n) |
| 118 | { |
| 119 | return combineLocations(s1: n->firstSourceLocation(), s2: n->lastSourceLocation()); |
| 120 | } |
| 121 | |
| 122 | static 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 | */ |
| 142 | static ScriptElementVariant |
| 143 | fieldMemberExpressionForQualifiedId(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 | |
| 163 | QQmlDomAstCreator::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 | |
| 177 | QQmlDomAstCreator::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 | |
| 183 | QQmlDomAstCreator::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 | |
| 190 | QQmlDomAstCreator::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 | |
| 197 | void 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 | |
| 205 | void 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 | */ |
| 227 | const ScriptElementVariant & |
| 228 | QQmlDomAstCreator::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 | |
| 242 | FileLocations::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 | |
| 250 | FileLocations::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 | |
| 318 | QQmlDomAstCreator::QQmlDomAstCreator(const MutableDomItem &qmlFile) |
| 319 | : qmlFile(qmlFile), |
| 320 | qmlFilePtr(qmlFile.ownerAs<QmlFile>()), |
| 321 | rootMap(qmlFilePtr->fileLocationsTree()) |
| 322 | { |
| 323 | } |
| 324 | |
| 325 | bool 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->domCreationOption() == Extended |
| 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 | |
| 384 | void 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 | |
| 399 | bool 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 | |
| 422 | bool 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 | |
| 479 | bool 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->statement); |
| 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 | |
| 596 | void 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 | |
| 662 | void QQmlDomAstCreator::endVisit(AST::FormalParameterList *list) |
| 663 | { |
| 664 | endVisitForLists(list); |
| 665 | } |
| 666 | |
| 667 | bool QQmlDomAstCreator::visit(AST::FunctionExpression *) |
| 668 | { |
| 669 | ++m_nestedFunctionDepth; |
| 670 | if (!m_enableScriptExpressions) |
| 671 | return false; |
| 672 | |
| 673 | return true; |
| 674 | } |
| 675 | |
| 676 | ScriptElementVariant 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 | |
| 706 | void 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 | |
| 752 | bool 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 | |
| 851 | bool 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 | |
| 860 | static 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 | |
| 870 | void 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 | |
| 927 | void 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 | |
| 939 | bool 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 | |
| 1003 | void 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 | |
| 1040 | void 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 | |
| 1052 | bool 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 | |
| 1099 | void 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 | |
| 1115 | bool 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 | |
| 1197 | void 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 | |
| 1212 | void 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 | |
| 1245 | bool 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 | |
| 1271 | void 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 | |
| 1281 | void QQmlDomAstCreator::endVisit(AST::ArgumentList *list) |
| 1282 | { |
| 1283 | endVisitForLists(list); |
| 1284 | } |
| 1285 | |
| 1286 | bool QQmlDomAstCreator::visit(AST::UiParameterList *) |
| 1287 | { |
| 1288 | return false; // do not create script node for Ui stuff |
| 1289 | } |
| 1290 | |
| 1291 | void 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 | |
| 1301 | void 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 | */ |
| 1312 | ScriptElementVariant 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 | |
| 1321 | bool QQmlDomAstCreator::visit(AST::UiQualifiedId *) |
| 1322 | { |
| 1323 | if (!m_enableScriptExpressions) |
| 1324 | return false; |
| 1325 | |
| 1326 | return false; |
| 1327 | } |
| 1328 | |
| 1329 | bool 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 | |
| 1343 | void 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 | |
| 1353 | bool 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 | |
| 1369 | void 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 | |
| 1374 | bool 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 | |
| 1403 | void 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 | |
| 1414 | bool 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 | |
| 1426 | bool 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 | |
| 1463 | void 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 | |
| 1490 | void QQmlDomAstCreator::throwRecursionDepthError() |
| 1491 | { |
| 1492 | qmlFile.addError(msg: astParseErrors().error( |
| 1493 | message: tr(sourceText: "Maximum statement or expression depth exceeded in QmlDomAstCreator" ))); |
| 1494 | } |
| 1495 | |
| 1496 | void QQmlDomAstCreator::endVisit(AST::StatementList *list) |
| 1497 | { |
| 1498 | endVisitForLists(list); |
| 1499 | } |
| 1500 | |
| 1501 | bool QQmlDomAstCreator::visit(AST::BinaryExpression *) |
| 1502 | { |
| 1503 | if (!m_enableScriptExpressions) |
| 1504 | return false; |
| 1505 | |
| 1506 | return true; |
| 1507 | } |
| 1508 | |
| 1509 | void 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 | |
| 1526 | bool QQmlDomAstCreator::visit(AST::Block *) |
| 1527 | { |
| 1528 | if (!m_enableScriptExpressions) |
| 1529 | return false; |
| 1530 | |
| 1531 | return true; |
| 1532 | } |
| 1533 | |
| 1534 | void 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 | |
| 1550 | bool QQmlDomAstCreator::visit(AST::ForStatement *) |
| 1551 | { |
| 1552 | if (!m_enableScriptExpressions) |
| 1553 | return false; |
| 1554 | |
| 1555 | return true; |
| 1556 | } |
| 1557 | |
| 1558 | void 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 | |
| 1618 | bool 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 | |
| 1629 | bool 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 | |
| 1640 | bool 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 | |
| 1649 | bool 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 | |
| 1660 | bool 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 | |
| 1671 | bool 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 | |
| 1682 | bool 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 | |
| 1693 | bool 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 | |
| 1702 | bool 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 | |
| 1711 | bool 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 | |
| 1724 | bool 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 | |
| 1736 | bool 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 | |
| 1748 | bool 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 | |
| 1759 | bool 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 | |
| 1768 | template<typename T> |
| 1769 | void 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 | |
| 1792 | void QQmlDomAstCreator::endVisit(AST::VariableDeclarationList *list) |
| 1793 | { |
| 1794 | endVisitForLists(list); |
| 1795 | } |
| 1796 | |
| 1797 | bool 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 | |
| 1814 | bool 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 | */ |
| 1827 | void QQmlDomAstCreator::endVisitHelper( |
| 1828 | AST::PatternElement *pe, |
| 1829 | const std::shared_ptr<ScriptElements::GenericScriptElement> ¤t) |
| 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 | |
| 1857 | void 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 | |
| 1871 | bool QQmlDomAstCreator::visit(AST::IfStatement *) |
| 1872 | { |
| 1873 | if (!m_enableScriptExpressions) |
| 1874 | return false; |
| 1875 | |
| 1876 | return true; |
| 1877 | } |
| 1878 | |
| 1879 | void 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 | |
| 1910 | bool QQmlDomAstCreator::visit(AST::ReturnStatement *) |
| 1911 | { |
| 1912 | if (!m_enableScriptExpressions) |
| 1913 | return false; |
| 1914 | |
| 1915 | return true; |
| 1916 | } |
| 1917 | |
| 1918 | void 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 | |
| 1935 | bool QQmlDomAstCreator::visit(AST::YieldExpression *) |
| 1936 | { |
| 1937 | if (!m_enableScriptExpressions) |
| 1938 | return false; |
| 1939 | |
| 1940 | return true; |
| 1941 | } |
| 1942 | |
| 1943 | void 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 | |
| 1960 | bool QQmlDomAstCreator::visit(AST::FieldMemberExpression *) |
| 1961 | { |
| 1962 | if (!m_enableScriptExpressions) |
| 1963 | return false; |
| 1964 | |
| 1965 | return true; |
| 1966 | } |
| 1967 | |
| 1968 | void 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 | |
| 1991 | bool QQmlDomAstCreator::visit(AST::ArrayMemberExpression *) |
| 1992 | { |
| 1993 | if (!m_enableScriptExpressions) |
| 1994 | return false; |
| 1995 | |
| 1996 | return true; |
| 1997 | } |
| 1998 | |
| 1999 | void 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 | |
| 2025 | bool QQmlDomAstCreator::visit(AST::CallExpression *) |
| 2026 | { |
| 2027 | if (!m_enableScriptExpressions) |
| 2028 | return false; |
| 2029 | |
| 2030 | return true; |
| 2031 | } |
| 2032 | |
| 2033 | void 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 | |
| 2061 | bool QQmlDomAstCreator::visit(AST::ArrayPattern *) |
| 2062 | { |
| 2063 | if (!m_enableScriptExpressions) |
| 2064 | return false; |
| 2065 | |
| 2066 | return true; |
| 2067 | } |
| 2068 | |
| 2069 | void 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 | |
| 2092 | bool QQmlDomAstCreator::visit(AST::ObjectPattern *) |
| 2093 | { |
| 2094 | if (!m_enableScriptExpressions) |
| 2095 | return false; |
| 2096 | |
| 2097 | return true; |
| 2098 | } |
| 2099 | |
| 2100 | void 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 | |
| 2120 | bool QQmlDomAstCreator::visit(AST::PatternProperty *) |
| 2121 | { |
| 2122 | if (!m_enableScriptExpressions) |
| 2123 | return false; |
| 2124 | |
| 2125 | return true; |
| 2126 | } |
| 2127 | |
| 2128 | void 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 | |
| 2151 | bool QQmlDomAstCreator::visit(AST::VariableStatement *) |
| 2152 | { |
| 2153 | if (!m_enableScriptExpressions) |
| 2154 | return false; |
| 2155 | |
| 2156 | return true; |
| 2157 | } |
| 2158 | |
| 2159 | void 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 | |
| 2181 | bool QQmlDomAstCreator::visit(AST::Type *) |
| 2182 | { |
| 2183 | if (!m_enableScriptExpressions) |
| 2184 | return false; |
| 2185 | |
| 2186 | return true; |
| 2187 | } |
| 2188 | |
| 2189 | void 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 | |
| 2210 | bool QQmlDomAstCreator::visit(AST::DefaultClause *) |
| 2211 | { |
| 2212 | if (!m_enableScriptExpressions) |
| 2213 | return false; |
| 2214 | |
| 2215 | return true; |
| 2216 | } |
| 2217 | |
| 2218 | void 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 | |
| 2236 | bool QQmlDomAstCreator::visit(AST::CaseClause *) |
| 2237 | { |
| 2238 | if (!m_enableScriptExpressions) |
| 2239 | return false; |
| 2240 | |
| 2241 | return true; |
| 2242 | } |
| 2243 | |
| 2244 | void 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 | |
| 2268 | bool QQmlDomAstCreator::visit(AST::CaseClauses *) |
| 2269 | { |
| 2270 | if (!m_enableScriptExpressions) |
| 2271 | return false; |
| 2272 | |
| 2273 | return true; |
| 2274 | } |
| 2275 | |
| 2276 | void 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 | |
| 2292 | bool QQmlDomAstCreator::visit(AST::CaseBlock *) |
| 2293 | { |
| 2294 | if (!m_enableScriptExpressions) |
| 2295 | return false; |
| 2296 | |
| 2297 | return true; |
| 2298 | } |
| 2299 | |
| 2300 | void 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 | |
| 2329 | bool QQmlDomAstCreator::visit(AST::SwitchStatement *) |
| 2330 | { |
| 2331 | if (!m_enableScriptExpressions) |
| 2332 | return false; |
| 2333 | |
| 2334 | return true; |
| 2335 | } |
| 2336 | |
| 2337 | void 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 | |
| 2361 | bool QQmlDomAstCreator::visit(AST::WhileStatement *) |
| 2362 | { |
| 2363 | if (!m_enableScriptExpressions) |
| 2364 | return false; |
| 2365 | |
| 2366 | return true; |
| 2367 | } |
| 2368 | |
| 2369 | void 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 | |
| 2394 | bool QQmlDomAstCreator::visit(AST::DoWhileStatement *) |
| 2395 | { |
| 2396 | if (!m_enableScriptExpressions) |
| 2397 | return false; |
| 2398 | |
| 2399 | return true; |
| 2400 | } |
| 2401 | |
| 2402 | void 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 | |
| 2428 | bool QQmlDomAstCreator::visit(AST::ForEachStatement *) |
| 2429 | { |
| 2430 | if (!m_enableScriptExpressions) |
| 2431 | return false; |
| 2432 | |
| 2433 | return true; |
| 2434 | } |
| 2435 | |
| 2436 | void 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 | |
| 2474 | bool 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 | |
| 2483 | void QQmlDomAstCreator::endVisit(AST::ClassExpression *) |
| 2484 | { |
| 2485 | } |
| 2486 | |
| 2487 | void 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 | |
| 2499 | /*! |
| 2500 | \internal |
| 2501 | Denotes the position of a template part in a template string. For example, in \c{`a${b}c${d}`}, \c a |
| 2502 | is \c AtBeginning and \c{${d}} is \c AtEnd while the others are \c InMiddle, and in \c{`a`}, \c a is |
| 2503 | \c AtBeginning and \c AtEnd. |
| 2504 | */ |
| 2505 | enum TemplatePartPosition : quint8 { |
| 2506 | InMiddle = 0, |
| 2507 | AtBeginning = 0x1, |
| 2508 | AtEnd = 0x2, |
| 2509 | }; |
| 2510 | |
| 2511 | Q_DECLARE_FLAGS(TemplatePartPositions, TemplatePartPosition) |
| 2512 | |
| 2513 | /*! |
| 2514 | \internal |
| 2515 | Sets the DollarLeftBraceTokenRegion sourcelocation in currentExpression if templatePartLocation |
| 2516 | claims that toBeSplit ends in \c{${}. |
| 2517 | */ |
| 2518 | static void ( |
| 2519 | const std::shared_ptr<ScriptElements::GenericScriptElement> ¤tExpression, |
| 2520 | const SourceLocation &toBeSplit, QStringView code, |
| 2521 | TemplatePartPositions templatePartLocation) |
| 2522 | { |
| 2523 | if (templatePartLocation & AtEnd || !currentExpression) |
| 2524 | return; |
| 2525 | |
| 2526 | const auto offset = toBeSplit.offset + toBeSplit.length - 2; |
| 2527 | constexpr auto length = quint32(std::char_traits<char>::length(s: "${" )); |
| 2528 | const auto [row, column] = SourceLocation::rowAndColumnFrom(text: code, offset, startHint: toBeSplit); |
| 2529 | currentExpression->addLocation(region: FileLocationRegion::DollarLeftBraceTokenRegion, |
| 2530 | location: SourceLocation{ offset, length, row, column }); |
| 2531 | return; |
| 2532 | } |
| 2533 | |
| 2534 | /*! |
| 2535 | \internal |
| 2536 | See also \l extractDollarBraceSourceLocationInto. |
| 2537 | */ |
| 2538 | static void ( |
| 2539 | const std::shared_ptr<ScriptElements::GenericScriptElement> ¤tTemplate, |
| 2540 | const SourceLocation &toBeSplit, QStringView code, |
| 2541 | TemplatePartPositions templatePartLocation) |
| 2542 | { |
| 2543 | if (!(templatePartLocation & AtEnd)) |
| 2544 | return; |
| 2545 | |
| 2546 | const auto offset = toBeSplit.offset + toBeSplit.length - 1; |
| 2547 | constexpr auto length = quint32(std::char_traits<char>::length(s: "`" )); |
| 2548 | const auto [row, column] = SourceLocation::rowAndColumnFrom(text: code, offset, startHint: toBeSplit); |
| 2549 | currentTemplate->addLocation(region: FileLocationRegion::RightBacktickTokenRegion, |
| 2550 | location: SourceLocation{ offset, length, row, column }); |
| 2551 | } |
| 2552 | |
| 2553 | /*! |
| 2554 | \internal |
| 2555 | See also \l extractDollarBraceSourceLocationInto. |
| 2556 | */ |
| 2557 | static void ( |
| 2558 | const std::shared_ptr<ScriptElements::GenericScriptElement> ¤tTemplate, |
| 2559 | const SourceLocation &toBeSplit, TemplatePartPositions templatePartLocation) |
| 2560 | { |
| 2561 | if (!(templatePartLocation & AtBeginning)) |
| 2562 | return; |
| 2563 | |
| 2564 | constexpr auto length = quint32(std::char_traits<char>::length(s: "`" )); |
| 2565 | const QQmlJS::SourceLocation leftBacktick{ toBeSplit.offset, length, toBeSplit.startLine, |
| 2566 | toBeSplit.startColumn }; |
| 2567 | currentTemplate->addLocation(region: FileLocationRegion::LeftBacktickTokenRegion, location: leftBacktick); |
| 2568 | } |
| 2569 | |
| 2570 | /*! |
| 2571 | \internal |
| 2572 | See also \l extractDollarBraceSourceLocationInto, but returns the extracted right brace instead of |
| 2573 | inserting right away. |
| 2574 | */ |
| 2575 | static SourceLocation (const SourceLocation &toBeSplit, |
| 2576 | TemplatePartPositions templatePartLocation) |
| 2577 | { |
| 2578 | if (templatePartLocation & AtBeginning) |
| 2579 | return SourceLocation{}; |
| 2580 | |
| 2581 | // extract } at the beginning and insert in next loop iteration |
| 2582 | return SourceLocation{ toBeSplit.offset, 1, toBeSplit.startLine, toBeSplit.startColumn }; |
| 2583 | } |
| 2584 | |
| 2585 | /*! |
| 2586 | \internal |
| 2587 | Cleans the toBeSplit sourcelocation from potential backticks, dollar braces and right braces to only |
| 2588 | contain the location of the string part. |
| 2589 | */ |
| 2590 | static SourceLocation (const SourceLocation &toBeSplit, QStringView code, |
| 2591 | TemplatePartPositions location) |
| 2592 | { |
| 2593 | |
| 2594 | // remove "`" or "}" at beginning and "`" or "${" at the end of this location. |
| 2595 | const quint32 length = toBeSplit.length - (location & AtEnd ? 2 : 3); |
| 2596 | const quint32 offset = toBeSplit.offset + 1; |
| 2597 | const auto [row, column] = SourceLocation::rowAndColumnFrom(text: code, offset, startHint: toBeSplit); |
| 2598 | return SourceLocation{ offset, length, row, column }; |
| 2599 | } |
| 2600 | |
| 2601 | void QQmlDomAstCreator::endVisit(AST::TemplateLiteral *literal) |
| 2602 | { |
| 2603 | if (!m_enableScriptExpressions) |
| 2604 | return; |
| 2605 | |
| 2606 | // AST::TemplateLiteral is a list and a TemplateLiteral at the same time: |
| 2607 | // in the Dom representation wrap the list into a separate TemplateLiteral Item. |
| 2608 | auto currentList = makeScriptList(ast: literal); |
| 2609 | auto currentTemplate = makeGenericScriptElement(ast: literal, kind: DomType::ScriptTemplateLiteral); |
| 2610 | |
| 2611 | const auto children = [&literal]() { |
| 2612 | std::vector<AST::TemplateLiteral *> result; |
| 2613 | for (auto it = literal; it; it = it->next) { |
| 2614 | result.push_back(x: it); |
| 2615 | } |
| 2616 | return result; |
| 2617 | }(); |
| 2618 | |
| 2619 | SourceLocation rightBrace; |
| 2620 | for (auto it = children.crbegin(); it != children.crend(); ++it) { |
| 2621 | // literalToken contains "`", "${", "}", for example "`asdf${" or "}asdf${" |
| 2622 | const QQmlJS::SourceLocation toBeSplit = (*it)->literalToken; |
| 2623 | std::shared_ptr<ScriptElements::GenericScriptElement> currentExpression; |
| 2624 | |
| 2625 | if ((*it)->expression) { |
| 2626 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2627 | currentExpression = makeGenericScriptElement(ast: (*it)->expression, |
| 2628 | kind: DomType::ScriptTemplateExpressionPart); |
| 2629 | |
| 2630 | currentExpression->insertChild(name: Fields::expression, |
| 2631 | v: scriptNodeStack.takeLast().takeVariant()); |
| 2632 | if (rightBrace.isValid()) { |
| 2633 | currentExpression->addLocation(region: FileLocationRegion::RightBraceRegion, |
| 2634 | location: std::exchange(obj&: rightBrace, new_val: SourceLocation{})); |
| 2635 | } |
| 2636 | currentList.append(statement: ScriptElementVariant::fromElement(element: currentExpression)); |
| 2637 | } |
| 2638 | |
| 2639 | if (!toBeSplit.isValid()) |
| 2640 | continue; |
| 2641 | |
| 2642 | const TemplatePartPositions location = [&it, &children]() { |
| 2643 | TemplatePartPositions result; |
| 2644 | if (it == children.crbegin()) |
| 2645 | result |= AtEnd; |
| 2646 | if (it == std::prev(x: children.crend())) |
| 2647 | result |= AtBeginning; |
| 2648 | return result; |
| 2649 | }(); |
| 2650 | |
| 2651 | extractRightBacktickSourceLocationInto(currentTemplate, toBeSplit, code: qmlFilePtr->code(), |
| 2652 | templatePartLocation: location); |
| 2653 | extractLeftBacktickSourceLocationInto(currentTemplate, toBeSplit, templatePartLocation: location); |
| 2654 | |
| 2655 | extractDollarBraceSourceLocationInto(currentExpression, toBeSplit, code: qmlFilePtr->code(), |
| 2656 | templatePartLocation: location); |
| 2657 | rightBrace = extractRightBraceSourceLocation(toBeSplit, templatePartLocation: location); |
| 2658 | |
| 2659 | if ((*it)->rawValue.isEmpty()) |
| 2660 | continue; |
| 2661 | |
| 2662 | const SourceLocation stringLocation = |
| 2663 | extractStringLocation(toBeSplit, code: qmlFilePtr->code(), location); |
| 2664 | auto currentString = |
| 2665 | makeGenericScriptElement(location: stringLocation, kind: DomType::ScriptTemplateStringPart); |
| 2666 | currentString->insertValue(name: Fields::value, v: (*it)->rawValue); |
| 2667 | |
| 2668 | currentList.append(statement: ScriptElementVariant::fromElement(element: currentString)); |
| 2669 | } |
| 2670 | currentList.reverse(); |
| 2671 | |
| 2672 | currentTemplate->insertChild(name: Fields::components, v: currentList); |
| 2673 | pushScriptElement(element: currentTemplate); |
| 2674 | } |
| 2675 | |
| 2676 | bool QQmlDomAstCreator::visit(AST::TryStatement *) |
| 2677 | { |
| 2678 | return m_enableScriptExpressions; |
| 2679 | } |
| 2680 | |
| 2681 | void QQmlDomAstCreator::endVisit(AST::TryStatement *statement) |
| 2682 | { |
| 2683 | if (!m_enableScriptExpressions) |
| 2684 | return; |
| 2685 | |
| 2686 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptTryCatchStatement); |
| 2687 | current->addLocation(region: FileLocationRegion::TryKeywordRegion, location: statement->tryToken); |
| 2688 | |
| 2689 | if (auto exp = statement->finallyExpression) { |
| 2690 | current->addLocation(region: FileLocationRegion::FinallyKeywordRegion, location: exp->finallyToken); |
| 2691 | |
| 2692 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2693 | current->insertChild(name: Fields::finallyBlock, v: currentScriptNodeEl().takeVariant()); |
| 2694 | removeCurrentScriptNode(expectedType: {}); |
| 2695 | } |
| 2696 | |
| 2697 | if (auto exp = statement->catchExpression) { |
| 2698 | current->addLocation(region: FileLocationRegion::CatchKeywordRegion, location: exp->catchToken); |
| 2699 | current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: exp->lparenToken); |
| 2700 | current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: exp->rparenToken); |
| 2701 | |
| 2702 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2703 | current->insertChild(name: Fields::catchBlock, v: currentScriptNodeEl().takeVariant()); |
| 2704 | removeCurrentScriptNode(expectedType: {}); |
| 2705 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2706 | current->insertChild(name: Fields::catchParameter, v: currentScriptNodeEl().takeVariant()); |
| 2707 | removeCurrentScriptNode(expectedType: {}); |
| 2708 | } |
| 2709 | |
| 2710 | if (statement->statement) { |
| 2711 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2712 | current->insertChild(name: Fields::block, v: currentScriptNodeEl().takeVariant()); |
| 2713 | removeCurrentScriptNode(expectedType: {}); |
| 2714 | } |
| 2715 | |
| 2716 | pushScriptElement(element: current); |
| 2717 | } |
| 2718 | |
| 2719 | bool QQmlDomAstCreator::visit(AST::Catch *) |
| 2720 | { |
| 2721 | // handled in visit(AST::TryStatement* ) |
| 2722 | return m_enableScriptExpressions; |
| 2723 | } |
| 2724 | |
| 2725 | void QQmlDomAstCreator::endVisit(AST::Catch *) |
| 2726 | { |
| 2727 | // handled in endVisit(AST::TryStatement* ) |
| 2728 | } |
| 2729 | |
| 2730 | bool QQmlDomAstCreator::visit(AST::Finally *) |
| 2731 | { |
| 2732 | // handled in visit(AST::TryStatement* ) |
| 2733 | return m_enableScriptExpressions; |
| 2734 | } |
| 2735 | |
| 2736 | void QQmlDomAstCreator::endVisit(AST::Finally *) |
| 2737 | { |
| 2738 | // handled in endVisit(AST::TryStatement* ) |
| 2739 | } |
| 2740 | |
| 2741 | bool QQmlDomAstCreator::visit(AST::ThrowStatement *) |
| 2742 | { |
| 2743 | return m_enableScriptExpressions; |
| 2744 | } |
| 2745 | |
| 2746 | void QQmlDomAstCreator::endVisit(AST::ThrowStatement *statement) |
| 2747 | { |
| 2748 | if (!m_enableScriptExpressions) |
| 2749 | return; |
| 2750 | |
| 2751 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptThrowStatement); |
| 2752 | current->addLocation(region: FileLocationRegion::ThrowKeywordRegion, location: statement->throwToken); |
| 2753 | |
| 2754 | if (statement->expression) { |
| 2755 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2756 | current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant()); |
| 2757 | removeCurrentScriptNode(expectedType: {}); |
| 2758 | } |
| 2759 | |
| 2760 | pushScriptElement(element: current); |
| 2761 | } |
| 2762 | |
| 2763 | bool QQmlDomAstCreator::visit(AST::LabelledStatement *) |
| 2764 | { |
| 2765 | return m_enableScriptExpressions; |
| 2766 | } |
| 2767 | |
| 2768 | void QQmlDomAstCreator::endVisit(AST::LabelledStatement *statement) |
| 2769 | { |
| 2770 | if (!m_enableScriptExpressions) |
| 2771 | return; |
| 2772 | |
| 2773 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptLabelledStatement); |
| 2774 | current->addLocation(region: FileLocationRegion::ColonTokenRegion, location: statement->colonToken); |
| 2775 | |
| 2776 | auto label = std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken); |
| 2777 | label->setName(statement->label); |
| 2778 | current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label)); |
| 2779 | |
| 2780 | |
| 2781 | if (statement->statement) { |
| 2782 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2783 | current->insertChild(name: Fields::statement, v: currentScriptNodeEl().takeVariant()); |
| 2784 | removeCurrentScriptNode(expectedType: {}); |
| 2785 | } |
| 2786 | |
| 2787 | pushScriptElement(element: current); |
| 2788 | } |
| 2789 | |
| 2790 | bool QQmlDomAstCreator::visit(AST::BreakStatement *) |
| 2791 | { |
| 2792 | return m_enableScriptExpressions; |
| 2793 | } |
| 2794 | |
| 2795 | void QQmlDomAstCreator::endVisit(AST::BreakStatement *statement) |
| 2796 | { |
| 2797 | if (!m_enableScriptExpressions) |
| 2798 | return; |
| 2799 | |
| 2800 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptBreakStatement); |
| 2801 | current->addLocation(region: FileLocationRegion::BreakKeywordRegion, location: statement->breakToken); |
| 2802 | |
| 2803 | if (!statement->label.isEmpty()) { |
| 2804 | auto label = |
| 2805 | std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken); |
| 2806 | label->setName(statement->label); |
| 2807 | current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label)); |
| 2808 | } |
| 2809 | |
| 2810 | pushScriptElement(element: current); |
| 2811 | } |
| 2812 | |
| 2813 | // note: thats for comma expressions |
| 2814 | bool QQmlDomAstCreator::visit(AST::Expression *) |
| 2815 | { |
| 2816 | return m_enableScriptExpressions; |
| 2817 | } |
| 2818 | |
| 2819 | // note: thats for comma expressions |
| 2820 | void QQmlDomAstCreator::endVisit(AST::Expression *commaExpression) |
| 2821 | { |
| 2822 | if (!m_enableScriptExpressions) |
| 2823 | return; |
| 2824 | |
| 2825 | auto current = makeScriptElement<ScriptElements::BinaryExpression>(ast: commaExpression); |
| 2826 | current->addLocation(region: OperatorTokenRegion, location: commaExpression->commaToken); |
| 2827 | |
| 2828 | if (commaExpression->right) { |
| 2829 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2830 | current->setRight(currentScriptNodeEl().takeVariant()); |
| 2831 | removeCurrentScriptNode(expectedType: {}); |
| 2832 | } |
| 2833 | |
| 2834 | if (commaExpression->left) { |
| 2835 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2836 | current->setLeft(currentScriptNodeEl().takeVariant()); |
| 2837 | removeCurrentScriptNode(expectedType: {}); |
| 2838 | } |
| 2839 | |
| 2840 | pushScriptElement(element: current); |
| 2841 | } |
| 2842 | |
| 2843 | bool QQmlDomAstCreator::visit(AST::ConditionalExpression *) |
| 2844 | { |
| 2845 | return m_enableScriptExpressions; |
| 2846 | } |
| 2847 | |
| 2848 | void QQmlDomAstCreator::endVisit(AST::ConditionalExpression *expression) |
| 2849 | { |
| 2850 | if (!m_enableScriptExpressions) |
| 2851 | return; |
| 2852 | |
| 2853 | auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptConditionalExpression); |
| 2854 | current->addLocation(region: FileLocationRegion::QuestionMarkTokenRegion, location: expression->questionToken); |
| 2855 | current->addLocation(region: FileLocationRegion::ColonTokenRegion, location: expression->colonToken); |
| 2856 | |
| 2857 | if (expression->ko) { |
| 2858 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2859 | current->insertChild(name: Fields::alternative, v: currentScriptNodeEl().takeVariant()); |
| 2860 | removeCurrentScriptNode(expectedType: {}); |
| 2861 | } |
| 2862 | |
| 2863 | if (expression->ok) { |
| 2864 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2865 | current->insertChild(name: Fields::consequence, v: currentScriptNodeEl().takeVariant()); |
| 2866 | removeCurrentScriptNode(expectedType: {}); |
| 2867 | } |
| 2868 | |
| 2869 | if (expression->expression) { |
| 2870 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 2871 | current->insertChild(name: Fields::condition, v: currentScriptNodeEl().takeVariant()); |
| 2872 | removeCurrentScriptNode(expectedType: {}); |
| 2873 | } |
| 2874 | |
| 2875 | pushScriptElement(element: current); |
| 2876 | } |
| 2877 | |
| 2878 | bool QQmlDomAstCreator::visit(AST::ContinueStatement *) |
| 2879 | { |
| 2880 | return m_enableScriptExpressions; |
| 2881 | } |
| 2882 | |
| 2883 | void QQmlDomAstCreator::endVisit(AST::ContinueStatement *statement) |
| 2884 | { |
| 2885 | if (!m_enableScriptExpressions) |
| 2886 | return; |
| 2887 | |
| 2888 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptContinueStatement); |
| 2889 | current->addLocation(region: FileLocationRegion::ContinueKeywordRegion, location: statement->continueToken); |
| 2890 | |
| 2891 | if (!statement->label.isEmpty()) { |
| 2892 | auto label = |
| 2893 | std::make_shared<ScriptElements::IdentifierExpression>(args&: statement->identifierToken); |
| 2894 | label->setName(statement->label); |
| 2895 | current->insertChild(name: Fields::label, v: ScriptElementVariant::fromElement(element: label)); |
| 2896 | } |
| 2897 | |
| 2898 | pushScriptElement(element: current); |
| 2899 | } |
| 2900 | |
| 2901 | /*! |
| 2902 | \internal |
| 2903 | Helper to create unary expressions from AST nodes. |
| 2904 | \sa makeGenericScriptElement |
| 2905 | */ |
| 2906 | std::shared_ptr<ScriptElements::GenericScriptElement> |
| 2907 | QQmlDomAstCreator::makeUnaryExpression(AST::Node *expression, QQmlJS::SourceLocation operatorToken, |
| 2908 | bool hasExpression, UnaryExpressionKind kind) |
| 2909 | { |
| 2910 | const DomType type = [&kind]() { |
| 2911 | switch (kind) { |
| 2912 | case Prefix: |
| 2913 | return DomType::ScriptUnaryExpression; |
| 2914 | case Postfix: |
| 2915 | return DomType::ScriptPostExpression; |
| 2916 | } |
| 2917 | Q_UNREACHABLE_RETURN(DomType::ScriptUnaryExpression); |
| 2918 | }(); |
| 2919 | |
| 2920 | auto current = makeGenericScriptElement(ast: expression, kind: type); |
| 2921 | current->addLocation(region: FileLocationRegion::OperatorTokenRegion, location: operatorToken); |
| 2922 | |
| 2923 | if (hasExpression) { |
| 2924 | if (!stackHasScriptVariant()) { |
| 2925 | Q_SCRIPTELEMENT_DISABLE(); |
| 2926 | return {}; |
| 2927 | } |
| 2928 | current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant()); |
| 2929 | removeCurrentScriptNode(expectedType: {}); |
| 2930 | } |
| 2931 | |
| 2932 | return current; |
| 2933 | } |
| 2934 | |
| 2935 | bool QQmlDomAstCreator::visit(AST::UnaryMinusExpression *) |
| 2936 | { |
| 2937 | return m_enableScriptExpressions; |
| 2938 | } |
| 2939 | |
| 2940 | void QQmlDomAstCreator::endVisit(AST::UnaryMinusExpression *statement) |
| 2941 | { |
| 2942 | if (!m_enableScriptExpressions) |
| 2943 | return; |
| 2944 | |
| 2945 | auto current = |
| 2946 | makeUnaryExpression(expression: statement, operatorToken: statement->minusToken, hasExpression: statement->expression, kind: Prefix); |
| 2947 | if (!current) |
| 2948 | return; |
| 2949 | |
| 2950 | pushScriptElement(element: current); |
| 2951 | } |
| 2952 | |
| 2953 | bool QQmlDomAstCreator::visit(AST::UnaryPlusExpression *) |
| 2954 | { |
| 2955 | return m_enableScriptExpressions; |
| 2956 | } |
| 2957 | |
| 2958 | void QQmlDomAstCreator::endVisit(AST::UnaryPlusExpression *statement) |
| 2959 | { |
| 2960 | if (!m_enableScriptExpressions) |
| 2961 | return; |
| 2962 | |
| 2963 | auto current = |
| 2964 | makeUnaryExpression(expression: statement, operatorToken: statement->plusToken, hasExpression: statement->expression, kind: Prefix); |
| 2965 | if (!current) |
| 2966 | return; |
| 2967 | |
| 2968 | pushScriptElement(element: current); |
| 2969 | } |
| 2970 | |
| 2971 | bool QQmlDomAstCreator::visit(AST::TildeExpression *) |
| 2972 | { |
| 2973 | return m_enableScriptExpressions; |
| 2974 | } |
| 2975 | |
| 2976 | void QQmlDomAstCreator::endVisit(AST::TildeExpression *statement) |
| 2977 | { |
| 2978 | if (!m_enableScriptExpressions) |
| 2979 | return; |
| 2980 | |
| 2981 | auto current = |
| 2982 | makeUnaryExpression(expression: statement, operatorToken: statement->tildeToken, hasExpression: statement->expression, kind: Prefix); |
| 2983 | if (!current) |
| 2984 | return; |
| 2985 | |
| 2986 | pushScriptElement(element: current); |
| 2987 | } |
| 2988 | |
| 2989 | bool QQmlDomAstCreator::visit(AST::NotExpression *) |
| 2990 | { |
| 2991 | return m_enableScriptExpressions; |
| 2992 | } |
| 2993 | |
| 2994 | void QQmlDomAstCreator::endVisit(AST::NotExpression *statement) |
| 2995 | { |
| 2996 | if (!m_enableScriptExpressions) |
| 2997 | return; |
| 2998 | |
| 2999 | auto current = |
| 3000 | makeUnaryExpression(expression: statement, operatorToken: statement->notToken, hasExpression: statement->expression, kind: Prefix); |
| 3001 | if (!current) |
| 3002 | return; |
| 3003 | |
| 3004 | pushScriptElement(element: current); |
| 3005 | } |
| 3006 | |
| 3007 | bool QQmlDomAstCreator::visit(AST::TypeOfExpression *) |
| 3008 | { |
| 3009 | return m_enableScriptExpressions; |
| 3010 | } |
| 3011 | |
| 3012 | void QQmlDomAstCreator::endVisit(AST::TypeOfExpression *statement) |
| 3013 | { |
| 3014 | if (!m_enableScriptExpressions) |
| 3015 | return; |
| 3016 | |
| 3017 | auto current = |
| 3018 | makeUnaryExpression(expression: statement, operatorToken: statement->typeofToken, hasExpression: statement->expression, kind: Prefix); |
| 3019 | if (!current) |
| 3020 | return; |
| 3021 | |
| 3022 | pushScriptElement(element: current); |
| 3023 | } |
| 3024 | |
| 3025 | bool QQmlDomAstCreator::visit(AST::DeleteExpression *) |
| 3026 | { |
| 3027 | return m_enableScriptExpressions; |
| 3028 | } |
| 3029 | |
| 3030 | void QQmlDomAstCreator::endVisit(AST::DeleteExpression *statement) |
| 3031 | { |
| 3032 | if (!m_enableScriptExpressions) |
| 3033 | return; |
| 3034 | |
| 3035 | auto current = |
| 3036 | makeUnaryExpression(expression: statement, operatorToken: statement->deleteToken, hasExpression: statement->expression, kind: Prefix); |
| 3037 | if (!current) |
| 3038 | return; |
| 3039 | |
| 3040 | pushScriptElement(element: current); |
| 3041 | } |
| 3042 | |
| 3043 | bool QQmlDomAstCreator::visit(AST::VoidExpression *) |
| 3044 | { |
| 3045 | return m_enableScriptExpressions; |
| 3046 | } |
| 3047 | |
| 3048 | void QQmlDomAstCreator::endVisit(AST::VoidExpression *statement) |
| 3049 | { |
| 3050 | if (!m_enableScriptExpressions) |
| 3051 | return; |
| 3052 | |
| 3053 | auto current = |
| 3054 | makeUnaryExpression(expression: statement, operatorToken: statement->voidToken, hasExpression: statement->expression, kind: Prefix); |
| 3055 | if (!current) |
| 3056 | return; |
| 3057 | |
| 3058 | pushScriptElement(element: current); |
| 3059 | } |
| 3060 | |
| 3061 | bool QQmlDomAstCreator::visit(AST::PostDecrementExpression *) |
| 3062 | { |
| 3063 | return m_enableScriptExpressions; |
| 3064 | } |
| 3065 | |
| 3066 | void QQmlDomAstCreator::endVisit(AST::PostDecrementExpression *statement) |
| 3067 | { |
| 3068 | if (!m_enableScriptExpressions) |
| 3069 | return; |
| 3070 | |
| 3071 | auto current = |
| 3072 | makeUnaryExpression(expression: statement, operatorToken: statement->decrementToken, hasExpression: statement->base, kind: Postfix); |
| 3073 | if (!current) |
| 3074 | return; |
| 3075 | |
| 3076 | pushScriptElement(element: current); |
| 3077 | } |
| 3078 | |
| 3079 | bool QQmlDomAstCreator::visit(AST::PostIncrementExpression *) |
| 3080 | { |
| 3081 | return m_enableScriptExpressions; |
| 3082 | } |
| 3083 | |
| 3084 | void QQmlDomAstCreator::endVisit(AST::PostIncrementExpression *statement) |
| 3085 | { |
| 3086 | if (!m_enableScriptExpressions) |
| 3087 | return; |
| 3088 | |
| 3089 | auto current = |
| 3090 | makeUnaryExpression(expression: statement, operatorToken: statement->incrementToken, hasExpression: statement->base, kind: Postfix); |
| 3091 | if (!current) |
| 3092 | return; |
| 3093 | |
| 3094 | pushScriptElement(element: current); |
| 3095 | } |
| 3096 | |
| 3097 | bool QQmlDomAstCreator::visit(AST::PreIncrementExpression *) |
| 3098 | { |
| 3099 | return m_enableScriptExpressions; |
| 3100 | } |
| 3101 | |
| 3102 | void QQmlDomAstCreator::endVisit(AST::PreIncrementExpression *statement) |
| 3103 | { |
| 3104 | if (!m_enableScriptExpressions) |
| 3105 | return; |
| 3106 | |
| 3107 | auto current = makeUnaryExpression(expression: statement, operatorToken: statement->incrementToken, hasExpression: statement->expression, |
| 3108 | kind: Prefix); |
| 3109 | if (!current) |
| 3110 | return; |
| 3111 | |
| 3112 | pushScriptElement(element: current); |
| 3113 | } |
| 3114 | |
| 3115 | bool QQmlDomAstCreator::visit(AST::EmptyStatement *) |
| 3116 | { |
| 3117 | return m_enableScriptExpressions; |
| 3118 | } |
| 3119 | |
| 3120 | void QQmlDomAstCreator::endVisit(AST::EmptyStatement *statement) |
| 3121 | { |
| 3122 | if (!m_enableScriptExpressions) |
| 3123 | return; |
| 3124 | |
| 3125 | auto current = makeGenericScriptElement(ast: statement, kind: DomType::ScriptEmptyStatement); |
| 3126 | current->addLocation(region: FileLocationRegion::SemicolonTokenRegion, location: statement->semicolonToken); |
| 3127 | pushScriptElement(element: current); |
| 3128 | } |
| 3129 | |
| 3130 | bool QQmlDomAstCreator::visit(AST::NestedExpression *) |
| 3131 | { |
| 3132 | return m_enableScriptExpressions; |
| 3133 | } |
| 3134 | |
| 3135 | void QQmlDomAstCreator::endVisit(AST::NestedExpression *expression) |
| 3136 | { |
| 3137 | if (!m_enableScriptExpressions) |
| 3138 | return; |
| 3139 | |
| 3140 | auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptParenthesizedExpression); |
| 3141 | current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: expression->lparenToken); |
| 3142 | current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: expression->rparenToken); |
| 3143 | |
| 3144 | if (expression->expression) { |
| 3145 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 3146 | current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant()); |
| 3147 | removeCurrentScriptNode(expectedType: {}); |
| 3148 | } |
| 3149 | |
| 3150 | pushScriptElement(element: current); |
| 3151 | } |
| 3152 | |
| 3153 | bool QQmlDomAstCreator::visit(AST::NewExpression *) |
| 3154 | { |
| 3155 | return m_enableScriptExpressions; |
| 3156 | } |
| 3157 | |
| 3158 | void QQmlDomAstCreator::endVisit(AST::NewExpression *expression) |
| 3159 | { |
| 3160 | if (!m_enableScriptExpressions) |
| 3161 | return; |
| 3162 | |
| 3163 | auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptNewExpression); |
| 3164 | current->addLocation(region: FileLocationRegion::NewKeywordRegion, location: expression->newToken); |
| 3165 | |
| 3166 | if (expression->expression) { |
| 3167 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 3168 | current->insertChild(name: Fields::expression, v: currentScriptNodeEl().takeVariant()); |
| 3169 | removeCurrentScriptNode(expectedType: {}); |
| 3170 | } |
| 3171 | |
| 3172 | pushScriptElement(element: current); |
| 3173 | } |
| 3174 | |
| 3175 | bool QQmlDomAstCreator::visit(AST::NewMemberExpression *) |
| 3176 | { |
| 3177 | return m_enableScriptExpressions; |
| 3178 | } |
| 3179 | |
| 3180 | void QQmlDomAstCreator::endVisit(AST::NewMemberExpression *expression) |
| 3181 | { |
| 3182 | if (!m_enableScriptExpressions) |
| 3183 | return; |
| 3184 | |
| 3185 | auto current = makeGenericScriptElement(ast: expression, kind: DomType::ScriptNewMemberExpression); |
| 3186 | current->addLocation(region: FileLocationRegion::NewKeywordRegion, location: expression->newToken); |
| 3187 | current->addLocation(region: FileLocationRegion::LeftParenthesisRegion, location: expression->lparenToken); |
| 3188 | current->addLocation(region: FileLocationRegion::RightParenthesisRegion, location: expression->rparenToken); |
| 3189 | |
| 3190 | if (expression->arguments) { |
| 3191 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptList()); |
| 3192 | current->insertChild(name: Fields::arguments, v: scriptNodeStack.takeLast().takeList()); |
| 3193 | } |
| 3194 | if (expression->base) { |
| 3195 | Q_SCRIPTELEMENT_EXIT_IF(!stackHasScriptVariant()); |
| 3196 | current->insertChild(name: Fields::base, v: scriptNodeStack.takeLast().takeVariant()); |
| 3197 | } |
| 3198 | |
| 3199 | pushScriptElement(element: current); |
| 3200 | } |
| 3201 | |
| 3202 | bool QQmlDomAstCreator::visit(AST::PreDecrementExpression *) |
| 3203 | { |
| 3204 | return m_enableScriptExpressions; |
| 3205 | } |
| 3206 | |
| 3207 | void QQmlDomAstCreator::endVisit(AST::PreDecrementExpression *statement) |
| 3208 | { |
| 3209 | if (!m_enableScriptExpressions) |
| 3210 | return; |
| 3211 | |
| 3212 | auto current = makeUnaryExpression(expression: statement, operatorToken: statement->decrementToken, hasExpression: statement->expression, |
| 3213 | kind: Prefix); |
| 3214 | if (!current) |
| 3215 | return; |
| 3216 | |
| 3217 | pushScriptElement(element: current); |
| 3218 | } |
| 3219 | |
| 3220 | static const DomEnvironment *environmentFrom(MutableDomItem &qmlFile) |
| 3221 | { |
| 3222 | auto top = qmlFile.top(); |
| 3223 | if (!top) { |
| 3224 | return {}; |
| 3225 | } |
| 3226 | auto domEnvironment = top.as<DomEnvironment>(); |
| 3227 | if (!domEnvironment) { |
| 3228 | return {}; |
| 3229 | } |
| 3230 | return domEnvironment; |
| 3231 | } |
| 3232 | |
| 3233 | static QStringList qmldirFilesFrom(MutableDomItem &qmlFile) |
| 3234 | { |
| 3235 | if (auto env = environmentFrom(qmlFile)) |
| 3236 | return env->qmldirFiles(); |
| 3237 | |
| 3238 | return {}; |
| 3239 | } |
| 3240 | |
| 3241 | QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, |
| 3242 | MutableDomItem &qmlFile, |
| 3243 | QQmlJSLogger *logger, |
| 3244 | QQmlJSImporter *importer) |
| 3245 | : m_root(current), |
| 3246 | m_logger(logger), |
| 3247 | m_importer(importer), |
| 3248 | m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory( |
| 3249 | localFile: m_logger->filePath(), mapper: m_importer->resourceFileMapper())), |
| 3250 | m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory, |
| 3251 | qmldirFilesFrom(qmlFile)), |
| 3252 | m_domCreator(qmlFile) |
| 3253 | { |
| 3254 | } |
| 3255 | |
| 3256 | #define X(name) \ |
| 3257 | bool QQmlDomAstCreatorWithQQmlJSScope::(name *node) \ |
| 3258 | { \ |
| 3259 | return visitT(node); \ |
| 3260 | } \ |
| 3261 | void QQmlDomAstCreatorWithQQmlJSScope::(name *node) \ |
| 3262 | { \ |
| 3263 | endVisitT(node); \ |
| 3264 | } |
| 3265 | QQmlJSASTClassListToVisit |
| 3266 | #undef X |
| 3267 | |
| 3268 | void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomAfterEndvisit() |
| 3269 | { |
| 3270 | const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; |
| 3271 | if (!m_domCreator.scriptNodeStack.isEmpty()) { |
| 3272 | auto topOfStack = m_domCreator.currentScriptNodeEl(); |
| 3273 | switch (topOfStack.kind) { |
| 3274 | case DomType::ScriptBlockStatement: |
| 3275 | case DomType::ScriptForStatement: |
| 3276 | case DomType::ScriptForEachStatement: |
| 3277 | case DomType::ScriptDoWhileStatement: |
| 3278 | case DomType::ScriptWhileStatement: |
| 3279 | case DomType::List: |
| 3280 | m_domCreator.currentScriptNodeEl().setSemanticScope(scope); |
| 3281 | break; |
| 3282 | case DomType::ScriptFunctionExpression: { |
| 3283 | // Put the body's scope into the function expression: function expressions will contain |
| 3284 | // their parents scope instead of their own without this |
| 3285 | auto element = m_domCreator.currentScriptNodeEl().value; |
| 3286 | auto scriptElementVariant = std::get_if<ScriptElementVariant>(ptr: &element); |
| 3287 | if (!scriptElementVariant || !scriptElementVariant->data()) |
| 3288 | break; |
| 3289 | scriptElementVariant->visit(visitor: [](auto &&e) { |
| 3290 | using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; |
| 3291 | if (e->kind() != DomType::ScriptFunctionExpression) |
| 3292 | return; |
| 3293 | |
| 3294 | if constexpr (std::is_same_v<U, |
| 3295 | ScriptElement::PointerType< |
| 3296 | ScriptElements::GenericScriptElement>>) { |
| 3297 | if (auto bodyPtr = e->elementChild(Fields::body)) { |
| 3298 | const auto bodyScope = bodyPtr.base()->semanticScope(); |
| 3299 | e->setSemanticScope(bodyScope); |
| 3300 | } |
| 3301 | } |
| 3302 | }); |
| 3303 | break; |
| 3304 | } |
| 3305 | |
| 3306 | // TODO: find which script elements also have a scope and implement them here |
| 3307 | default: |
| 3308 | break; |
| 3309 | }; |
| 3310 | } else if (!m_domCreator.nodeStack.isEmpty()) { |
| 3311 | std::visit( |
| 3312 | visitor: [&scope](auto &&e) { |
| 3313 | using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; |
| 3314 | // TODO: find which dom elements also have a scope and implement them here |
| 3315 | if constexpr (std::is_same_v<U, QmlObject>) { |
| 3316 | e.setSemanticScope(scope); |
| 3317 | } else if constexpr (std::is_same_v<U, QmlComponent>) { |
| 3318 | e.setSemanticScope(scope); |
| 3319 | } else if constexpr (std::is_same_v<U, MethodInfo>) { |
| 3320 | if (e.body) { |
| 3321 | if (auto scriptElement = e.body->scriptElement()) { |
| 3322 | scriptElement.base()->setSemanticScope(scope); |
| 3323 | } |
| 3324 | } |
| 3325 | e.setSemanticScope(scope); |
| 3326 | } |
| 3327 | }, |
| 3328 | variants&: m_domCreator.currentNodeEl().item.value); |
| 3329 | } |
| 3330 | } |
| 3331 | |
| 3332 | void QQmlDomAstCreatorWithQQmlJSScope::setScopeInDomBeforeEndvisit() |
| 3333 | { |
| 3334 | const QQmlJSScope::ConstPtr scope = m_scopeCreator.m_currentScope; |
| 3335 | |
| 3336 | // depending whether the property definition has a binding, the property definition might be |
| 3337 | // either at the last position in the stack or at the position before the last position. |
| 3338 | if (m_domCreator.nodeStack.size() > 1 |
| 3339 | && m_domCreator.nodeStack.last().item.kind == DomType::Binding) { |
| 3340 | std::visit( |
| 3341 | visitor: [&scope](auto &&e) { |
| 3342 | using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; |
| 3343 | if constexpr (std::is_same_v<U, PropertyDefinition>) { |
| 3344 | // Make sure to use the property definition scope instead of the binding |
| 3345 | // scope. If the current scope is a binding scope (this happens when the |
| 3346 | // property definition has a binding, like `property int i: 45` for |
| 3347 | // example), then the property definition scope is the parent of the current |
| 3348 | // scope. |
| 3349 | const bool usePropertyDefinitionScopeInsteadOfTheBindingScope = |
| 3350 | scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope |
| 3351 | && scope->parentScope() |
| 3352 | && scope->parentScope()->scopeType() == QQmlSA::ScopeType::QMLScope; |
| 3353 | e.setSemanticScope(usePropertyDefinitionScopeInsteadOfTheBindingScope |
| 3354 | ? scope->parentScope() |
| 3355 | : scope); |
| 3356 | } |
| 3357 | }, |
| 3358 | variants&: m_domCreator.currentNodeEl(i: 1).item.value); |
| 3359 | } |
| 3360 | if (m_domCreator.nodeStack.size() > 0) { |
| 3361 | std::visit( |
| 3362 | visitor: [&scope](auto &&e) { |
| 3363 | using U = std::remove_cv_t<std::remove_reference_t<decltype(e)>>; |
| 3364 | if constexpr (std::is_same_v<U, PropertyDefinition>) { |
| 3365 | e.setSemanticScope(scope); |
| 3366 | Q_ASSERT(e.semanticScope()); |
| 3367 | } else if constexpr (std::is_same_v<U, MethodInfo>) { |
| 3368 | if (e.methodType == MethodInfo::Signal) { |
| 3369 | e.setSemanticScope(scope); |
| 3370 | } |
| 3371 | } |
| 3372 | }, |
| 3373 | variants&: m_domCreator.currentNodeEl().item.value); |
| 3374 | } |
| 3375 | } |
| 3376 | |
| 3377 | void QQmlDomAstCreatorWithQQmlJSScope::throwRecursionDepthError() |
| 3378 | { |
| 3379 | } |
| 3380 | |
| 3381 | } // end namespace Dom |
| 3382 | } // end namespace QQmlJS |
| 3383 | |
| 3384 | #undef Q_SCRIPTELEMENT_DISABLE |
| 3385 | #undef Q_SCRIPTELEMENT_EXIT_IF |
| 3386 | |
| 3387 | QT_END_NAMESPACE |
| 3388 | |