| 1 | // Copyright (C) 2019 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #ifndef QQMLJSIMPORTEDMEMBERSVISITOR_P_H |
| 5 | #define QQMLJSIMPORTEDMEMBERSVISITOR_P_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | |
| 17 | #include <private/qduplicatetracker_p.h> |
| 18 | #include <private/qqmljsannotation_p.h> |
| 19 | #include <private/qqmljsast_p.h> |
| 20 | #include <private/qqmljscontextualtypes_p.h> |
| 21 | #include <private/qqmljsdiagnosticmessage_p.h> |
| 22 | #include <private/qqmljsimporter_p.h> |
| 23 | #include <private/qqmljslogger_p.h> |
| 24 | #include <private/qqmljsscope_p.h> |
| 25 | #include <private/qqmljsscopesbyid_p.h> |
| 26 | #include <private/qv4compileddata_p.h> |
| 27 | |
| 28 | #include <QtQmlCompiler/qtqmlcompilerexports.h> |
| 29 | |
| 30 | #include <QtCore/qvariant.h> |
| 31 | #include <QtCore/qstack.h> |
| 32 | |
| 33 | #include <functional> |
| 34 | |
| 35 | QT_BEGIN_NAMESPACE |
| 36 | |
| 37 | namespace QQmlJS::Dom { |
| 38 | class QQmlDomAstCreatorWithQQmlJSScope; |
| 39 | } |
| 40 | |
| 41 | struct QQmlJSResourceFileMapper; |
| 42 | class Q_QMLCOMPILER_EXPORT QQmlJSImportVisitor : public QQmlJS::AST::Visitor |
| 43 | { |
| 44 | public: |
| 45 | QQmlJSImportVisitor(); |
| 46 | QQmlJSImportVisitor(const QQmlJSScope::Ptr &target, |
| 47 | QQmlJSImporter *importer, QQmlJSLogger *logger, |
| 48 | const QString &implicitImportDirectory, |
| 49 | const QStringList &qmldirFiles = QStringList()); |
| 50 | ~QQmlJSImportVisitor(); |
| 51 | |
| 52 | using QQmlJS::AST::Visitor::endVisit; |
| 53 | using QQmlJS::AST::Visitor::postVisit; |
| 54 | using QQmlJS::AST::Visitor::preVisit; |
| 55 | using QQmlJS::AST::Visitor::visit; |
| 56 | |
| 57 | QQmlJSScope::Ptr result() const { return m_exportedRootScope; } |
| 58 | |
| 59 | const QQmlJSLogger *logger() const { return m_logger; } |
| 60 | QQmlJSLogger *logger() { return m_logger; } |
| 61 | |
| 62 | QQmlJSImporter::ImportedTypes imports() const { return m_rootScopeImports; } |
| 63 | QQmlJSScopesById addressableScopes() const { return m_scopesById; } |
| 64 | QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> signalHandlers() const |
| 65 | { |
| 66 | return m_signalHandlers; |
| 67 | } |
| 68 | QSet<QQmlJSScope::ConstPtr> literalScopesToCheck() const { return m_literalScopesToCheck; } |
| 69 | QList<QQmlJSScope::ConstPtr> qmlTypes() const { return m_qmlTypes; } |
| 70 | QHash<QV4::CompiledData::Location, QQmlJSScope::ConstPtr> scopesBylocation() const |
| 71 | { |
| 72 | return m_scopesByIrLocation; |
| 73 | } |
| 74 | |
| 75 | static QString implicitImportDirectory( |
| 76 | const QString &localFile, QQmlJSResourceFileMapper *mapper); |
| 77 | |
| 78 | // ### should this be restricted? |
| 79 | QQmlJSImporter *importer() { return m_importer; } |
| 80 | const QQmlJSImporter *importer() const { return m_importer; } |
| 81 | |
| 82 | struct UnfinishedBinding |
| 83 | { |
| 84 | QQmlJSScope::Ptr owner; |
| 85 | std::function<QQmlJSMetaPropertyBinding()> create; |
| 86 | QQmlJSScope::BindingTargetSpecifier specifier = QQmlJSScope::SimplePropertyTarget; |
| 87 | }; |
| 88 | |
| 89 | QStringList seenModuleQualifiers() const { return m_seenModuleQualifiers; } |
| 90 | |
| 91 | protected: |
| 92 | // Linter warnings, we might want to move this at some point |
| 93 | bool visit(QQmlJS::AST::StringLiteral *) override; |
| 94 | |
| 95 | bool visit(QQmlJS::AST::ExpressionStatement *ast) override; |
| 96 | void endVisit(QQmlJS::AST::ExpressionStatement *ast) override; |
| 97 | |
| 98 | bool visit(QQmlJS::AST::UiProgram *) override; |
| 99 | void endVisit(QQmlJS::AST::UiProgram *) override; |
| 100 | bool visit(QQmlJS::AST::UiObjectDefinition *) override; |
| 101 | void endVisit(QQmlJS::AST::UiObjectDefinition *) override; |
| 102 | bool visit(QQmlJS::AST::UiInlineComponent *) override; |
| 103 | void endVisit(QQmlJS::AST::UiInlineComponent *) override; |
| 104 | bool visit(QQmlJS::AST::UiPublicMember *) override; |
| 105 | void endVisit(QQmlJS::AST::UiPublicMember *) override; |
| 106 | bool visit(QQmlJS::AST::UiRequired *required) override; |
| 107 | bool visit(QQmlJS::AST::UiScriptBinding *) override; |
| 108 | void endVisit(QQmlJS::AST::UiScriptBinding *) override; |
| 109 | bool visit(QQmlJS::AST::UiArrayBinding *) override; |
| 110 | void endVisit(QQmlJS::AST::UiArrayBinding *) override; |
| 111 | bool visit(QQmlJS::AST::UiEnumDeclaration *uied) override; |
| 112 | bool visit(QQmlJS::AST::FunctionExpression *fexpr) override; |
| 113 | void endVisit(QQmlJS::AST::FunctionExpression *) override; |
| 114 | bool visit(QQmlJS::AST::UiSourceElement *) override; |
| 115 | bool visit(QQmlJS::AST::FunctionDeclaration *fdecl) override; |
| 116 | void endVisit(QQmlJS::AST::FunctionDeclaration *) override; |
| 117 | bool visit(QQmlJS::AST::ClassExpression *ast) override; |
| 118 | void endVisit(QQmlJS::AST::ClassExpression *) override; |
| 119 | bool visit(QQmlJS::AST::UiImport *import) override; |
| 120 | bool visit(QQmlJS::AST::UiPragma *pragma) override; |
| 121 | bool visit(QQmlJS::AST::ClassDeclaration *ast) override; |
| 122 | void endVisit(QQmlJS::AST::ClassDeclaration *ast) override; |
| 123 | bool visit(QQmlJS::AST::ForStatement *ast) override; |
| 124 | void endVisit(QQmlJS::AST::ForStatement *ast) override; |
| 125 | bool visit(QQmlJS::AST::ForEachStatement *ast) override; |
| 126 | void endVisit(QQmlJS::AST::ForEachStatement *ast) override; |
| 127 | bool visit(QQmlJS::AST::Block *ast) override; |
| 128 | void endVisit(QQmlJS::AST::Block *ast) override; |
| 129 | bool visit(QQmlJS::AST::CaseBlock *ast) override; |
| 130 | void endVisit(QQmlJS::AST::CaseBlock *ast) override; |
| 131 | bool visit(QQmlJS::AST::Catch *ast) override; |
| 132 | void endVisit(QQmlJS::AST::Catch *ast) override; |
| 133 | bool visit(QQmlJS::AST::WithStatement *withStatement) override; |
| 134 | void endVisit(QQmlJS::AST::WithStatement *ast) override; |
| 135 | |
| 136 | bool visit(QQmlJS::AST::VariableDeclarationList *vdl) override; |
| 137 | bool visit(QQmlJS::AST::FormalParameterList *fpl) override; |
| 138 | |
| 139 | bool visit(QQmlJS::AST::UiObjectBinding *uiob) override; |
| 140 | void endVisit(QQmlJS::AST::UiObjectBinding *uiob) override; |
| 141 | |
| 142 | bool visit(QQmlJS::AST::ExportDeclaration *exp) override; |
| 143 | void endVisit(QQmlJS::AST::ExportDeclaration *exp) override; |
| 144 | |
| 145 | bool visit(QQmlJS::AST::ESModule *module) override; |
| 146 | void endVisit(QQmlJS::AST::ESModule *module) override; |
| 147 | |
| 148 | bool visit(QQmlJS::AST::Program *program) override; |
| 149 | void endVisit(QQmlJS::AST::Program *program) override; |
| 150 | |
| 151 | void endVisit(QQmlJS::AST::FieldMemberExpression *) override; |
| 152 | bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; |
| 153 | |
| 154 | bool visit(QQmlJS::AST::PatternElement *) override; |
| 155 | |
| 156 | void throwRecursionDepthError() override; |
| 157 | |
| 158 | QString m_implicitImportDirectory; |
| 159 | QStringList m_qmldirFiles; |
| 160 | QQmlJSScope::Ptr m_currentScope; |
| 161 | const QQmlJSScope::Ptr m_exportedRootScope; |
| 162 | QQmlJSImporter *m_importer = nullptr; |
| 163 | QQmlJSLogger *m_logger = nullptr; |
| 164 | |
| 165 | using RootDocumentNameType = QQmlJSScope::RootDocumentNameType; |
| 166 | using InlineComponentNameType = QQmlJSScope::InlineComponentNameType; |
| 167 | using InlineComponentOrDocumentRootName = QQmlJSScope::RootDocumentNameType; |
| 168 | QQmlJSScope::InlineComponentOrDocumentRootName m_currentRootName = |
| 169 | QQmlJSScope::RootDocumentNameType(); |
| 170 | bool m_nextIsInlineComponent = false; |
| 171 | bool m_rootIsSingleton = false; |
| 172 | QQmlJSScope::Ptr m_savedBindingOuterScope; |
| 173 | QQmlJSScope::ConstPtr m_globalScope; |
| 174 | QQmlJSScopesById m_scopesById; |
| 175 | QQmlJSImporter::ImportedTypes m_rootScopeImports; |
| 176 | QList<QQmlJSScope::ConstPtr> m_qmlTypes; |
| 177 | |
| 178 | // We need to record the locations as IR locations because those contain less data. |
| 179 | // This way we can look up objects by IR location later. |
| 180 | QHash<QV4::CompiledData::Location, QQmlJSScope::ConstPtr> m_scopesByIrLocation; |
| 181 | |
| 182 | // Maps all qmlNames to the source location of their import |
| 183 | QMultiHash<QString, QQmlJS::SourceLocation> m_importTypeLocationMap; |
| 184 | // Maps all static modules to the source location of their import |
| 185 | QMultiHash<QString, QQmlJS::SourceLocation> m_importStaticModuleLocationMap; |
| 186 | // Contains all import source locations (could be extracted from above but that is expensive) |
| 187 | QSet<QQmlJS::SourceLocation> m_importLocations; |
| 188 | // A set of all types that have been used during type resolution |
| 189 | QSet<QString> m_usedTypes; |
| 190 | |
| 191 | QList<UnfinishedBinding> m_bindings; |
| 192 | |
| 193 | // stores JS functions and Script bindings per scope (only the name). mimics |
| 194 | // the content of QmlIR::Object::functionsAndExpressions |
| 195 | QHash<QQmlJSScope::ConstPtr, QList<QString>> m_functionsAndExpressions; |
| 196 | |
| 197 | struct FunctionOrExpressionIdentifier |
| 198 | { |
| 199 | QQmlJSScope::ConstPtr scope; |
| 200 | QString name; |
| 201 | friend bool operator==(const FunctionOrExpressionIdentifier &x, |
| 202 | const FunctionOrExpressionIdentifier &y) |
| 203 | { |
| 204 | return x.scope == y.scope && x.name == y.name; |
| 205 | } |
| 206 | friend bool operator!=(const FunctionOrExpressionIdentifier &x, |
| 207 | const FunctionOrExpressionIdentifier &y) |
| 208 | { |
| 209 | return !(x == y); |
| 210 | } |
| 211 | friend size_t qHash(const FunctionOrExpressionIdentifier &x, size_t seed = 0) |
| 212 | { |
| 213 | return qHashMulti(seed, args: x.scope, args: x.name); |
| 214 | } |
| 215 | }; |
| 216 | |
| 217 | // tells whether last-processed UiScriptBinding is truly a script binding |
| 218 | bool m_thisScriptBindingIsJavaScript = false; |
| 219 | QStack<FunctionOrExpressionIdentifier> m_functionStack; |
| 220 | // stores the number of functions inside each function |
| 221 | QHash<FunctionOrExpressionIdentifier, int> m_innerFunctions; |
| 222 | QQmlJSMetaMethod::RelativeFunctionIndex |
| 223 | addFunctionOrExpression(const QQmlJSScope::ConstPtr &scope, const QString &name); |
| 224 | void forgetFunctionExpression(const QString &name); |
| 225 | int synthesizeCompilationUnitRuntimeFunctionIndices(const QQmlJSScope::Ptr &scope, |
| 226 | int count) const; |
| 227 | void populateRuntimeFunctionIndicesForDocument() const; |
| 228 | |
| 229 | void enterEnvironment(QQmlJSScope::ScopeType type, const QString &name, |
| 230 | const QQmlJS::SourceLocation &location); |
| 231 | // Finds an existing scope before attempting to create a new one. Returns \c |
| 232 | // true if the scope already exists and \c false if the new scope is created |
| 233 | bool enterEnvironmentNonUnique(QQmlJSScope::ScopeType type, const QString &name, |
| 234 | const QQmlJS::SourceLocation &location); |
| 235 | void leaveEnvironment(); |
| 236 | |
| 237 | // A set of types that have not been resolved but have been used during the |
| 238 | // AST traversal |
| 239 | QDuplicateTracker<QQmlJSScope::ConstPtr> m_unresolvedTypes; |
| 240 | template<typename ErrorHandler> |
| 241 | bool checkTypeResolved(const QQmlJSScope::ConstPtr &type, ErrorHandler handle) |
| 242 | { |
| 243 | if (type->isFullyResolved() || type->isInCustomParserParent()) |
| 244 | return true; |
| 245 | |
| 246 | // Note: ignore duplicates, but only after we are certain that the type |
| 247 | // is still unresolved |
| 248 | if (!m_unresolvedTypes.hasSeen(s: type)) |
| 249 | handle(type); |
| 250 | |
| 251 | return false; |
| 252 | } |
| 253 | |
| 254 | bool checkTypeResolved(const QQmlJSScope::ConstPtr &type) |
| 255 | { |
| 256 | return checkTypeResolved(type, handle: [&](const QQmlJSScope::ConstPtr &type) { |
| 257 | warnUnresolvedType(type); |
| 258 | }); |
| 259 | } |
| 260 | |
| 261 | void warnUnresolvedType(const QQmlJSScope::ConstPtr &type) const; |
| 262 | void warnMissingPropertyForBinding( |
| 263 | const QString &property, const QQmlJS::SourceLocation &location, |
| 264 | const std::optional<QQmlJSFixSuggestion> &fixSuggestion = {}); |
| 265 | |
| 266 | QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list); |
| 267 | void setAllBindings(); |
| 268 | void addDefaultProperties(); |
| 269 | void processDefaultProperties(); |
| 270 | void processPropertyBindings(); |
| 271 | void checkRequiredProperties(); |
| 272 | void processPropertyTypes(); |
| 273 | void processMethodTypes(); |
| 274 | void processPropertyBindingObjects(); |
| 275 | void flushPendingSignalParameters(); |
| 276 | |
| 277 | QQmlJSScope::ConstPtr scopeById(const QString &id, const QQmlJSScope::ConstPtr ¤t); |
| 278 | |
| 279 | void breakInheritanceCycles(const QQmlJSScope::Ptr &scope); |
| 280 | void checkDeprecation(const QQmlJSScope::ConstPtr &scope); |
| 281 | void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope); |
| 282 | bool rootScopeIsValid() const { return m_exportedRootScope->sourceLocation().isValid(); } |
| 283 | |
| 284 | enum class BindingExpressionParseResult { Invalid, Script, Literal, Translation }; |
| 285 | BindingExpressionParseResult parseBindingExpression(const QString &name, |
| 286 | const QQmlJS::AST::Statement *statement); |
| 287 | bool isImportPrefix(QString prefix) const; |
| 288 | |
| 289 | // Used to temporarily store annotations for functions and generators wrapped in UiSourceElements |
| 290 | QVector<QQmlJSAnnotation> m_pendingMethodAnnotations; |
| 291 | |
| 292 | struct PendingPropertyType |
| 293 | { |
| 294 | QQmlJSScope::Ptr scope; |
| 295 | QString name; |
| 296 | QQmlJS::SourceLocation location; |
| 297 | }; |
| 298 | |
| 299 | struct PendingMethodTypeAnnotations |
| 300 | { |
| 301 | QQmlJSScope::Ptr scope; |
| 302 | QString methodName; |
| 303 | // This keeps type annotations' locations in order (parameters then return type). |
| 304 | // If an annotation is not present, it is represented by an invalid source location. |
| 305 | QVarLengthArray<QQmlJS::SourceLocation, 3> locations; |
| 306 | }; |
| 307 | |
| 308 | struct PendingPropertyObjectBinding |
| 309 | { |
| 310 | QQmlJSScope::Ptr scope; |
| 311 | QQmlJSScope::Ptr childScope; |
| 312 | QString name; |
| 313 | QQmlJS::SourceLocation location; |
| 314 | bool onToken; |
| 315 | }; |
| 316 | |
| 317 | struct RequiredProperty |
| 318 | { |
| 319 | QQmlJSScope::Ptr scope; |
| 320 | QString name; |
| 321 | QQmlJS::SourceLocation location; |
| 322 | }; |
| 323 | |
| 324 | /*! |
| 325 | Utility wrapper that adds visibility scope to the data. |
| 326 | |
| 327 | This wrapper becomes useful for binding processing where we need to know |
| 328 | both the property (or signal handler) owner and the scope in which the |
| 329 | binding is executed (the "visibility" scope). |
| 330 | |
| 331 | As visibility scope (and data) does not typically have sufficient |
| 332 | information about a proper source location of that data, the location |
| 333 | also has to be provided to simplify the error reporting. |
| 334 | */ |
| 335 | template<typename T> |
| 336 | struct WithVisibilityScope |
| 337 | { |
| 338 | QQmlJSScope::Ptr visibilityScope; |
| 339 | QQmlJS::SourceLocation dataLocation; |
| 340 | T data; |
| 341 | }; |
| 342 | |
| 343 | QHash<QQmlJSScope::Ptr, QVector<QQmlJSScope::Ptr>> m_pendingDefaultProperties; |
| 344 | QVector<PendingPropertyType> m_pendingPropertyTypes; |
| 345 | QVector<PendingMethodTypeAnnotations> m_pendingMethodTypeAnnotations; |
| 346 | QVector<PendingPropertyObjectBinding> m_pendingPropertyObjectBindings; |
| 347 | QVector<RequiredProperty> m_requiredProperties; |
| 348 | QVector<QQmlJSScope::Ptr> m_objectBindingScopes; |
| 349 | QVector<QQmlJSScope::Ptr> m_objectDefinitionScopes; |
| 350 | |
| 351 | QHash<QQmlJSScope::Ptr, QVector<WithVisibilityScope<QString>>> m_propertyBindings; |
| 352 | |
| 353 | QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> m_signalHandlers; |
| 354 | QSet<QQmlJSScope::ConstPtr> m_literalScopesToCheck; |
| 355 | QQmlJS::SourceLocation m_pendingSignalHandler; |
| 356 | QStringList m_seenModuleQualifiers; |
| 357 | |
| 358 | private: |
| 359 | void checkSignal( |
| 360 | const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location, |
| 361 | const QString &handlerName, const QStringList &handlerParameters); |
| 362 | void importBaseModules(); |
| 363 | void resolveAliases(); |
| 364 | void resolveGroupProperties(); |
| 365 | void handleIdDeclaration(QQmlJS::AST::UiScriptBinding *scriptBinding); |
| 366 | |
| 367 | void visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr); |
| 368 | void processImportWarnings( |
| 369 | const QString &what, const QList<QQmlJS::DiagnosticMessage> &warnings, |
| 370 | const QQmlJS::SourceLocation &srcLocation = QQmlJS::SourceLocation()); |
| 371 | void addImportWithLocation( |
| 372 | const QString &name, const QQmlJS::SourceLocation &loc, bool hadWarnings); |
| 373 | void populateCurrentScope(QQmlJSScope::ScopeType type, const QString &name, |
| 374 | const QQmlJS::SourceLocation &location); |
| 375 | void enterRootScope(QQmlJSScope::ScopeType type, const QString &name, |
| 376 | const QQmlJS::SourceLocation &location); |
| 377 | |
| 378 | QList<QQmlJS::DiagnosticMessage> importFromHost( |
| 379 | const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location); |
| 380 | QList<QQmlJS::DiagnosticMessage> importFromQrc( |
| 381 | const QString &path, const QString &prefix, const QQmlJS::SourceLocation &location); |
| 382 | |
| 383 | public: |
| 384 | friend class QQmlJS::Dom::QQmlDomAstCreatorWithQQmlJSScope; |
| 385 | }; |
| 386 | |
| 387 | QT_END_NAMESPACE |
| 388 | |
| 389 | #endif // QQMLJSIMPORTEDMEMBERSVISITOR_P_H |
| 390 | |