| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2019 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the tools applications of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include "findunqualified.h" | 
| 30 | #include "importedmembersvisitor.h" | 
| 31 | #include "scopetree.h" | 
| 32 | #include "typedescriptionreader.h" | 
| 33 |  | 
| 34 | #include <QtQml/private/qqmljsast_p.h> | 
| 35 | #include <QtQml/private/qqmljslexer_p.h> | 
| 36 | #include <QtQml/private/qqmljsparser_p.h> | 
| 37 | #include <QtQml/private/qv4codegen_p.h> | 
| 38 | #include <QtQml/private/qqmldirparser_p.h> | 
| 39 |  | 
| 40 | #include <QtCore/qfile.h> | 
| 41 | #include <QtCore/qdiriterator.h> | 
| 42 | #include <QtCore/qscopedvaluerollback.h> | 
| 43 |  | 
| 44 | static const QString prefixedName(const QString &prefix, const QString &name) | 
| 45 | { | 
| 46 |     Q_ASSERT(!prefix.endsWith('.')); | 
| 47 |     return prefix.isEmpty() ? name : (prefix  + QLatin1Char('.') + name); | 
| 48 | } | 
| 49 |  | 
| 50 | static QQmlDirParser createQmldirParserForFile(const QString &filename) | 
| 51 | { | 
| 52 |     QFile f(filename); | 
| 53 |     f.open(flags: QFile::ReadOnly); | 
| 54 |     QQmlDirParser parser; | 
| 55 |     parser.parse(source: f.readAll()); | 
| 56 |     return parser; | 
| 57 | } | 
| 58 |  | 
| 59 | static TypeDescriptionReader createQmltypesReaderForFile(const QString &filename) | 
| 60 | { | 
| 61 |     QFile f(filename); | 
| 62 |     f.open(flags: QFile::ReadOnly); | 
| 63 |     TypeDescriptionReader reader { filename, f.readAll() }; | 
| 64 |     return reader; | 
| 65 | } | 
| 66 |  | 
| 67 | void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, const QString &name) | 
| 68 | { | 
| 69 |     m_currentScope = m_currentScope->createNewChildScope(type, name).get(); | 
| 70 | } | 
| 71 |  | 
| 72 | void FindUnqualifiedIDVisitor::leaveEnvironment() | 
| 73 | { | 
| 74 |     m_currentScope = m_currentScope->parentScope(); | 
| 75 | } | 
| 76 |  | 
| 77 | void FindUnqualifiedIDVisitor::(QQmlJS::AST::UiHeaderItemList *) | 
| 78 | { | 
| 79 |     using namespace QQmlJS::AST; | 
| 80 |  | 
| 81 |     while (header) { | 
| 82 |         if (auto import = cast<UiImport *>(ast: header->headerItem)) { | 
| 83 |             if (import->version) { | 
| 84 |                 QString path; | 
| 85 |                 auto uri = import->importUri; | 
| 86 |                 while (uri) { | 
| 87 |                     path.append(s: uri->name); | 
| 88 |                     path.append(s: "/" ); | 
| 89 |                     uri = uri->next; | 
| 90 |                 } | 
| 91 |                 path.chop(n: 1); | 
| 92 |                 importHelper(module: path, | 
| 93 |                              prefix: import->asToken.isValid() ? import->importId.toString() : QString(), | 
| 94 |                              major: import->version->majorVersion, | 
| 95 |                              minor: import->version->minorVersion); | 
| 96 |             } | 
| 97 |         } | 
| 98 |         header = header->next; | 
| 99 |     } | 
| 100 | } | 
| 101 |  | 
| 102 | ScopeTree *FindUnqualifiedIDVisitor::parseProgram(QQmlJS::AST::Program *program, | 
| 103 |                                                   const QString &name) | 
| 104 | { | 
| 105 |     using namespace QQmlJS::AST; | 
| 106 |     ScopeTree *result = new ScopeTree(ScopeType::JSLexicalScope, name); | 
| 107 |     for (auto *statement = program->statements; statement; statement = statement->next) { | 
| 108 |         if (auto *function = cast<FunctionDeclaration *>(ast: statement->statement)) { | 
| 109 |             MetaMethod method(function->name.toString()); | 
| 110 |             method.setMethodType(MetaMethod::Method); | 
| 111 |             for (auto *parameters = function->formals; parameters; parameters = parameters->next) | 
| 112 |                 method.addParameter(name: parameters->element->bindingIdentifier.toString(), type: "" ); | 
| 113 |             result->addMethod(method); | 
| 114 |         } | 
| 115 |     } | 
| 116 |     return result; | 
| 117 | } | 
| 118 |  | 
| 119 | enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned, BasePath }; | 
| 120 |  | 
| 121 | QStringList completeImportPaths(const QString &uri, const QString &basePath, int vmaj, int vmin) | 
| 122 | { | 
| 123 |     static const QLatin1Char Slash('/'); | 
| 124 |     static const QLatin1Char Backslash('\\'); | 
| 125 |  | 
| 126 |     const QVector<QStringRef> parts = uri.splitRef(sep: QLatin1Char('.'), behavior: Qt::SkipEmptyParts); | 
| 127 |  | 
| 128 |     QStringList qmlDirPathsPaths; | 
| 129 |     // fully & partially versioned parts + 1 unversioned for each base path | 
| 130 |     qmlDirPathsPaths.reserve(alloc: 2 * parts.count() + 1); | 
| 131 |  | 
| 132 |     auto versionString = [](int vmaj, int vmin, ImportVersion version) | 
| 133 |     { | 
| 134 |         if (version == FullyVersioned) { | 
| 135 |             // extension with fully encoded version number (eg. MyModule.3.2) | 
| 136 |             return QString::fromLatin1(str: ".%1.%2" ).arg(a: vmaj).arg(a: vmin); | 
| 137 |         } | 
| 138 |         if (version == PartiallyVersioned) { | 
| 139 |             // extension with encoded version major (eg. MyModule.3) | 
| 140 |             return QString::fromLatin1(str: ".%1" ).arg(a: vmaj); | 
| 141 |         } | 
| 142 |         // else extension without version number (eg. MyModule) | 
| 143 |         return QString(); | 
| 144 |     }; | 
| 145 |     auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep) { | 
| 146 |         QString str; | 
| 147 |         for (auto it = refs.cbegin(); it != refs.cend(); ++it) { | 
| 148 |             if (it != refs.cbegin()) | 
| 149 |                 str += sep; | 
| 150 |             str += *it; | 
| 151 |         } | 
| 152 |         return str; | 
| 153 |     }; | 
| 154 |  | 
| 155 |     const ImportVersion initial = (vmin >= 0) | 
| 156 |             ? FullyVersioned | 
| 157 |             : (vmaj >= 0 ? PartiallyVersioned : Unversioned); | 
| 158 |     for (int version = initial; version <= BasePath; ++version) { | 
| 159 |         const QString ver = versionString(vmaj, vmin, static_cast<ImportVersion>(version)); | 
| 160 |  | 
| 161 |         QString dir = basePath; | 
| 162 |         if (!dir.endsWith(c: Slash) && !dir.endsWith(c: Backslash)) | 
| 163 |             dir += Slash; | 
| 164 |  | 
| 165 |         if (version == BasePath) { | 
| 166 |             qmlDirPathsPaths += dir; | 
| 167 |         } else { | 
| 168 |             // append to the end | 
| 169 |             qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver; | 
| 170 |         } | 
| 171 |  | 
| 172 |         if (version < Unversioned) { | 
| 173 |             // insert in the middle | 
| 174 |             for (int index = parts.count() - 2; index >= 0; --index) { | 
| 175 |                 qmlDirPathsPaths += dir + joinStringRefs(parts.mid(pos: 0, len: index + 1), Slash) | 
| 176 |                         + ver + Slash | 
| 177 |                         + joinStringRefs(parts.mid(pos: index + 1), Slash); | 
| 178 |             } | 
| 179 |         } | 
| 180 |     } | 
| 181 |     return qmlDirPathsPaths; | 
| 182 | } | 
| 183 |  | 
| 184 | static const QLatin1String SlashQmldir             = QLatin1String("/qmldir" ); | 
| 185 | static const QLatin1String SlashPluginsDotQmltypes = QLatin1String("/plugins.qmltypes" ); | 
| 186 |  | 
| 187 | void FindUnqualifiedIDVisitor::readQmltypes(const QString &filename, | 
| 188 |                                             FindUnqualifiedIDVisitor::Import &result) | 
| 189 | { | 
| 190 |     auto reader = createQmltypesReaderForFile(filename); | 
| 191 |     auto succ = reader(&result.objects, &result.moduleApis, &result.dependencies); | 
| 192 |     if (!succ) | 
| 193 |         m_colorOut.writeUncolored(message: reader.errorMessage()); | 
| 194 | } | 
| 195 |  | 
| 196 | FindUnqualifiedIDVisitor::Import FindUnqualifiedIDVisitor::readQmldir(const QString &path) | 
| 197 | { | 
| 198 |     Import result; | 
| 199 |     auto reader = createQmldirParserForFile(filename: path + SlashQmldir); | 
| 200 |     const auto imports = reader.imports(); | 
| 201 |     for (const QString &import : imports) | 
| 202 |         result.dependencies.append(t: import); | 
| 203 |  | 
| 204 |     QHash<QString, ScopeTree *> qmlComponents; | 
| 205 |     const auto components = reader.components(); | 
| 206 |     for (auto it = components.begin(), end = components.end(); it != end; ++it) { | 
| 207 |         const QString filePath = path + QLatin1Char('/') + it->fileName; | 
| 208 |         if (!QFile::exists(fileName: filePath)) { | 
| 209 |             m_colorOut.write(message: QLatin1String("warning: " ), color: Warning); | 
| 210 |             m_colorOut.write(message: it->fileName + QLatin1String(" is listed as component in " ) | 
| 211 |                              + path + SlashQmldir | 
| 212 |                              + QLatin1String(" but does not exist.\n" )); | 
| 213 |             continue; | 
| 214 |         } | 
| 215 |  | 
| 216 |         auto mo = qmlComponents.find(akey: it.key()); | 
| 217 |         if (mo == qmlComponents.end()) | 
| 218 |             mo = qmlComponents.insert(akey: it.key(), avalue: localFile2ScopeTree(filePath)); | 
| 219 |  | 
| 220 |         (*mo)->addExport( | 
| 221 |                     name: it.key(), package: reader.typeNamespace(), | 
| 222 |                     version: ComponentVersion(it->majorVersion, it->minorVersion)); | 
| 223 |     } | 
| 224 |     for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) | 
| 225 |         result.objects.insert( akey: it.key(), avalue: ScopeTree::ConstPtr(it.value())); | 
| 226 |  | 
| 227 |     if (!reader.plugins().isEmpty() && QFile::exists(fileName: path + SlashPluginsDotQmltypes)) | 
| 228 |         readQmltypes(filename: path + SlashPluginsDotQmltypes, result); | 
| 229 |  | 
| 230 |     return result; | 
| 231 | } | 
| 232 |  | 
| 233 | void FindUnqualifiedIDVisitor::processImport(const QString &prefix, const FindUnqualifiedIDVisitor::Import &import) | 
| 234 | { | 
| 235 |     for (auto const &dependency : qAsConst(t: import.dependencies)) { | 
| 236 |         auto const split = dependency.split(sep: " " ); | 
| 237 |         auto const &id = split.at(i: 0); | 
| 238 |         if (split.length() > 1) { | 
| 239 |             const auto version = split.at(i: 1).split(sep: '.'); | 
| 240 |             importHelper(module: id, prefix: QString(), | 
| 241 |                          major: version.at(i: 0).toInt(), | 
| 242 |                          minor: version.length() > 1 ? version.at(i: 1).toInt() : -1); | 
| 243 |         } else { | 
| 244 |             importHelper(module: id, prefix: QString(), major: -1, minor: -1); | 
| 245 |         } | 
| 246 |  | 
| 247 |  | 
| 248 |     } | 
| 249 |  | 
| 250 |     // add objects | 
| 251 |     for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { | 
| 252 |         const auto &val = it.value(); | 
| 253 |         m_types[it.key()] = val; | 
| 254 |         m_exportedName2Scope.insert(akey: prefixedName(prefix, name: val->className()), avalue: val); | 
| 255 |  | 
| 256 |         const auto exports = val->exports(); | 
| 257 |         for (const auto &valExport : exports) | 
| 258 |             m_exportedName2Scope.insert(akey: prefixedName(prefix, name: valExport.type()), avalue: val); | 
| 259 |  | 
| 260 |         const auto enums = val->enums(); | 
| 261 |         for (const auto &valEnum : enums) | 
| 262 |             m_currentScope->addEnum(fakeEnum: valEnum); | 
| 263 |     } | 
| 264 | } | 
| 265 |  | 
| 266 | void FindUnqualifiedIDVisitor::importHelper(const QString &module, const QString &prefix, | 
| 267 |                                             int major, int minor) | 
| 268 | { | 
| 269 |     const QString id = QString(module).replace(before: QLatin1Char('/'), after: QLatin1Char('.')); | 
| 270 |     QPair<QString, QString> importId { id, prefix }; | 
| 271 |     if (m_alreadySeenImports.contains(value: importId)) | 
| 272 |         return; | 
| 273 |     m_alreadySeenImports.insert(value: importId); | 
| 274 |  | 
| 275 |     for (const QString &qmltypeDir : m_qmltypeDirs) { | 
| 276 |         auto qmltypesPaths = completeImportPaths(uri: id, basePath: qmltypeDir, vmaj: major, vmin: minor); | 
| 277 |  | 
| 278 |         for (auto const &qmltypesPath : qmltypesPaths) { | 
| 279 |             if (QFile::exists(fileName: qmltypesPath + SlashQmldir)) { | 
| 280 |                 processImport(prefix, import: readQmldir(path: qmltypesPath)); | 
| 281 |  | 
| 282 |                 // break so that we don't import unversioned qml components | 
| 283 |                 // in addition to versioned ones | 
| 284 |                 break; | 
| 285 |             } | 
| 286 |  | 
| 287 |             if (!m_qmltypeFiles.isEmpty()) | 
| 288 |                 continue; | 
| 289 |  | 
| 290 |             Import result; | 
| 291 |  | 
| 292 |             QDirIterator it { qmltypesPath, QStringList() << QLatin1String("*.qmltypes" ), QDir::Files }; | 
| 293 |  | 
| 294 |             while (it.hasNext()) | 
| 295 |                 readQmltypes(filename: it.next(), result); | 
| 296 |  | 
| 297 |             processImport(prefix, import: result); | 
| 298 |         } | 
| 299 |     } | 
| 300 |  | 
| 301 |     if (!m_qmltypeFiles.isEmpty()) | 
| 302 |     { | 
| 303 |         Import result; | 
| 304 |  | 
| 305 |         for (const auto &qmltypeFile : m_qmltypeFiles) | 
| 306 |             readQmltypes(filename: qmltypeFile, result); | 
| 307 |  | 
| 308 |         processImport(prefix: "" , import: result); | 
| 309 |     } | 
| 310 | } | 
| 311 |  | 
| 312 | ScopeTree *FindUnqualifiedIDVisitor::localFile2ScopeTree(const QString &filePath) | 
| 313 | { | 
| 314 |     using namespace QQmlJS::AST; | 
| 315 |     const QFileInfo info { filePath }; | 
| 316 |     QString baseName = info.baseName(); | 
| 317 |     const QString scopeName = baseName.endsWith(s: ".ui" ) ? baseName.chopped(n: 3) : baseName; | 
| 318 |  | 
| 319 |     QQmlJS::Engine engine; | 
| 320 |     QQmlJS::Lexer lexer(&engine); | 
| 321 |  | 
| 322 |     const QString lowerSuffix = info.suffix().toLower(); | 
| 323 |     const bool isESModule = lowerSuffix == QLatin1String("mjs" ); | 
| 324 |     const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js" ); | 
| 325 |  | 
| 326 |     QFile file(filePath); | 
| 327 |     if (!file.open(flags: QFile::ReadOnly)) { | 
| 328 |         return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, | 
| 329 |                              scopeName); | 
| 330 |     } | 
| 331 |  | 
| 332 |     QString code = file.readAll(); | 
| 333 |     file.close(); | 
| 334 |  | 
| 335 |     lexer.setCode(code, /*line = */ lineno: 1, /*qmlMode=*/ !isJavaScript); | 
| 336 |     QQmlJS::Parser parser(&engine); | 
| 337 |  | 
| 338 |     const bool success = isJavaScript ? (isESModule ? parser.parseModule() | 
| 339 |                                                     : parser.parseProgram()) | 
| 340 |                                       : parser.parse(); | 
| 341 |     if (!success) { | 
| 342 |         return new ScopeTree(isJavaScript ? ScopeType::JSLexicalScope : ScopeType::QMLScope, | 
| 343 |                              scopeName); | 
| 344 |     } | 
| 345 |  | 
| 346 |     if (!isJavaScript) { | 
| 347 |         QQmlJS::AST::UiProgram *program = parser.ast(); | 
| 348 |         parseHeaders(header: program->headers); | 
| 349 |         ImportedMembersVisitor membersVisitor(&m_colorOut); | 
| 350 |         program->members->accept(visitor: &membersVisitor); | 
| 351 |         return membersVisitor.result(scopeName); | 
| 352 |     } | 
| 353 |  | 
| 354 |     // TODO: Anything special to do with ES modules here? | 
| 355 |     return parseProgram(program: QQmlJS::AST::cast<QQmlJS::AST::Program *>(parser.rootNode()), name: scopeName); | 
| 356 | } | 
| 357 |  | 
| 358 | void FindUnqualifiedIDVisitor::importFileOrDirectory(const QString &fileOrDirectory, | 
| 359 |                                                      const QString &prefix) | 
| 360 | { | 
| 361 |     QString name = fileOrDirectory; | 
| 362 |  | 
| 363 |     if (QFileInfo(name).isRelative()) | 
| 364 |         name = QDir(QFileInfo { m_filePath }.path()).filePath(fileName: name); | 
| 365 |  | 
| 366 |     if (QFileInfo(name).isFile()) { | 
| 367 |         m_exportedName2Scope.insert(akey: prefix, avalue: ScopeTree::ConstPtr(localFile2ScopeTree(filePath: name))); | 
| 368 |         return; | 
| 369 |     } | 
| 370 |  | 
| 371 |     QDirIterator it { name, QStringList() << QLatin1String("*.qml" ), QDir::NoFilter }; | 
| 372 |     while (it.hasNext()) { | 
| 373 |         ScopeTree::ConstPtr scope(localFile2ScopeTree(filePath: it.next())); | 
| 374 |         if (!scope->className().isEmpty()) | 
| 375 |             m_exportedName2Scope.insert(akey: prefixedName(prefix, name: scope->className()), avalue: scope); | 
| 376 |     } | 
| 377 | } | 
| 378 |  | 
| 379 | void FindUnqualifiedIDVisitor::importExportedNames(const QStringRef &prefix, QString name) | 
| 380 | { | 
| 381 |     QList<ScopeTree::ConstPtr> scopes; | 
| 382 |     for (;;) { | 
| 383 |         ScopeTree::ConstPtr scope = m_exportedName2Scope.value(akey: m_exportedName2Scope.contains(akey: name) | 
| 384 |                                                                ? name | 
| 385 |                                                                : prefix + QLatin1Char('.') + name); | 
| 386 |         if (scope) { | 
| 387 |             if (scopes.contains(t: scope)) { | 
| 388 |                 QString inheritenceCycle = name; | 
| 389 |                 for (const auto &seen: qAsConst(t&: scopes)) { | 
| 390 |                     inheritenceCycle.append(s: QLatin1String(" -> " )); | 
| 391 |                     inheritenceCycle.append(s: seen->superclassName()); | 
| 392 |                 } | 
| 393 |  | 
| 394 |                 m_colorOut.write(message: QLatin1String("Warning: " ), color: Warning); | 
| 395 |                 m_colorOut.write(message: QString::fromLatin1(str: "%1 is part of an inheritance cycle: %2\n" ) | 
| 396 |                                  .arg(a: name) | 
| 397 |                                  .arg(a: inheritenceCycle)); | 
| 398 |                 m_unknownImports.insert(value: name); | 
| 399 |                 m_visitFailed = true; | 
| 400 |                 break; | 
| 401 |             } | 
| 402 |             scopes.append(t: scope); | 
| 403 |             const auto properties = scope->properties(); | 
| 404 |             for (auto property : properties) { | 
| 405 |                 property.setType(m_exportedName2Scope.value(akey: property.typeName()).get()); | 
| 406 |                 m_currentScope->insertPropertyIdentifier(prop: property); | 
| 407 |             } | 
| 408 |  | 
| 409 |             m_currentScope->addMethods(methods: scope->methods()); | 
| 410 |             name = scope->superclassName(); | 
| 411 |             if (name.isEmpty() || name == QLatin1String("QObject" )) | 
| 412 |                 break; | 
| 413 |         } else { | 
| 414 |             m_colorOut.write(message: QLatin1String("warning: " ), color: Warning); | 
| 415 |             m_colorOut.write(message: name + QLatin1String(" was not found."  | 
| 416 |                                                   " Did you add all import paths?\n" )); | 
| 417 |             m_unknownImports.insert(value: name); | 
| 418 |             m_visitFailed = true; | 
| 419 |             break; | 
| 420 |         } | 
| 421 |     } | 
| 422 | } | 
| 423 |  | 
| 424 | void FindUnqualifiedIDVisitor::throwRecursionDepthError() | 
| 425 | { | 
| 426 |     m_colorOut.write(QStringLiteral("Error" ), color: Error); | 
| 427 |     m_colorOut.write(QStringLiteral("Maximum statement or expression depth exceeded" ), color: Error); | 
| 428 |     m_visitFailed = true; | 
| 429 | } | 
| 430 |  | 
| 431 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) | 
| 432 | { | 
| 433 |     enterEnvironment(type: ScopeType::QMLScope, name: "program" ); | 
| 434 |     QHash<QString, ScopeTree::ConstPtr> objects; | 
| 435 |     QList<ModuleApiInfo> moduleApis; | 
| 436 |     QStringList dependencies; | 
| 437 |     for (auto const &dir : m_qmltypeDirs) { | 
| 438 |         QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes" ), QDir::NoFilter, | 
| 439 |                           QDirIterator::Subdirectories }; | 
| 440 |         while (it.hasNext()) { | 
| 441 |             auto reader = createQmltypesReaderForFile(filename: it.next()); | 
| 442 |             auto succ = reader(&objects, &moduleApis, &dependencies); | 
| 443 |             if (!succ) | 
| 444 |                 m_colorOut.writeUncolored(message: reader.errorMessage()); | 
| 445 |         } | 
| 446 |     } | 
| 447 |  | 
| 448 |     if (!m_qmltypeFiles.isEmpty()) | 
| 449 |     { | 
| 450 |         for (const auto &qmltypeFile : m_qmltypeFiles) { | 
| 451 |             auto reader = createQmltypesReaderForFile(filename: qmltypeFile); | 
| 452 |             auto succ = reader(&objects, &moduleApis, &dependencies); | 
| 453 |             if (!succ) | 
| 454 |                 m_colorOut.writeUncolored(message: reader.errorMessage()); | 
| 455 |         } | 
| 456 |     } | 
| 457 |  | 
| 458 |     // add builtins | 
| 459 |     for (auto objectIt = objects.begin(); objectIt != objects.end(); ++objectIt) { | 
| 460 |         auto val = objectIt.value(); | 
| 461 |         m_types[objectIt.key()] = val; | 
| 462 |  | 
| 463 |         const auto exports = val->exports(); | 
| 464 |         for (const auto &valExport : exports) | 
| 465 |             m_exportedName2Scope.insert(akey: valExport.type(), avalue: val); | 
| 466 |  | 
| 467 |         const auto enums = val->enums(); | 
| 468 |         for (const auto &valEnum : enums) | 
| 469 |             m_currentScope->addEnum(fakeEnum: valEnum); | 
| 470 |     } | 
| 471 |     // add "self" (as we only ever check the first part of a qualified identifier, we get away with | 
| 472 |     // using an empty ScopeTree | 
| 473 |     m_exportedName2Scope.insert(akey: QFileInfo { m_filePath }.baseName(), avalue: {}); | 
| 474 |  | 
| 475 |     importFileOrDirectory(fileOrDirectory: "." , prefix: QString()); | 
| 476 |     return true; | 
| 477 | } | 
| 478 |  | 
| 479 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiProgram *) | 
| 480 | { | 
| 481 |     leaveEnvironment(); | 
| 482 | } | 
| 483 |  | 
| 484 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassExpression *ast) | 
| 485 | { | 
| 486 |     enterEnvironment(type: ScopeType::JSFunctionScope, name: ast->name.toString()); | 
| 487 |     return true; | 
| 488 | } | 
| 489 |  | 
| 490 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassExpression *) | 
| 491 | { | 
| 492 |     leaveEnvironment(); | 
| 493 | } | 
| 494 |  | 
| 495 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassDeclaration *ast) | 
| 496 | { | 
| 497 |     enterEnvironment(type: ScopeType::JSFunctionScope, name: ast->name.toString()); | 
| 498 |     return true; | 
| 499 | } | 
| 500 |  | 
| 501 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassDeclaration *) | 
| 502 | { | 
| 503 |     leaveEnvironment(); | 
| 504 | } | 
| 505 |  | 
| 506 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForStatement *) | 
| 507 | { | 
| 508 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "forloop" ); | 
| 509 |     return true; | 
| 510 | } | 
| 511 |  | 
| 512 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForStatement *) | 
| 513 | { | 
| 514 |     leaveEnvironment(); | 
| 515 | } | 
| 516 |  | 
| 517 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForEachStatement *) | 
| 518 | { | 
| 519 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "foreachloop" ); | 
| 520 |     return true; | 
| 521 | } | 
| 522 |  | 
| 523 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForEachStatement *) | 
| 524 | { | 
| 525 |     leaveEnvironment(); | 
| 526 | } | 
| 527 |  | 
| 528 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Block *) | 
| 529 | { | 
| 530 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "block" ); | 
| 531 |     return true; | 
| 532 | } | 
| 533 |  | 
| 534 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Block *) | 
| 535 | { | 
| 536 |     leaveEnvironment(); | 
| 537 | } | 
| 538 |  | 
| 539 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::CaseBlock *) | 
| 540 | { | 
| 541 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "case" ); | 
| 542 |     return true; | 
| 543 | } | 
| 544 |  | 
| 545 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *) | 
| 546 | { | 
| 547 |     leaveEnvironment(); | 
| 548 | } | 
| 549 |  | 
| 550 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *catchStatement) | 
| 551 | { | 
| 552 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "catch" ); | 
| 553 |     m_currentScope->insertJSIdentifier(id: catchStatement->patternElement->bindingIdentifier.toString(), | 
| 554 |                                        scope: QQmlJS::AST::VariableScope::Let); | 
| 555 |     return true; | 
| 556 | } | 
| 557 |  | 
| 558 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *) | 
| 559 | { | 
| 560 |     leaveEnvironment(); | 
| 561 | } | 
| 562 |  | 
| 563 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::WithStatement *withStatement) | 
| 564 | { | 
| 565 |     m_colorOut.write(message: QString::fromLatin1(str: "Warning: " ), color: Warning); | 
| 566 |     m_colorOut.write(message: QString::fromLatin1( | 
| 567 |                          str: "%1:%2: with statements are strongly discouraged in QML "  | 
| 568 |                          "and might cause false positives when analysing unqalified identifiers\n" ) | 
| 569 |                      .arg(a: withStatement->firstSourceLocation().startLine) | 
| 570 |                      .arg(a: withStatement->firstSourceLocation().startColumn), | 
| 571 |                      color: Normal); | 
| 572 |     enterEnvironment(type: ScopeType::JSLexicalScope, name: "with" ); | 
| 573 |     return true; | 
| 574 | } | 
| 575 |  | 
| 576 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) | 
| 577 | { | 
| 578 |     leaveEnvironment(); | 
| 579 | } | 
| 580 |  | 
| 581 | static QString signalName(const QStringRef &handlerName) | 
| 582 | { | 
| 583 |     if (handlerName.startsWith(s: "on" ) && handlerName.size() > 2) { | 
| 584 |         QString signal = handlerName.mid(pos: 2).toString(); | 
| 585 |         for (int i = 0; i < signal.length(); ++i) { | 
| 586 |             QCharRef ch = signal[i]; | 
| 587 |             if (ch.isLower()) | 
| 588 |                 return QString(); | 
| 589 |             if (ch.isUpper()) { | 
| 590 |                 ch = ch.toLower(); | 
| 591 |                 return signal; | 
| 592 |             } | 
| 593 |         } | 
| 594 |     } | 
| 595 |     return QString(); | 
| 596 | } | 
| 597 |  | 
| 598 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) | 
| 599 | { | 
| 600 |     using namespace QQmlJS::AST; | 
| 601 |     auto name = uisb->qualifiedId->name; | 
| 602 |     if (name == QLatin1String("id" )) { | 
| 603 |         // found id | 
| 604 |         auto expstat = cast<ExpressionStatement *>(ast: uisb->statement); | 
| 605 |         auto identexp = cast<IdentifierExpression *>(ast: expstat->expression); | 
| 606 |         QString elementName = m_currentScope->name(); | 
| 607 |         m_qmlid2scope.insert(akey: identexp->name.toString(), avalue: m_currentScope); | 
| 608 |         if (m_currentScope->isVisualRootScope()) | 
| 609 |             m_rootId = identexp->name.toString(); | 
| 610 |     } else { | 
| 611 |         const QString signal = signalName(handlerName: name); | 
| 612 |         if (signal.isEmpty()) | 
| 613 |             return true; | 
| 614 |  | 
| 615 |         if (!m_currentScope->methods().contains(akey: signal)) { | 
| 616 |             m_currentScope->addUnmatchedSignalHandler(handler: name.toString(), location: uisb->firstSourceLocation()); | 
| 617 |             return true; | 
| 618 |         } | 
| 619 |  | 
| 620 |         const auto statement = uisb->statement; | 
| 621 |         if (statement->kind == Node::Kind::Kind_ExpressionStatement) { | 
| 622 |             if (cast<ExpressionStatement *>(ast: statement)->expression->asFunctionDefinition()) { | 
| 623 |                 // functions are already handled | 
| 624 |                 // they do not get names inserted according to the signal, but access their formal | 
| 625 |                 // parameters | 
| 626 |                 return true; | 
| 627 |             } | 
| 628 |         } | 
| 629 |  | 
| 630 |         auto method = m_currentScope->methods()[signal]; | 
| 631 |         for (auto const ¶m : method.parameterNames()) { | 
| 632 |             const auto firstSourceLocation = statement->firstSourceLocation(); | 
| 633 |             bool hasMultilineStatementBody | 
| 634 |                     = statement->lastSourceLocation().startLine > firstSourceLocation.startLine; | 
| 635 |             m_currentScope->insertSignalIdentifier(id: param, method, loc: firstSourceLocation, | 
| 636 |                                                    hasMultilineHandlerBody: hasMultilineStatementBody); | 
| 637 |         } | 
| 638 |         return true; | 
| 639 |     } | 
| 640 |     return true; | 
| 641 | } | 
| 642 |  | 
| 643 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) | 
| 644 | { | 
| 645 |     // property bool inactive: !active | 
| 646 |     // extract name inactive | 
| 647 |     MetaProperty property( | 
| 648 |                 uipm->name.toString(), | 
| 649 |                 // TODO: signals, complex types etc. | 
| 650 |                 uipm->memberType ? uipm->memberType->name.toString() : QString(), | 
| 651 |                 uipm->typeModifier == QLatin1String("list" ), | 
| 652 |                 !uipm->isReadonlyMember, | 
| 653 |                 false, | 
| 654 |                 uipm->memberType ? (uipm->memberType->name == QLatin1String("alias" )) : false, | 
| 655 |                 0); | 
| 656 |     property.setType(m_exportedName2Scope.value(akey: property.typeName()).get()); | 
| 657 |     m_currentScope->insertPropertyIdentifier(prop: property); | 
| 658 |     return true; | 
| 659 | } | 
| 660 |  | 
| 661 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) | 
| 662 | { | 
| 663 |     auto name = idexp->name; | 
| 664 |     m_currentScope->addIdToAccessed(id: name.toString(), location: idexp->firstSourceLocation()); | 
| 665 |     m_fieldMemberBase = idexp; | 
| 666 |     return true; | 
| 667 | } | 
| 668 |  | 
| 669 | FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList qmltypeDirs, QStringList qmltypeFiles, QString code, | 
| 670 |                                                    QString fileName, bool silent) | 
| 671 |     : m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global"  }), | 
| 672 |       m_currentScope(m_rootScope.get()), | 
| 673 |       m_qmltypeDirs(std::move(qmltypeDirs)), | 
| 674 |       m_qmltypeFiles(std::move(qmltypeFiles)), | 
| 675 |       m_code(std::move(code)), | 
| 676 |       m_rootId(QLatin1String("<id>" )), | 
| 677 |       m_filePath(std::move(fileName)), | 
| 678 |       m_colorOut(silent) | 
| 679 | { | 
| 680 |     // setup color output | 
| 681 |     m_colorOut.insertMapping(colorID: Error, colorCode: ColorOutput::RedForeground); | 
| 682 |     m_colorOut.insertMapping(colorID: Warning, colorCode: ColorOutput::PurpleForeground); | 
| 683 |     m_colorOut.insertMapping(colorID: Info, colorCode: ColorOutput::BlueForeground); | 
| 684 |     m_colorOut.insertMapping(colorID: Normal, colorCode: ColorOutput::DefaultColor); | 
| 685 |     m_colorOut.insertMapping(colorID: Hint, colorCode: ColorOutput::GreenForeground); | 
| 686 |     QLatin1String jsGlobVars[] = { | 
| 687 |         /* Not listed on the MDN page; browser and QML extensions: */ | 
| 688 |         // console/debug api | 
| 689 |         QLatin1String("console" ), QLatin1String("print" ), | 
| 690 |         // garbage collector | 
| 691 |         QLatin1String("gc" ), | 
| 692 |         // i18n | 
| 693 |         QLatin1String("qsTr" ), QLatin1String("qsTrId" ), QLatin1String("QT_TR_NOOP" ), | 
| 694 |         QLatin1String("QT_TRANSLATE_NOOP" ), QLatin1String("QT_TRID_NOOP" ), | 
| 695 |         // XMLHttpRequest | 
| 696 |         QLatin1String("XMLHttpRequest" ) | 
| 697 |     }; | 
| 698 |     for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; | 
| 699 |          *globalName != nullptr; | 
| 700 |          ++globalName) { | 
| 701 |         m_currentScope->insertJSIdentifier(id: QString::fromLatin1(str: *globalName), | 
| 702 |                                            scope: QQmlJS::AST::VariableScope::Const); | 
| 703 |     } | 
| 704 |     for (const auto& jsGlobVar: jsGlobVars) | 
| 705 |         m_currentScope->insertJSIdentifier(id: jsGlobVar, scope: QQmlJS::AST::VariableScope::Const); | 
| 706 | } | 
| 707 |  | 
| 708 | bool FindUnqualifiedIDVisitor::check() | 
| 709 | { | 
| 710 |     if (m_visitFailed) | 
| 711 |         return false; | 
| 712 |  | 
| 713 |     // now that all ids are known, revisit any Connections whose target were perviously unknown | 
| 714 |     for (auto const &outstandingConnection: m_outstandingConnections) { | 
| 715 |         auto targetScope = m_qmlid2scope[outstandingConnection.targetName]; | 
| 716 |         if (outstandingConnection.scope) | 
| 717 |             outstandingConnection.scope->addMethods(methods: targetScope->methods()); | 
| 718 |         QScopedValueRollback<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope); | 
| 719 |         outstandingConnection.uiod->initializer->accept(visitor: this); | 
| 720 |     } | 
| 721 |     return m_rootScope->recheckIdentifiers(code: m_code, qmlIDs: m_qmlid2scope, types: m_exportedName2Scope, | 
| 722 |                                            root: m_rootScope.get(), rootId: m_rootId, colorOut&: m_colorOut); | 
| 723 | } | 
| 724 |  | 
| 725 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) | 
| 726 | { | 
| 727 |     while (vdl) { | 
| 728 |         m_currentScope->insertJSIdentifier(id: vdl->declaration->bindingIdentifier.toString(), | 
| 729 |                                            scope: vdl->declaration->scope); | 
| 730 |         vdl = vdl->next; | 
| 731 |     } | 
| 732 |     return true; | 
| 733 | } | 
| 734 |  | 
| 735 | void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr) | 
| 736 | { | 
| 737 |     using namespace QQmlJS::AST; | 
| 738 |     auto name = fexpr->name.toString(); | 
| 739 |     if (!name.isEmpty()) { | 
| 740 |         if (m_currentScope->scopeType() == ScopeType::QMLScope) | 
| 741 |             m_currentScope->addMethod(method: MetaMethod(name, QLatin1String("void" ))); | 
| 742 |         else | 
| 743 |             m_currentScope->insertJSIdentifier(id: name, scope: VariableScope::Const); | 
| 744 |         enterEnvironment(type: ScopeType::JSFunctionScope, name); | 
| 745 |     } else { | 
| 746 |         enterEnvironment(type: ScopeType::JSFunctionScope, name: QLatin1String("<anon>" )); | 
| 747 |     } | 
| 748 | } | 
| 749 |  | 
| 750 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr) | 
| 751 | { | 
| 752 |     visitFunctionExpressionHelper(fexpr); | 
| 753 |     return true; | 
| 754 | } | 
| 755 |  | 
| 756 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionExpression *) | 
| 757 | { | 
| 758 |     leaveEnvironment(); | 
| 759 | } | 
| 760 |  | 
| 761 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl) | 
| 762 | { | 
| 763 |     visitFunctionExpressionHelper(fexpr: fdecl); | 
| 764 |     return true; | 
| 765 | } | 
| 766 |  | 
| 767 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *) | 
| 768 | { | 
| 769 |     leaveEnvironment(); | 
| 770 | } | 
| 771 |  | 
| 772 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FormalParameterList *fpl) | 
| 773 | { | 
| 774 |     for (auto const &boundName : fpl->boundNames()) { | 
| 775 |         m_currentScope->insertJSIdentifier(id: boundName.id, scope: QQmlJS::AST::VariableScope::Const); | 
| 776 |     } | 
| 777 |     return true; | 
| 778 | } | 
| 779 |  | 
| 780 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) | 
| 781 | { | 
| 782 |     // construct path | 
| 783 |     QString prefix = QLatin1String("" ); | 
| 784 |     if (import->asToken.isValid()) { | 
| 785 |         prefix += import->importId; | 
| 786 |     } | 
| 787 |     auto dirname = import->fileName.toString(); | 
| 788 |     if (!dirname.isEmpty()) | 
| 789 |         importFileOrDirectory(fileOrDirectory: dirname, prefix); | 
| 790 |  | 
| 791 |     QString path {}; | 
| 792 |     if (!import->importId.isEmpty()) { | 
| 793 |         // TODO: do not put imported ids into the same space as qml IDs | 
| 794 |         const QString importId = import->importId.toString(); | 
| 795 |         m_qmlid2scope.insert(akey: importId, avalue: m_exportedName2Scope.value(akey: importId).get()); | 
| 796 |     } | 
| 797 |     if (import->version) { | 
| 798 |         auto uri = import->importUri; | 
| 799 |         while (uri) { | 
| 800 |             path.append(s: uri->name); | 
| 801 |             path.append(s: "/" ); | 
| 802 |             uri = uri->next; | 
| 803 |         } | 
| 804 |         path.chop(n: 1); | 
| 805 |  | 
| 806 |         importHelper(module: path, prefix, major: import->version->majorVersion, minor: import->version->minorVersion); | 
| 807 |     } | 
| 808 |     return true; | 
| 809 | } | 
| 810 |  | 
| 811 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) | 
| 812 | { | 
| 813 |     MetaEnum qmlEnum(uied->name.toString()); | 
| 814 |     for (const auto *member = uied->members; member; member = member->next) | 
| 815 |         qmlEnum.addKey(key: member->member.toString()); | 
| 816 |     m_currentScope->addEnum(fakeEnum: qmlEnum); | 
| 817 |     return true; | 
| 818 | } | 
| 819 |  | 
| 820 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) | 
| 821 | { | 
| 822 |     // property QtObject __styleData: QtObject {...} | 
| 823 |  | 
| 824 |     QString name {}; | 
| 825 |     auto id = uiob->qualifiedTypeNameId; | 
| 826 |     QStringRef prefix = uiob->qualifiedTypeNameId->name; | 
| 827 |     while (id) { | 
| 828 |         name += id->name.toString() + QLatin1Char('.'); | 
| 829 |         id = id->next; | 
| 830 |     } | 
| 831 |     name.chop(n: 1); | 
| 832 |  | 
| 833 |     MetaProperty prop(uiob->qualifiedId->name.toString(), name, false, true, true, | 
| 834 |                       name == QLatin1String("alias" ), 0); | 
| 835 |     prop.setType(m_exportedName2Scope.value(akey: uiob->qualifiedTypeNameId->name.toString()).get()); | 
| 836 |     m_currentScope->addProperty(prop); | 
| 837 |  | 
| 838 |     enterEnvironment(type: ScopeType::QMLScope, name); | 
| 839 |     importExportedNames(prefix, name); | 
| 840 |     return true; | 
| 841 | } | 
| 842 |  | 
| 843 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) | 
| 844 | { | 
| 845 |     const auto childScope = m_currentScope; | 
| 846 |     leaveEnvironment(); | 
| 847 |     MetaProperty property(uiob->qualifiedId->name.toString(), | 
| 848 |                           uiob->qualifiedTypeNameId->name.toString(), | 
| 849 |                           false, true, true, | 
| 850 |                           uiob->qualifiedTypeNameId->name == QLatin1String("alias" ), | 
| 851 |                           0); | 
| 852 |     property.setType(childScope); | 
| 853 |     m_currentScope->addProperty(prop: property); | 
| 854 | } | 
| 855 |  | 
| 856 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) | 
| 857 | { | 
| 858 |     using namespace QQmlJS::AST; | 
| 859 |  | 
| 860 |     QString name {}; | 
| 861 |     auto id = uiod->qualifiedTypeNameId; | 
| 862 |     QStringRef prefix = uiod->qualifiedTypeNameId->name; | 
| 863 |     while (id) { | 
| 864 |         name += id->name.toString() + QLatin1Char('.'); | 
| 865 |         id = id->next; | 
| 866 |     } | 
| 867 |     name.chop(n: 1); | 
| 868 |     enterEnvironment(type: ScopeType::QMLScope, name); | 
| 869 |     if (name.isLower()) | 
| 870 |         return false; // Ignore grouped properties for now | 
| 871 |  | 
| 872 |     importExportedNames(prefix, name); | 
| 873 |     if (name.endsWith(s: "Connections" )) { | 
| 874 |         QString target; | 
| 875 |         auto member = uiod->initializer->members; | 
| 876 |         while (member) { | 
| 877 |             if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) { | 
| 878 |                 auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member); | 
| 879 |                 if (asBinding->qualifiedId->name == QLatin1String("target" )) { | 
| 880 |                     if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) { | 
| 881 |                         auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression; | 
| 882 |                         if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(ast: expr)) { | 
| 883 |                             target = idexpr->name.toString(); | 
| 884 |                         } else { | 
| 885 |                             // more complex expressions are not supported | 
| 886 |                         } | 
| 887 |                     } | 
| 888 |                     break; | 
| 889 |                 } | 
| 890 |             } | 
| 891 |             member = member->next; | 
| 892 |         } | 
| 893 |         const ScopeTree *targetScope; | 
| 894 |         if (target.isEmpty()) { | 
| 895 |             // no target set, connection comes from parentF | 
| 896 |             ScopeTree* scope = m_currentScope; | 
| 897 |             do { | 
| 898 |                 scope = scope->parentScope(); // TODO: rename method | 
| 899 |             } while (scope->scopeType() != ScopeType::QMLScope); | 
| 900 |             targetScope = m_exportedName2Scope.value(akey: scope->name()).get(); | 
| 901 |         } else { | 
| 902 |             // there was a target, check if we already can find it | 
| 903 |             auto scopeIt =  m_qmlid2scope.find(akey: target); | 
| 904 |             if (scopeIt != m_qmlid2scope.end()) { | 
| 905 |                 targetScope = *scopeIt; | 
| 906 |             } else { | 
| 907 |                 m_outstandingConnections.push_back(t: {.targetName: target, .scope: m_currentScope, .uiod: uiod}); | 
| 908 |                 return false; // visit children later once target is known | 
| 909 |             } | 
| 910 |         } | 
| 911 |         if (targetScope) | 
| 912 |             m_currentScope->addMethods(methods: targetScope->methods()); | 
| 913 |     } | 
| 914 |     return true; | 
| 915 | } | 
| 916 |  | 
| 917 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::PatternElement *element) | 
| 918 | { | 
| 919 |     if (element->isVariableDeclaration()) { | 
| 920 |         QQmlJS::AST::BoundNames names; | 
| 921 |         element->boundNames(names: &names); | 
| 922 |         for (const auto &name : names) | 
| 923 |             m_currentScope->insertJSIdentifier(id: name.id, scope: element->scope); | 
| 924 |     } | 
| 925 |  | 
| 926 |     return true; | 
| 927 | } | 
| 928 |  | 
| 929 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) | 
| 930 | { | 
| 931 |     auto childScope = m_currentScope; | 
| 932 |     leaveEnvironment(); | 
| 933 |     childScope->updateParentProperty(scope: m_currentScope); | 
| 934 | } | 
| 935 |  | 
| 936 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FieldMemberExpression *) | 
| 937 | { | 
| 938 |     return true; | 
| 939 | } | 
| 940 |  | 
| 941 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) | 
| 942 | { | 
| 943 |     using namespace QQmlJS::AST; | 
| 944 |     ExpressionNode *base = fieldMember->base; | 
| 945 |     while (auto *nested = cast<NestedExpression *>(ast: base)) | 
| 946 |         base = nested->expression; | 
| 947 |  | 
| 948 |     if (m_fieldMemberBase == base) { | 
| 949 |         QString type; | 
| 950 |         if (auto *binary = cast<BinaryExpression *>(ast: base)) { | 
| 951 |             if (binary->op == QSOperator::As) { | 
| 952 |                 // This is terrible. It's fixed in 6.0. | 
| 953 |                 if (auto *right = cast<Type *>(ast: static_cast<Node *>(binary->right))) | 
| 954 |                     type = right->toString(); | 
| 955 |             } | 
| 956 |         } | 
| 957 |         m_currentScope->accessMember(name: fieldMember->name.toString(), | 
| 958 |                                      parentType: type, | 
| 959 |                                      location: fieldMember->identifierToken); | 
| 960 |         m_fieldMemberBase = fieldMember; | 
| 961 |     } else { | 
| 962 |         m_fieldMemberBase = nullptr; | 
| 963 |     } | 
| 964 | } | 
| 965 |  | 
| 966 | bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::BinaryExpression *) | 
| 967 | { | 
| 968 |     return true; | 
| 969 | } | 
| 970 |  | 
| 971 | void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp) | 
| 972 | { | 
| 973 |     if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left) | 
| 974 |         m_fieldMemberBase = binExp; | 
| 975 |     else | 
| 976 |         m_fieldMemberBase = nullptr; | 
| 977 | } | 
| 978 |  |