diff options
author | Sami Shalayel <[email protected]> | 2025-03-31 16:27:06 +0200 |
---|---|---|
committer | Sami Shalayel <[email protected]> | 2025-04-24 14:45:48 +0200 |
commit | 9b2cff650a5452d38b45c6e40f8b7eea7a048d77 (patch) | |
tree | f25bd880637696198b91026becdb78592d226a5c | |
parent | 9a5e2a72a1b696dbd935c6dd656c1520dc14952a (diff) |
qmllint: Implement WarnFunctionUsedBeforeDeclaration
Warn about functions used before their declaration. Its not technically
an error like the "var used before declaration" because functions are
"hoisted up" and therefore available even before their declaration, so
create a new warning category for it instead of reusing the "var used
before declaration" category. Disable the warning by default: Qt Creator
used to have it as default, while other tools like eslint don't.
For the same reason, don't warn about functions used before declaration
during codegen, and add a method to warn about it in
CodeGenWarningInterface. The code for "var used before declaration"
can be reused by function declarations by adding a sourcelocation for
function declarations in the "addLocalVar"-call, so make sure to
differentiate between functions and vars by adding an extra member to
Context::ResolvedName.
Task-number: QTBUG-129307
Change-Id: I83a4f8cd00c120db23a0cec3365a00ed44de2836
Reviewed-by: Olivier De Cannière <[email protected]>
-rw-r--r-- | src/qml/compiler/qv4codegen.cpp | 16 | ||||
-rw-r--r-- | src/qml/compiler/qv4codegen_p.h | 3 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilercontext.cpp | 1 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilercontext_p.h | 1 | ||||
-rw-r--r-- | src/qml/compiler/qv4compilerscanfunctions.cpp | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslinter.cpp | 12 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslogger.cpp | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsloggingutils.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 12 |
9 files changed, 49 insertions, 3 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index b57a21eab6..af63c7edf7 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -51,6 +51,13 @@ void CodegenWarningInterface::reportVarUsedBeforeDeclaration( << declarationLocation.startLine << ":" << declarationLocation.startColumn << "."; } +void CodegenWarningInterface::reportFunctionUsedBeforeDeclaration(const QString &, const QString &, + QQmlJS::SourceLocation, + QQmlJS::SourceLocation) +{ + // we don't report this by default, only when using qmllint. +} + static inline void setJumpOutLocation(QV4::Moth::BytecodeGenerator *bytecodeGenerator, const Statement *body, const SourceLocation &fallback) { @@ -2680,8 +2687,13 @@ Codegen::Reference Codegen::referenceForName(const QString &name, bool isLhs, co if (resolved.declarationLocation.isValid() && accessLocation.isValid() && resolved.declarationLocation.begin() > accessLocation.end()) { Q_ASSERT(_interface); - _interface->reportVarUsedBeforeDeclaration( - name, url().toLocalFile(), resolved.declarationLocation, accessLocation); + if (resolved.memberType == Context::FunctionDefinition) { + _interface->reportFunctionUsedBeforeDeclaration( + name, url().toLocalFile(), resolved.declarationLocation, accessLocation); + } else { + _interface->reportVarUsedBeforeDeclaration( + name, url().toLocalFile(), resolved.declarationLocation, accessLocation); + } if (resolved.type == Context::ResolvedName::Stack && resolved.requiresTDZCheck) throwsReferenceError = true; } diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 55c198d52c..20fea8990d 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -53,6 +53,9 @@ public: virtual void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName, QQmlJS::SourceLocation declarationLocation, QQmlJS::SourceLocation accessLocation); + virtual void reportFunctionUsedBeforeDeclaration(const QString &name, const QString &fileName, + QQmlJS::SourceLocation declarationLocation, + QQmlJS::SourceLocation accessLocation); virtual ~CodegenWarningInterface() = default; }; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index 8c6526a9fa..84bf4877f9 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -108,6 +108,7 @@ Context::ResolvedName Context::resolveName(const QString &name, const QQmlJS::So if (m.type != Context::UndefinedMember) { result.type = m.canEscape ? ResolvedName::Local : ResolvedName::Stack; + result.memberType = m.type; result.scope = scope; result.index = m.index; result.isConst = (m.scope == VariableScope::Const); diff --git a/src/qml/compiler/qv4compilercontext_p.h b/src/qml/compiler/qv4compilercontext_p.h index c4087e771a..fc50fba997 100644 --- a/src/qml/compiler/qv4compilercontext_p.h +++ b/src/qml/compiler/qv4compilercontext_p.h @@ -337,6 +337,7 @@ struct Context { Import }; Type type = Unresolved; + Context::MemberType memberType = UndefinedMember; bool isArgOrEval = false; bool isConst = false; bool requiresTDZCheck = false; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index e520ccc002..c7e8b50e2b 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -655,7 +655,8 @@ bool ScanFunctions::enterFunction( outerContext->hasNestedFunctions = true; // The identifier of a function expression cannot be referenced from the enclosing environment. if (nameContext == FunctionNameContext::Outer) { - if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, expr)) { + if (!outerContext->addLocalVar(name, Context::FunctionDefinition, VariableScope::Var, + expr, expr->identifierToken)) { _cg->throwSyntaxError(ast->firstSourceLocation(), QStringLiteral("Identifier %1 has already been declared").arg(name)); return false; } diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp index 561190a2db..0a90376296 100644 --- a/src/qmlcompiler/qqmljslinter.cpp +++ b/src/qmlcompiler/qqmljslinter.cpp @@ -56,6 +56,18 @@ public: declarationLocation, true, true, {}, {}, accessLocation.startLine); } + void reportFunctionUsedBeforeDeclaration(const QString &name, const QString &fileName, + QQmlJS::SourceLocation declarationLocation, + QQmlJS::SourceLocation accessLocation) override + { + Q_UNUSED(fileName) + + m_logger->log("Function '%1' is used here before its declaration."_L1.arg(name), + qmlFunctionUsedBeforeDeclaration, accessLocation); + m_logger->log("Note: declaration of '%1' here"_L1.arg(name), + qmlFunctionUsedBeforeDeclaration, declarationLocation); + } + private: QQmlJSLogger *m_logger; }; diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp index ff2a77a044..03db79e658 100644 --- a/src/qmlcompiler/qqmljslogger.cpp +++ b/src/qmlcompiler/qqmljslogger.cpp @@ -92,6 +92,9 @@ using namespace Qt::StringLiterals; QtWarningMsg, true, false) \ X(qmlVarUsedBeforeDeclaration, "var-used-before-declaration", "VarUsedBeforeDeclaration", \ "Warn if a variable is used before declaration", QtWarningMsg, false, false) \ + X(qmlFunctionUsedBeforeDeclaration, "function-used-before-declaration", \ + "FunctionUsedBeforeDeclaration", "Warn if a function is used before declaration", \ + QtWarningMsg, true, false) \ X(qmlInvalidLintDirective, "invalid-lint-directive", "InvalidLintDirective", \ "Warn if an invalid qmllint comment is found", QtWarningMsg, false, false) \ X(qmlUseProperFunction, "use-proper-function", "UseProperFunction", \ diff --git a/src/qmlcompiler/qqmljsloggingutils.h b/src/qmlcompiler/qqmljsloggingutils.h index 078b7d1a45..56d4ac49b5 100644 --- a/src/qmlcompiler/qqmljsloggingutils.h +++ b/src/qmlcompiler/qqmljsloggingutils.h @@ -70,6 +70,7 @@ extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlCompiler; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlAttachedPropertyReuse; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlPlugin; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration; +extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlFunctionUsedBeforeDeclaration; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlInvalidLintDirective; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUseProperFunction; extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlAccessSingleton; diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 1bfc84f903..0bf28ef52f 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -1536,6 +1536,18 @@ void TestQmllint::dirtyJsSnippet_data() << u"console.log(a = 1)"_s << Result{ { { "Unqualified access"_L1, 1, 13 } } } << defaultOptions; + { + CallQmllintOptions options; + options.enableCategories.append("function-used-before-declaration"_L1); + QTest::newRow("functionUsedBeforeDeclaration") + << u"fff(); function fff() {}"_s + << Result{ { + { "Function 'fff' is used here before its declaration"_L1, 1, 1 }, + { "Note: declaration of 'fff' here"_L1, 1, 17 }, + }, + } + << options; + } } void TestQmllint::dirtyJsSnippet() |