| 1 | // Copyright (C) 2020 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "qqmljsimporter_p.h" |
| 5 | #include "qqmljstypedescriptionreader_p.h" |
| 6 | #include "qqmljstypereader_p.h" |
| 7 | #include "qqmljsimportvisitor_p.h" |
| 8 | #include "qqmljslogger_p.h" |
| 9 | #include "qqmljsutils_p.h" |
| 10 | |
| 11 | #include <QtQml/private/qqmlimportresolver_p.h> |
| 12 | |
| 13 | #include <QtCore/qfileinfo.h> |
| 14 | #include <QtCore/qdiriterator.h> |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | using namespace Qt::StringLiterals; |
| 19 | |
| 20 | static const QLatin1String SlashQmldir = QLatin1String("/qmldir" ); |
| 21 | static const QLatin1String PluginsDotQmltypes = QLatin1String("plugins.qmltypes" ); |
| 22 | static const QLatin1String JsrootDotQmltypes = QLatin1String("jsroot.qmltypes" ); |
| 23 | |
| 24 | |
| 25 | QQmlJS::Import::Import(QString prefix, QString name, QTypeRevision version, bool isFile, |
| 26 | bool isDependency) : |
| 27 | m_prefix(std::move(prefix)), |
| 28 | m_name(std::move(name)), |
| 29 | m_version(version), |
| 30 | m_isFile(isFile), |
| 31 | m_isDependency(isDependency) |
| 32 | { |
| 33 | } |
| 34 | |
| 35 | bool QQmlJS::Import::isValid() const |
| 36 | { |
| 37 | return !m_name.isEmpty(); |
| 38 | } |
| 39 | |
| 40 | static const QString prefixedName(const QString &prefix, const QString &name) |
| 41 | { |
| 42 | Q_ASSERT(!prefix.endsWith(u'.')); |
| 43 | return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name); |
| 44 | } |
| 45 | |
| 46 | QQmlDirParser QQmlJSImporter::createQmldirParserForFile(const QString &filename, Import *import) |
| 47 | { |
| 48 | Q_ASSERT(import); |
| 49 | QFile f(filename); |
| 50 | QQmlDirParser parser; |
| 51 | if (f.open(flags: QFile::ReadOnly)) { |
| 52 | parser.parse(source: QString::fromUtf8(ba: f.readAll())); |
| 53 | import->warnings.append(other: parser.errors(uri: filename)); |
| 54 | } else { |
| 55 | import->warnings.append(t: { |
| 56 | QStringLiteral("Could not open qmldir file: " ) + filename, |
| 57 | .type: QtWarningMsg, |
| 58 | .loc: QQmlJS::SourceLocation() |
| 59 | }); |
| 60 | } |
| 61 | |
| 62 | return parser; |
| 63 | } |
| 64 | |
| 65 | void QQmlJSImporter::readQmltypes(const QString &filename, Import *result) |
| 66 | { |
| 67 | const QFileInfo fileInfo(filename); |
| 68 | if (!fileInfo.exists()) { |
| 69 | result->warnings.append(t: { |
| 70 | QStringLiteral("QML types file does not exist: " ) + filename, |
| 71 | .type: QtWarningMsg, |
| 72 | .loc: QQmlJS::SourceLocation() |
| 73 | }); |
| 74 | return; |
| 75 | } |
| 76 | |
| 77 | if (fileInfo.isDir()) { |
| 78 | result->warnings.append(t: { |
| 79 | QStringLiteral("QML types file cannot be a directory: " ) + filename, |
| 80 | .type: QtWarningMsg, |
| 81 | .loc: QQmlJS::SourceLocation() |
| 82 | }); |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | QFile file(filename); |
| 87 | if (!file.open(flags: QFile::ReadOnly)) { |
| 88 | result->warnings.append(t: { |
| 89 | QStringLiteral("QML types file cannot be opened: " ) + filename, |
| 90 | .type: QtWarningMsg, |
| 91 | .loc: QQmlJS::SourceLocation() |
| 92 | }); |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | QQmlJSTypeDescriptionReader reader { filename, QString::fromUtf8(ba: file.readAll()) }; |
| 97 | QStringList dependencyStrings; |
| 98 | auto succ = reader(&result->objects, &dependencyStrings); |
| 99 | if (!succ) |
| 100 | result->warnings.append(t: { .message: reader.errorMessage(), .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() }); |
| 101 | |
| 102 | const QString warningMessage = reader.warningMessage(); |
| 103 | if (!warningMessage.isEmpty()) |
| 104 | result->warnings.append(t: { .message: warningMessage, .type: QtWarningMsg, .loc: QQmlJS::SourceLocation() }); |
| 105 | |
| 106 | if (dependencyStrings.isEmpty()) |
| 107 | return; |
| 108 | |
| 109 | result->warnings.append(t: { |
| 110 | QStringLiteral("Found deprecated dependency specifications in %1." |
| 111 | "Specify dependencies in qmldir and use qmltyperegistrar " |
| 112 | "to generate qmltypes files without dependencies." ) |
| 113 | .arg(a: filename), |
| 114 | .type: QtWarningMsg, |
| 115 | .loc: QQmlJS::SourceLocation() |
| 116 | }); |
| 117 | |
| 118 | for (const QString &dependency : std::as_const(t&: dependencyStrings)) { |
| 119 | const auto blank = dependency.indexOf(ch: u' '); |
| 120 | if (blank < 0) { |
| 121 | result->dependencies.append( |
| 122 | t: QQmlDirParser::Import(dependency, {}, QQmlDirParser::Import::Default)); |
| 123 | continue; |
| 124 | } |
| 125 | |
| 126 | const QString module = dependency.left(n: blank); |
| 127 | const QString versionString = dependency.mid(position: blank + 1).trimmed(); |
| 128 | if (versionString == QStringLiteral("auto" )) { |
| 129 | result->dependencies.append( |
| 130 | t: QQmlDirParser::Import(module, {}, QQmlDirParser::Import::Auto)); |
| 131 | continue; |
| 132 | } |
| 133 | |
| 134 | const auto dot = versionString.indexOf(ch: u'.'); |
| 135 | |
| 136 | const QTypeRevision version = dot < 0 |
| 137 | ? QTypeRevision::fromMajorVersion(majorVersion: versionString.toUShort()) |
| 138 | : QTypeRevision::fromVersion(majorVersion: versionString.left(n: dot).toUShort(), |
| 139 | minorVersion: versionString.mid(position: dot + 1).toUShort()); |
| 140 | |
| 141 | result->dependencies.append( |
| 142 | t: QQmlDirParser::Import(module, version, QQmlDirParser::Import::Default)); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | static QString internalName(const QQmlJSScope::ConstPtr &scope) |
| 147 | { |
| 148 | if (const auto *factory = scope.factory()) |
| 149 | return factory->internalName(); |
| 150 | return scope->internalName(); |
| 151 | } |
| 152 | |
| 153 | static bool isComposite(const QQmlJSScope::ConstPtr &scope) |
| 154 | { |
| 155 | // The only thing the factory can do is load a composite type. |
| 156 | return scope.factory() || scope->isComposite(); |
| 157 | } |
| 158 | |
| 159 | static QStringList aliases(const QQmlJSScope::ConstPtr &scope) |
| 160 | { |
| 161 | return isComposite(scope) |
| 162 | ? QStringList() |
| 163 | : scope->aliases(); |
| 164 | } |
| 165 | |
| 166 | QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFileMapper *mapper, |
| 167 | QQmlJSImporterFlags flags) |
| 168 | : m_importPaths(importPaths), |
| 169 | m_mapper(mapper), |
| 170 | m_flags(flags), |
| 171 | m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, |
| 172 | const ImportVisitorPrerequisites &p) { |
| 173 | auto visitor = std::unique_ptr<QQmlJS::AST::BaseVisitor>(new QQmlJSImportVisitor( |
| 174 | p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles)); |
| 175 | QQmlJS::AST::Node::accept(node: rootNode, visitor: visitor.get()); |
| 176 | }) |
| 177 | { |
| 178 | } |
| 179 | |
| 180 | static QString resolvePreferredPath( |
| 181 | const QString &qmldirPath, const QString &prefer, QQmlJSResourceFileMapper *mapper) |
| 182 | { |
| 183 | if (prefer.isEmpty()) |
| 184 | return qmldirPath; |
| 185 | |
| 186 | if (!prefer.endsWith(c: u'/')) { |
| 187 | qWarning() << "Ignoring invalid prefer path" << prefer << "(has to end with slash)" ; |
| 188 | return qmldirPath; |
| 189 | } |
| 190 | |
| 191 | if (prefer.startsWith(c: u':')) { |
| 192 | // Resource path: Resolve via resource file mapper if possible. |
| 193 | if (!mapper) |
| 194 | return qmldirPath; |
| 195 | |
| 196 | Q_ASSERT(prefer.endsWith(u'/')); |
| 197 | const auto entry = mapper->entry( |
| 198 | filter: QQmlJSResourceFileMapper::resourceFileFilter(file: prefer.mid(position: 1) + SlashQmldir.mid(pos: 1))); |
| 199 | |
| 200 | // This can be empty if the .qrc files does not belong to this module. |
| 201 | // In that case we trust the given qmldir file. |
| 202 | return entry.filePath.endsWith(s: SlashQmldir) |
| 203 | ? entry.filePath |
| 204 | : qmldirPath; |
| 205 | } |
| 206 | |
| 207 | // Host file system path. This should be rare. We don't generate it. |
| 208 | const QFileInfo f(prefer + SlashQmldir); |
| 209 | const QString canonical = f.canonicalFilePath(); |
| 210 | if (canonical.isEmpty()) { |
| 211 | qWarning() << "No qmldir at" << prefer; |
| 212 | return qmldirPath; |
| 213 | } |
| 214 | return canonical; |
| 215 | } |
| 216 | |
| 217 | QQmlJSImporter::Import QQmlJSImporter::readQmldir(const QString &modulePath) |
| 218 | { |
| 219 | Import result; |
| 220 | const QString moduleQmldirPath = modulePath + SlashQmldir; |
| 221 | auto reader = createQmldirParserForFile(filename: moduleQmldirPath, import: &result); |
| 222 | |
| 223 | const QString resolvedQmldirPath |
| 224 | = resolvePreferredPath(qmldirPath: moduleQmldirPath, prefer: reader.preferredPath(), mapper: m_mapper); |
| 225 | if (resolvedQmldirPath != moduleQmldirPath) |
| 226 | reader = createQmldirParserForFile(filename: resolvedQmldirPath, import: &result); |
| 227 | |
| 228 | // Leave the trailing slash |
| 229 | Q_ASSERT(resolvedQmldirPath.endsWith(SlashQmldir)); |
| 230 | QStringView resolvedPath = QStringView(resolvedQmldirPath).chopped(n: SlashQmldir.size() - 1); |
| 231 | |
| 232 | result.name = reader.typeNamespace(); |
| 233 | |
| 234 | result.isStaticModule = reader.isStaticModule(); |
| 235 | result.isSystemModule = reader.isSystemModule(); |
| 236 | result.imports.append(other: reader.imports()); |
| 237 | result.dependencies.append(other: reader.dependencies()); |
| 238 | |
| 239 | if (result.isSystemModule) { |
| 240 | // Import jsroot first, so that the builtins override it. |
| 241 | const QString jsrootPath = resolvedPath + JsrootDotQmltypes; |
| 242 | if (QFile::exists(fileName: jsrootPath)) { |
| 243 | readQmltypes(filename: jsrootPath, result: &result); |
| 244 | } else { |
| 245 | result.warnings.append(t: { |
| 246 | QStringLiteral("System module at %1 does not contain jsroot.qmltypes" ) |
| 247 | .arg(a: resolvedPath), |
| 248 | .type: QtWarningMsg, |
| 249 | .loc: QQmlJS::SourceLocation() |
| 250 | }); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | const auto typeInfos = reader.typeInfos(); |
| 255 | for (const auto &typeInfo : typeInfos) { |
| 256 | const QString typeInfoPath = QFileInfo(typeInfo).isRelative() |
| 257 | ? resolvedPath + typeInfo |
| 258 | : typeInfo; |
| 259 | readQmltypes(filename: typeInfoPath, result: &result); |
| 260 | } |
| 261 | |
| 262 | if (typeInfos.isEmpty() && !reader.plugins().isEmpty()) { |
| 263 | const QString defaultTypeInfoPath = resolvedPath + PluginsDotQmltypes; |
| 264 | if (QFile::exists(fileName: defaultTypeInfoPath)) { |
| 265 | result.warnings.append(t: { |
| 266 | QStringLiteral("typeinfo not declared in qmldir file: " ) |
| 267 | + defaultTypeInfoPath, |
| 268 | .type: QtWarningMsg, |
| 269 | .loc: QQmlJS::SourceLocation() |
| 270 | }); |
| 271 | readQmltypes(filename: defaultTypeInfoPath, result: &result); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | QHash<QString, QQmlJSExportedScope> qmlComponents; |
| 276 | const auto components = reader.components(); |
| 277 | for (auto it = components.begin(), end = components.end(); it != end; ++it) { |
| 278 | const QString filePath = resolvedPath + it->fileName; |
| 279 | if (!QFile::exists(fileName: filePath)) { |
| 280 | result.warnings.append(t: { |
| 281 | .message: it->fileName + QStringLiteral(" is listed as component in " ) |
| 282 | + resolvedQmldirPath |
| 283 | + QStringLiteral(" but does not exist.\n" ), |
| 284 | .type: QtWarningMsg, |
| 285 | .loc: QQmlJS::SourceLocation() |
| 286 | }); |
| 287 | continue; |
| 288 | } |
| 289 | |
| 290 | auto mo = qmlComponents.find(key: it->fileName); |
| 291 | if (mo == qmlComponents.end()) { |
| 292 | QQmlJSScope::Ptr imported = localFile2ScopeTree(filePath); |
| 293 | if (auto *factory = imported.factory()) { |
| 294 | if (it->singleton) { |
| 295 | factory->setIsSingleton(true); |
| 296 | } |
| 297 | } |
| 298 | mo = qmlComponents.insert(key: it->fileName, value: {.scope: imported, .exports: QList<QQmlJSScope::Export>() }); |
| 299 | } |
| 300 | |
| 301 | mo->exports.append(t: QQmlJSScope::Export( |
| 302 | reader.typeNamespace(), it.key(), it->version, QTypeRevision())); |
| 303 | } |
| 304 | for (auto it = qmlComponents.begin(), end = qmlComponents.end(); it != end; ++it) |
| 305 | result.objects.append(t: it.value()); |
| 306 | |
| 307 | const auto scripts = reader.scripts(); |
| 308 | for (const auto &script : scripts) { |
| 309 | const QString filePath = resolvedPath + script.fileName; |
| 310 | auto mo = result.scripts.find(key: script.fileName); |
| 311 | if (mo == result.scripts.end()) |
| 312 | mo = result.scripts.insert(key: script.fileName, value: { .scope: localFile2ScopeTree(filePath), .exports: {} }); |
| 313 | |
| 314 | mo->exports.append(t: QQmlJSScope::Export( |
| 315 | reader.typeNamespace(), script.nameSpace, |
| 316 | script.version, QTypeRevision())); |
| 317 | } |
| 318 | return result; |
| 319 | } |
| 320 | |
| 321 | QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory) |
| 322 | { |
| 323 | Import import; |
| 324 | if (directory.startsWith(c: u':')) { |
| 325 | if (m_mapper) { |
| 326 | const auto resources = m_mapper->filter( |
| 327 | filter: QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory: directory.mid(position: 1))); |
| 328 | for (const auto &entry : resources) { |
| 329 | const QString name = QFileInfo(entry.resourcePath).baseName(); |
| 330 | if (name.front().isUpper()) { |
| 331 | import.objects.append(t: { |
| 332 | .scope: localFile2ScopeTree(filePath: entry.filePath), |
| 333 | .exports: { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) } |
| 334 | }); |
| 335 | } |
| 336 | } |
| 337 | } else { |
| 338 | qWarning() << "Cannot read files from resource directory" << directory |
| 339 | << "because no resource file mapper was provided" ; |
| 340 | } |
| 341 | |
| 342 | return import; |
| 343 | } |
| 344 | |
| 345 | QDirIterator it { |
| 346 | directory, |
| 347 | QStringList() << QLatin1String("*.qml" ), |
| 348 | QDir::NoFilter |
| 349 | }; |
| 350 | while (it.hasNext()) { |
| 351 | QString name = it.nextFileInfo().completeBaseName(); |
| 352 | |
| 353 | // Non-uppercase names cannot be imported anyway. |
| 354 | if (!name.front().isUpper()) |
| 355 | continue; |
| 356 | |
| 357 | // .ui.qml is fine |
| 358 | if (name.endsWith(s: u".ui" )) |
| 359 | name = name.chopped(n: 3); |
| 360 | |
| 361 | // Names with dots in them cannot be imported either. |
| 362 | if (name.contains(c: u'.')) |
| 363 | continue; |
| 364 | |
| 365 | import.objects.append(t: { |
| 366 | .scope: localFile2ScopeTree(filePath: it.filePath()), |
| 367 | .exports: { QQmlJSScope::Export(QString(), name, QTypeRevision(), QTypeRevision()) } |
| 368 | }); |
| 369 | } |
| 370 | return import; |
| 371 | } |
| 372 | |
| 373 | void QQmlJSImporter::importDependencies( |
| 374 | const QQmlJSImporter::Import &import, QQmlJSImporter::AvailableTypes *types, |
| 375 | const QString &prefix, QTypeRevision version, bool isDependency) |
| 376 | { |
| 377 | // Import the dependencies with an invalid prefix. The prefix will never be matched by actual |
| 378 | // QML code but the C++ types will be visible. |
| 379 | for (auto const &dependency : std::as_const(t: import.dependencies)) |
| 380 | importHelper(module: dependency.module, types, prefix: QString(), version: dependency.version, isDependency: true); |
| 381 | |
| 382 | bool hasOptionalImports = false; |
| 383 | for (auto const &import : std::as_const(t: import.imports)) { |
| 384 | if (import.flags & QQmlDirParser::Import::Optional) { |
| 385 | hasOptionalImports = true; |
| 386 | if (!useOptionalImports()) { |
| 387 | continue; |
| 388 | } |
| 389 | |
| 390 | if (!(import.flags & QQmlDirParser::Import::OptionalDefault)) |
| 391 | continue; |
| 392 | } |
| 393 | |
| 394 | importHelper(module: import.module, types, prefix: isDependency ? QString() : prefix, |
| 395 | version: (import.flags & QQmlDirParser::Import::Auto) ? version : import.version, |
| 396 | isDependency); |
| 397 | } |
| 398 | |
| 399 | if (hasOptionalImports && !useOptionalImports()) { |
| 400 | types->warnings.append(t: { |
| 401 | .message: u"%1 uses optional imports which are not supported. Some types might not be found."_s |
| 402 | .arg(a: import.name), |
| 403 | .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() |
| 404 | }); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry, |
| 409 | const QQmlJS::Import &importDescription) |
| 410 | { |
| 411 | const QTypeRevision importVersion = importDescription.version(); |
| 412 | const QTypeRevision exportVersion = exportEntry.version(); |
| 413 | if (!importVersion.hasMajorVersion()) |
| 414 | return true; |
| 415 | if (importVersion.majorVersion() != exportVersion.majorVersion()) |
| 416 | return false; |
| 417 | return !importVersion.hasMinorVersion() |
| 418 | || exportVersion.minorVersion() <= importVersion.minorVersion(); |
| 419 | } |
| 420 | |
| 421 | /* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about commonconstructs. |
| 422 | We might want to improve it in the future if it causes issues |
| 423 | */ |
| 424 | static bool fileSelectedScopesAreCompatibleHeuristic(const QQmlJSScope::ConstPtr &scope1, const QQmlJSScope::ConstPtr &scope2) { |
| 425 | for (const auto &[propertyName, prop]: scope1->properties().asKeyValueRange()) |
| 426 | if (!scope2->hasProperty(name: propertyName)) |
| 427 | return false; |
| 428 | for (const auto &[methodName, method]: scope1->methods().asKeyValueRange()) |
| 429 | if (!scope2->hasMethod(name: methodName)) |
| 430 | return false; |
| 431 | return true; |
| 432 | } |
| 433 | |
| 434 | void QQmlJSImporter::processImport( |
| 435 | const QQmlJS::Import &importDescription, const QQmlJSImporter::Import &import, |
| 436 | QQmlJSImporter::AvailableTypes *types) |
| 437 | { |
| 438 | // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++ |
| 439 | // names with $internal$. This is to avoid clashes between them. |
| 440 | // In the list of C++ types we insert types that don't have a C++ name as their |
| 441 | // QML name prefixed with $anonymous$. |
| 442 | const QString anonPrefix = QStringLiteral("$anonymous$" ); |
| 443 | const QString internalPrefix = QStringLiteral("$internal$" ); |
| 444 | const QString modulePrefix = QStringLiteral("$module$" ); |
| 445 | QHash<QString, QList<QQmlJSScope::Export>> seenExports; |
| 446 | |
| 447 | const auto insertAliases = [&](const QQmlJSScope::ConstPtr &scope, QTypeRevision revision) { |
| 448 | const QStringList cppAliases = aliases(scope); |
| 449 | for (const QString &alias : cppAliases) |
| 450 | types->cppNames.setType(name: alias, type: { .scope: scope, .revision: revision }); |
| 451 | }; |
| 452 | |
| 453 | const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) { |
| 454 | QQmlJSScope::Export bestExport; |
| 455 | |
| 456 | // Resolve conflicting qmlNames within an import |
| 457 | for (const auto &valExport : val.exports) { |
| 458 | const QString qmlName = prefixedName(prefix: importDescription.prefix(), name: valExport.type()); |
| 459 | if (!isVersionAllowed(exportEntry: valExport, importDescription)) |
| 460 | continue; |
| 461 | |
| 462 | // Even if the QML name is overridden by some other type, we still want |
| 463 | // to insert the C++ type, with the highest revision available. |
| 464 | if (!bestExport.isValid() || valExport.version() > bestExport.version()) |
| 465 | bestExport = valExport; |
| 466 | |
| 467 | const auto it = types->qmlNames.types().find(key: qmlName); |
| 468 | if (it != types->qmlNames.types().end()) { |
| 469 | |
| 470 | // The same set of exports can declare the same name multiple times for different |
| 471 | // versions. That's the common thing and we would just continue here when we hit |
| 472 | // it again after having inserted successfully once. |
| 473 | // However, it can also declare *different* names. Then we need to do the whole |
| 474 | // thing again. |
| 475 | if (it->scope == val.scope && it->revision == valExport.version()) |
| 476 | continue; |
| 477 | |
| 478 | const auto existingExports = seenExports.value(key: qmlName); |
| 479 | enum { LowerVersion, SameVersion, HigherVersion } seenVersion = LowerVersion; |
| 480 | for (const QQmlJSScope::Export &entry : existingExports) { |
| 481 | if (!isVersionAllowed(exportEntry: entry, importDescription)) |
| 482 | continue; |
| 483 | |
| 484 | if (valExport.version() < entry.version()) { |
| 485 | seenVersion = HigherVersion; |
| 486 | break; |
| 487 | } |
| 488 | |
| 489 | if (seenVersion == LowerVersion && valExport.version() == entry.version()) |
| 490 | seenVersion = SameVersion; |
| 491 | } |
| 492 | |
| 493 | switch (seenVersion) { |
| 494 | case LowerVersion: |
| 495 | break; |
| 496 | case SameVersion: { |
| 497 | if (m_flags & QQmlJSImporterFlag::TolerateFileSelectors) { |
| 498 | auto isFileSelected = [](const QQmlJSScope::ConstPtr &scope) -> bool |
| 499 | { |
| 500 | return scope->filePath().contains(s: u"+" ); |
| 501 | }; |
| 502 | auto warnAboutFileSelector = [&](const QString &path) { |
| 503 | types->warnings.append(t: { |
| 504 | QStringLiteral("Type %1 is ambiguous due to file selector usage, ignoring %2." ) |
| 505 | .arg(args: qmlName, args: path), |
| 506 | .type: QtInfoMsg, |
| 507 | .loc: QQmlJS::SourceLocation() |
| 508 | }); |
| 509 | }; |
| 510 | if (it->scope) { |
| 511 | if (isFileSelected(val.scope)) { |
| 512 | // new entry is file selected, skip if it looks compatible |
| 513 | if (fileSelectedScopesAreCompatibleHeuristic(scope1: it->scope, scope2: val.scope)) { |
| 514 | warnAboutFileSelector(val.scope->filePath()); |
| 515 | continue; |
| 516 | } |
| 517 | } else if (isFileSelected(it->scope)) { |
| 518 | // the first scope we saw is file selected. If they are compatible |
| 519 | // we update to the new one without file selector |
| 520 | if (fileSelectedScopesAreCompatibleHeuristic(scope1: it->scope, scope2: val.scope)) { |
| 521 | warnAboutFileSelector(it->scope->filePath()); |
| 522 | break; |
| 523 | } |
| 524 | } |
| 525 | } |
| 526 | } |
| 527 | types->warnings.append(t: { |
| 528 | QStringLiteral("Ambiguous type detected. " |
| 529 | "%1 %2.%3 is defined multiple times." ) |
| 530 | .arg(a: qmlName) |
| 531 | .arg(a: valExport.version().majorVersion()) |
| 532 | .arg(a: valExport.version().minorVersion()), |
| 533 | .type: QtCriticalMsg, |
| 534 | .loc: QQmlJS::SourceLocation() |
| 535 | }); |
| 536 | |
| 537 | // Invalidate the type. We don't know which one to use. |
| 538 | types->qmlNames.clearType(name: qmlName); |
| 539 | continue; |
| 540 | } |
| 541 | case HigherVersion: |
| 542 | continue; |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | types->qmlNames.setType(name: qmlName, type: { .scope: val.scope, .revision: valExport.version() }); |
| 547 | seenExports[qmlName].append(t: valExport); |
| 548 | } |
| 549 | |
| 550 | const QTypeRevision bestRevision = bestExport.isValid() |
| 551 | ? bestExport.revision() |
| 552 | : QTypeRevision::zero(); |
| 553 | types->cppNames.setType(name: cppName, type: { .scope: val.scope, .revision: bestRevision }); |
| 554 | |
| 555 | insertAliases(val.scope, bestRevision); |
| 556 | |
| 557 | const QTypeRevision bestVersion = bestExport.isValid() |
| 558 | ? bestExport.version() |
| 559 | : QTypeRevision::zero(); |
| 560 | types->qmlNames.setType(name: prefixedName(prefix: internalPrefix, name: cppName), type: { .scope: val.scope, .revision: bestVersion }); |
| 561 | }; |
| 562 | |
| 563 | // Empty type means "this is the prefix" |
| 564 | if (!importDescription.prefix().isEmpty()) |
| 565 | types->qmlNames.setType(name: importDescription.prefix(), type: {}); |
| 566 | |
| 567 | if (!importDescription.isDependency()) { |
| 568 | // Add a marker to show that this module has been imported |
| 569 | types->qmlNames.setType(name: prefixedName(prefix: modulePrefix, name: importDescription.name()), type: {}); |
| 570 | |
| 571 | if (import.isStaticModule) |
| 572 | types->staticModules << import.name; |
| 573 | |
| 574 | if (import.isSystemModule) |
| 575 | types->hasSystemModule = true; |
| 576 | |
| 577 | types->warnings.append(l: import.warnings); |
| 578 | } |
| 579 | |
| 580 | for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) { |
| 581 | // You cannot have a script without an export |
| 582 | Q_ASSERT(!it->exports.isEmpty()); |
| 583 | insertExports(*it, prefixedName(prefix: anonPrefix, name: internalName(scope: it->scope))); |
| 584 | } |
| 585 | |
| 586 | // add objects |
| 587 | for (const auto &val : import.objects) { |
| 588 | const QString cppName = isComposite(scope: val.scope) |
| 589 | ? prefixedName(prefix: anonPrefix, name: internalName(scope: val.scope)) |
| 590 | : internalName(scope: val.scope); |
| 591 | |
| 592 | if (val.exports.isEmpty()) { |
| 593 | // Insert an unresolvable dummy name |
| 594 | types->qmlNames.setType( |
| 595 | name: prefixedName(prefix: internalPrefix, name: cppName), type: { .scope: val.scope, .revision: QTypeRevision() }); |
| 596 | types->cppNames.setType(name: cppName, type: { .scope: val.scope, .revision: QTypeRevision() }); |
| 597 | insertAliases(val.scope, QTypeRevision()); |
| 598 | } else { |
| 599 | insertExports(val, cppName); |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | /* We need to create a temporary AvailableTypes instance here to make builtins available as |
| 604 | QQmlJSScope::resolveTypes relies on them being available. They cannot be part of the regular |
| 605 | types as they would keep overwriting existing types when loaded from cache. |
| 606 | This is only a problem with builtin types as only builtin types can be overridden by any |
| 607 | sibling import. Consider the following qmldir: |
| 608 | |
| 609 | module Things |
| 610 | import QtQml 2.0 |
| 611 | import QtQuick.LocalStorage auto |
| 612 | |
| 613 | The module "Things" sees QtQml's definition of Qt, not the builtins', even though |
| 614 | QtQuick.LocalStorage does not depend on QtQml and is imported afterwards. Conversely: |
| 615 | |
| 616 | module Stuff |
| 617 | import ModuleOverridingQObject |
| 618 | import QtQuick |
| 619 | |
| 620 | The module "Stuff" sees QtQml's definition of QObject (via QtQuick), even if |
| 621 | ModuleOverridingQObject has overridden it. |
| 622 | */ |
| 623 | |
| 624 | QQmlJSImporter::AvailableTypes tempTypes(builtinImportHelper().cppNames); |
| 625 | tempTypes.cppNames.addTypes(types: types->cppNames); |
| 626 | |
| 627 | // At present, there are corner cases that couldn't be resolved in a single |
| 628 | // pass of resolveTypes() (e.g. QQmlEasingEnums::Type). However, such cases |
| 629 | // only happen when enumerations are involved, thus the strategy is to |
| 630 | // resolve enumerations (which can potentially create new child scopes) |
| 631 | // before resolving the type fully |
| 632 | const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(name: u"Array"_s ).scope; |
| 633 | for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { |
| 634 | if (!it->scope.factory()) { |
| 635 | QQmlJSScope::resolveEnums(self: it->scope, contextualTypes: tempTypes.cppNames); |
| 636 | QQmlJSScope::resolveList(self: it->scope, arrayType); |
| 637 | } |
| 638 | } |
| 639 | |
| 640 | for (const auto &val : std::as_const(t: import.objects)) { |
| 641 | // Otherwise we have already done it in localFile2ScopeTree() |
| 642 | if (!val.scope.factory() && val.scope->baseType().isNull()) { |
| 643 | |
| 644 | // ignore the scope currently analyzed by QQmlJSImportVisitor, as its only populated |
| 645 | // after importing the implicit directory. |
| 646 | if (val.scope->baseTypeName() == "$InProcess$"_L1 ) |
| 647 | continue; |
| 648 | |
| 649 | // Composite types use QML names, and we should have resolved those already. |
| 650 | // ... except that old qmltypes files might specify composite types with C++ names. |
| 651 | // Warn about those. |
| 652 | if (val.scope->isComposite()) { |
| 653 | types->warnings.append(t: { |
| 654 | QStringLiteral("Found incomplete composite type %1. Do not use qmlplugindump." ) |
| 655 | .arg(a: val.scope->internalName()), |
| 656 | .type: QtWarningMsg, |
| 657 | .loc: QQmlJS::SourceLocation() |
| 658 | }); |
| 659 | } |
| 660 | |
| 661 | QQmlJSScope::resolveNonEnumTypes(self: val.scope, contextualTypes: tempTypes.cppNames); |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | /*! |
| 667 | * \internal |
| 668 | * Imports builtins, but only the subset hardcoded into the parser. |
| 669 | */ |
| 670 | QQmlJSImporter::ImportedTypes QQmlJSImporter::importHardCodedBuiltins() |
| 671 | { |
| 672 | const auto builtins = builtinImportHelper(); |
| 673 | |
| 674 | QQmlJS::ContextualTypes result( |
| 675 | QQmlJS::ContextualTypes::QML, {}, {}, builtins.cppNames.arrayType()); |
| 676 | for (const QString hardcoded : { |
| 677 | "void"_L1 , "int"_L1 , "bool"_L1 , "double"_L1 , "real"_L1 , "string"_L1 , "url"_L1 , |
| 678 | "date"_L1 , "regexp"_L1 , "rect"_L1 , "point"_L1 , "size"_L1 , "variant"_L1 , "var"_L1 , |
| 679 | }) { |
| 680 | |
| 681 | const auto type = builtins.qmlNames.type(name: hardcoded); |
| 682 | Q_ASSERT(type.scope); |
| 683 | result.setType(name: hardcoded, type); |
| 684 | } |
| 685 | |
| 686 | return ImportedTypes(std::move(result), {}); |
| 687 | } |
| 688 | |
| 689 | |
| 690 | QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper() |
| 691 | { |
| 692 | if (m_builtins) |
| 693 | return *m_builtins; |
| 694 | |
| 695 | AvailableTypes builtins(QQmlJS::ContextualTypes(QQmlJS::ContextualTypes::INTERNAL, {}, {}, {})); |
| 696 | |
| 697 | importHelper(module: u"QML"_s , types: &builtins, prefix: QString(), version: QTypeRevision::fromVersion(majorVersion: 1, minorVersion: 0)); |
| 698 | |
| 699 | QQmlJSScope::ConstPtr arrayType = builtins.cppNames.type(name: u"Array"_s ).scope; |
| 700 | Q_ASSERT(arrayType); |
| 701 | |
| 702 | m_builtins = AvailableTypes(QQmlJS::ContextualTypes( |
| 703 | QQmlJS::ContextualTypes::INTERNAL, builtins.cppNames.types(), builtins.cppNames.names(), |
| 704 | arrayType)); |
| 705 | m_builtins->qmlNames = QQmlJS::ContextualTypes( |
| 706 | QQmlJS::ContextualTypes::QML, builtins.qmlNames.types(), builtins.qmlNames.names(), |
| 707 | arrayType); |
| 708 | m_builtins->staticModules = std::move(builtins.staticModules); |
| 709 | m_builtins->warnings = std::move(builtins.warnings); |
| 710 | m_builtins->hasSystemModule = builtins.hasSystemModule; |
| 711 | |
| 712 | return *m_builtins; |
| 713 | } |
| 714 | |
| 715 | /*! |
| 716 | * Imports types from the specified \a qmltypesFiles. |
| 717 | */ |
| 718 | QList<QQmlJS::DiagnosticMessage> QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles) |
| 719 | { |
| 720 | QList<QQmlJS::DiagnosticMessage> warnings; |
| 721 | for (const auto &file : qmldirFiles) { |
| 722 | Import result; |
| 723 | QString qmldirName; |
| 724 | if (file.endsWith(s: SlashQmldir)) { |
| 725 | result = readQmldir(modulePath: file.chopped(n: SlashQmldir.size())); |
| 726 | setQualifiedNamesOn(result); |
| 727 | qmldirName = file; |
| 728 | } else { |
| 729 | warnings.append(t: { |
| 730 | QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes." ) |
| 731 | .arg(a: file), |
| 732 | .type: QtWarningMsg, |
| 733 | .loc: QQmlJS::SourceLocation() |
| 734 | }); |
| 735 | |
| 736 | readQmltypes(filename: file, result: &result); |
| 737 | |
| 738 | // Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere |
| 739 | // else except for cache lookups, it will blow up due to a missing file instead of |
| 740 | // producing weird results. |
| 741 | qmldirName = file + QStringLiteral("_FAKE_QMLDIR" ); |
| 742 | } |
| 743 | |
| 744 | warnings.append(l: result.warnings); |
| 745 | m_seenQmldirFiles.insert(key: qmldirName, value: result); |
| 746 | |
| 747 | for (const auto &object : std::as_const(t&: result.objects)) { |
| 748 | for (const auto &ex : object.exports) { |
| 749 | m_seenImports.insert(key: {ex.package(), ex.version()}, value: qmldirName); |
| 750 | // We also have to handle the case that no version is provided |
| 751 | m_seenImports.insert(key: {ex.package(), QTypeRevision()}, value: qmldirName); |
| 752 | } |
| 753 | } |
| 754 | } |
| 755 | |
| 756 | return warnings; |
| 757 | } |
| 758 | |
| 759 | QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module, |
| 760 | const QString &prefix, |
| 761 | QTypeRevision version, |
| 762 | QStringList *staticModuleList) |
| 763 | { |
| 764 | const AvailableTypes builtins = builtinImportHelper(); |
| 765 | AvailableTypes result(builtins.cppNames); |
| 766 | if (!importHelper(module, types: &result, prefix, version)) { |
| 767 | result.warnings.append(t: { |
| 768 | QStringLiteral("Failed to import %1. Are your import paths set up properly?" ) |
| 769 | .arg(a: module), |
| 770 | .type: QtWarningMsg, |
| 771 | .loc: QQmlJS::SourceLocation() |
| 772 | }); |
| 773 | } |
| 774 | |
| 775 | if (staticModuleList) |
| 776 | *staticModuleList << result.staticModules; |
| 777 | |
| 778 | return ImportedTypes(std::move(result.qmlNames), std::move(result.warnings)); |
| 779 | } |
| 780 | |
| 781 | QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames() |
| 782 | { |
| 783 | auto builtins = builtinImportHelper(); |
| 784 | return ImportedTypes(std::move(builtins.cppNames), std::move(builtins.warnings)); |
| 785 | } |
| 786 | |
| 787 | bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, |
| 788 | const QString &prefix, QTypeRevision version, bool isDependency, |
| 789 | bool isFile) |
| 790 | { |
| 791 | // QtQuick/Controls and QtQuick.Controls are the same module |
| 792 | const QString moduleCacheName = QString(module).replace(before: u'/', after: u'.'); |
| 793 | |
| 794 | if (isDependency) |
| 795 | Q_ASSERT(prefix.isEmpty()); |
| 796 | |
| 797 | const QQmlJS::Import cacheKey(prefix, moduleCacheName, version, isFile, isDependency); |
| 798 | |
| 799 | auto getTypesFromCache = [&]() -> bool { |
| 800 | if (!m_cachedImportTypes.contains(key: cacheKey)) |
| 801 | return false; |
| 802 | |
| 803 | const auto &cacheEntry = m_cachedImportTypes[cacheKey]; |
| 804 | |
| 805 | types->cppNames.addTypes(types: cacheEntry->cppNames); |
| 806 | types->staticModules << cacheEntry->staticModules; |
| 807 | types->hasSystemModule |= cacheEntry->hasSystemModule; |
| 808 | |
| 809 | // No need to import qml names for dependencies |
| 810 | if (!isDependency) { |
| 811 | types->warnings.append(l: cacheEntry->warnings); |
| 812 | types->qmlNames.addTypes(types: cacheEntry->qmlNames); |
| 813 | } |
| 814 | |
| 815 | return true; |
| 816 | }; |
| 817 | |
| 818 | if (getTypesFromCache()) |
| 819 | return true; |
| 820 | |
| 821 | auto cacheTypes = QSharedPointer<QQmlJSImporter::AvailableTypes>( |
| 822 | new QQmlJSImporter::AvailableTypes(QQmlJS::ContextualTypes( |
| 823 | QQmlJS::ContextualTypes::INTERNAL, {}, {}, types->cppNames.arrayType()))); |
| 824 | m_cachedImportTypes[cacheKey] = cacheTypes; |
| 825 | |
| 826 | const std::pair<QString, QTypeRevision> importId { module, version }; |
| 827 | const auto it = m_seenImports.constFind(key: importId); |
| 828 | |
| 829 | if (it != m_seenImports.constEnd()) { |
| 830 | if (it->isEmpty()) |
| 831 | return false; |
| 832 | |
| 833 | Q_ASSERT(m_seenQmldirFiles.contains(*it)); |
| 834 | const QQmlJSImporter::Import import = m_seenQmldirFiles.value(key: *it); |
| 835 | |
| 836 | importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency); |
| 837 | processImport(importDescription: cacheKey, import, types: cacheTypes.get()); |
| 838 | |
| 839 | const bool typesFromCache = getTypesFromCache(); |
| 840 | Q_ASSERT(typesFromCache); |
| 841 | return typesFromCache; |
| 842 | } |
| 843 | |
| 844 | QStringList modulePaths; |
| 845 | if (isFile) { |
| 846 | const auto import = readDirectory(directory: module); |
| 847 | m_seenQmldirFiles.insert(key: module, value: import); |
| 848 | m_seenImports.insert(key: importId, value: module); |
| 849 | importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency); |
| 850 | processImport(importDescription: cacheKey, import, types: cacheTypes.get()); |
| 851 | |
| 852 | // Try to load a qmldir below, on top of the directory import. |
| 853 | modulePaths.append(t: module); |
| 854 | } else if (module == "QML"_L1 ) { |
| 855 | // Always load builtins from the resource file system. We don't want any nasty surprises |
| 856 | // that could surface when loading them from an actual import path. We rely on the builtins |
| 857 | // to contain a number of types later on. It's better to always use our own copy. |
| 858 | modulePaths = { ":/qt-project.org/imports/QML"_L1 }; |
| 859 | } else { |
| 860 | modulePaths = qQmlResolveImportPaths(uri: module, basePaths: m_importPaths, version); |
| 861 | } |
| 862 | |
| 863 | for (auto const &modulePath : std::as_const(t&: modulePaths)) { |
| 864 | QString qmldirPath = modulePath + SlashQmldir; |
| 865 | if (modulePath.startsWith(c: u':')) { |
| 866 | if (module == "QML"_L1 ) { |
| 867 | // Do not try to map the builtins' resource path. |
| 868 | } else if (m_mapper) { |
| 869 | const QString resourcePath = modulePath.mid( |
| 870 | position: 1, n: modulePath.endsWith(c: u'/') ? modulePath.size() - 2 : -1) |
| 871 | + SlashQmldir; |
| 872 | const auto entry = m_mapper->entry( |
| 873 | filter: QQmlJSResourceFileMapper::resourceFileFilter(file: resourcePath)); |
| 874 | qmldirPath = entry.filePath; |
| 875 | } else { |
| 876 | // The builtins are loaded from the resource file system. Don't warn about them. |
| 877 | qWarning() << "Cannot read files from resource directory" << modulePath |
| 878 | << "because no resource file mapper was provided" ; |
| 879 | } |
| 880 | } |
| 881 | |
| 882 | const auto it = m_seenQmldirFiles.constFind(key: qmldirPath); |
| 883 | if (it != m_seenQmldirFiles.constEnd()) { |
| 884 | const QQmlJSImporter::Import import = *it; |
| 885 | m_seenImports.insert(key: importId, value: qmldirPath); |
| 886 | importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency); |
| 887 | processImport(importDescription: cacheKey, import, types: cacheTypes.get()); |
| 888 | |
| 889 | const bool typesFromCache = getTypesFromCache(); |
| 890 | Q_ASSERT(typesFromCache); |
| 891 | return typesFromCache; |
| 892 | } |
| 893 | |
| 894 | const QFileInfo file(qmldirPath); |
| 895 | if (file.exists()) { |
| 896 | const auto import = readQmldir(modulePath: file.canonicalPath()); |
| 897 | setQualifiedNamesOn(import); |
| 898 | m_seenQmldirFiles.insert(key: qmldirPath, value: import); |
| 899 | m_seenImports.insert(key: importId, value: qmldirPath); |
| 900 | importDependencies(import, types: cacheTypes.get(), prefix, version, isDependency); |
| 901 | |
| 902 | // Potentially merges with the result of readDirectory() above. |
| 903 | processImport(importDescription: cacheKey, import, types: cacheTypes.get()); |
| 904 | |
| 905 | const bool typesFromCache = getTypesFromCache(); |
| 906 | Q_ASSERT(typesFromCache); |
| 907 | return typesFromCache; |
| 908 | } |
| 909 | } |
| 910 | |
| 911 | if (isFile) { |
| 912 | // We've loaded the directory above |
| 913 | const bool typesFromCache = getTypesFromCache(); |
| 914 | Q_ASSERT(typesFromCache); |
| 915 | return typesFromCache; |
| 916 | } |
| 917 | |
| 918 | m_seenImports.insert(key: importId, value: QString()); |
| 919 | return false; |
| 920 | } |
| 921 | |
| 922 | QQmlJSScope::Ptr QQmlJSImporter::localFile2ScopeTree(const QString &filePath) |
| 923 | { |
| 924 | const QString sourceFolderFile = preferQmlFilesFromSourceFolder() |
| 925 | ? QQmlJSUtils::qmlSourcePathFromBuildPath(mapper: m_mapper, pathInBuildFolder: filePath) |
| 926 | : filePath; |
| 927 | |
| 928 | const auto seen = m_importedFiles.find(key: sourceFolderFile); |
| 929 | if (seen != m_importedFiles.end()) |
| 930 | return *seen; |
| 931 | |
| 932 | return *m_importedFiles.insert( |
| 933 | key: sourceFolderFile, |
| 934 | value: { QQmlJSScope::create(), |
| 935 | QSharedPointer<QDeferredFactory<QQmlJSScope>>(new QDeferredFactory<QQmlJSScope>( |
| 936 | this, sourceFolderFile)) }); |
| 937 | } |
| 938 | |
| 939 | /*! |
| 940 | \internal |
| 941 | Add scopes manually created and QQmlJSImportVisited to QQmlJSImporter. |
| 942 | This allows theses scopes to not get loaded twice during linting, for example. |
| 943 | |
| 944 | Returns false if the importer contains a scope different than \a scope for the same |
| 945 | QQmlJSScope::filePath. |
| 946 | */ |
| 947 | bool QQmlJSImporter::registerScope(const QQmlJSScope::Ptr &scope) |
| 948 | { |
| 949 | Q_ASSERT(!scope.factory()); |
| 950 | |
| 951 | QQmlJSScope::Ptr &existing = m_importedFiles[scope->filePath()]; |
| 952 | if (existing) |
| 953 | return existing == scope; |
| 954 | existing = scope; |
| 955 | return true; |
| 956 | } |
| 957 | |
| 958 | QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file) |
| 959 | { |
| 960 | return localFile2ScopeTree(filePath: file); |
| 961 | } |
| 962 | |
| 963 | QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory( |
| 964 | const QString &directory, const QString &prefix) |
| 965 | { |
| 966 | const AvailableTypes builtins = builtinImportHelper(); |
| 967 | QQmlJSImporter::AvailableTypes types(QQmlJS::ContextualTypes( |
| 968 | QQmlJS::ContextualTypes::INTERNAL, {}, {}, builtins.cppNames.arrayType())); |
| 969 | importHelper(module: directory, types: &types, prefix, version: QTypeRevision(), isDependency: false, isFile: true); |
| 970 | return ImportedTypes(std::move(types.qmlNames), std::move(types.warnings)); |
| 971 | } |
| 972 | |
| 973 | void QQmlJSImporter::setImportPaths(const QStringList &importPaths) |
| 974 | { |
| 975 | m_importPaths = importPaths; |
| 976 | |
| 977 | // We have to get rid off all cache elements directly referencing modules, since changing |
| 978 | // importPaths might change which module is found first |
| 979 | m_seenImports.clear(); |
| 980 | m_cachedImportTypes.clear(); |
| 981 | // Luckily this doesn't apply to m_seenQmldirFiles |
| 982 | } |
| 983 | |
| 984 | void QQmlJSImporter::clearCache() |
| 985 | { |
| 986 | m_seenImports.clear(); |
| 987 | m_cachedImportTypes.clear(); |
| 988 | m_seenQmldirFiles.clear(); |
| 989 | m_importedFiles.clear(); |
| 990 | m_builtins.reset(); |
| 991 | } |
| 992 | |
| 993 | QQmlJSScope::ConstPtr QQmlJSImporter::jsGlobalObject() |
| 994 | { |
| 995 | return builtinImportHelper().cppNames.type(name: u"GlobalObject"_s ).scope; |
| 996 | } |
| 997 | |
| 998 | void QQmlJSImporter::setQualifiedNamesOn(const Import &import) |
| 999 | { |
| 1000 | for (auto &object : import.objects) { |
| 1001 | if (object.exports.isEmpty()) |
| 1002 | continue; |
| 1003 | if (auto *factory = object.scope.factory()) { |
| 1004 | factory->setModuleName(import.name); |
| 1005 | } else { |
| 1006 | object.scope->setOwnModuleName(import.name); |
| 1007 | } |
| 1008 | } |
| 1009 | } |
| 1010 | |
| 1011 | void QQmlJSImporter::runImportVisitor(QQmlJS::AST::Node *rootNode, |
| 1012 | const ImportVisitorPrerequisites &p) |
| 1013 | { |
| 1014 | m_importVisitor(rootNode, this, p); |
| 1015 | } |
| 1016 | |
| 1017 | QT_END_NAMESPACE |
| 1018 | |