aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler')
-rw-r--r--src/qmlcompiler/qqmljslinter.cpp2
-rw-r--r--src/qmlcompiler/qqmljslintervisitor.cpp65
-rw-r--r--src/qmlcompiler/qqmljslintervisitor_p.h11
-rw-r--r--src/qmlcompiler/qqmljslogger.cpp4
-rw-r--r--src/qmlcompiler/qqmljsloggingutils.h1
5 files changed, 80 insertions, 3 deletions
diff --git a/src/qmlcompiler/qqmljslinter.cpp b/src/qmlcompiler/qqmljslinter.cpp
index 595a512992..13f5a0a362 100644
--- a/src/qmlcompiler/qqmljslinter.cpp
+++ b/src/qmlcompiler/qqmljslinter.cpp
@@ -608,7 +608,7 @@ QQmlJSLinter::LintResult QQmlJSLinter::lintFile(const QString &filename,
QQmlJS::LinterVisitor v{ target, &m_importer, m_logger.get(),
QQmlJSImportVisitor::implicitImportDirectory(
m_logger->filePath(), m_importer.resourceFileMapper()),
- qmldirFiles };
+ qmldirFiles, &engine };
if (m_enablePlugins) {
for (const Plugin &plugin : m_plugins) {
diff --git a/src/qmlcompiler/qqmljslintervisitor.cpp b/src/qmlcompiler/qqmljslintervisitor.cpp
index e08567a26f..2705fc945e 100644
--- a/src/qmlcompiler/qqmljslintervisitor.cpp
+++ b/src/qmlcompiler/qqmljslintervisitor.cpp
@@ -17,6 +17,15 @@ namespace QQmlJS {
are purely syntactic checks, or style-checks warnings that don't make sense during compilation.
*/
+LinterVisitor::LinterVisitor(
+ const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger,
+ const QString &implicitImportDirectory, const QStringList &qmldirFiles,
+ QQmlJS::Engine *engine)
+ : QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles)
+ , m_engine(engine)
+{
+}
+
bool LinterVisitor::visit(StringLiteral *sl)
{
QQmlJSImportVisitor::visit(sl);
@@ -267,6 +276,62 @@ bool LinterVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
return true;
}
+void LinterVisitor::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc,
+ SourceLocation nextLoc)
+{
+ if (!statements || !nextLoc.isValid())
+ return;
+
+ quint32 afterLastStatement = 0;
+ for (StatementList *it = statements; it; it = it->next) {
+ if (!it->next) {
+ Node::Kind kind = (Node::Kind) it->statement->kind;
+ if (kind == Node::Kind_BreakStatement || kind == Node::Kind_ReturnStatement
+ || kind == Node::Kind_ThrowStatement) {
+ return;
+ }
+ afterLastStatement = it->statement->lastSourceLocation().end();
+ }
+ }
+
+ const auto &comments = m_engine->comments();
+ auto it = std::find_if(comments.cbegin(), comments.cend(),
+ [&](auto c) { return afterLastStatement < c.offset; });
+ auto end = std::find_if(it, comments.cend(),
+ [&](auto c) { return c.offset >= nextLoc.offset; });
+
+ for (; it != end; ++it) {
+ const QString &commentText = m_engine->code().mid(it->offset, it->length);
+ if (commentText.contains("fall through"_L1)
+ || commentText.contains("fall-through"_L1)
+ || commentText.contains("fallthrough"_L1)) {
+ return;
+ }
+ }
+
+ m_logger->log("Unterminated non-empty case block"_L1, qmlUnterminatedCase, errorLoc);
+}
+
+bool LinterVisitor::visit(QQmlJS::AST::CaseBlock *block)
+{
+ QQmlJSImportVisitor::visit(block);
+
+ std::vector<std::pair<SourceLocation, StatementList *>> clauses;
+ for (CaseClauses *it = block->clauses; it; it = it->next)
+ clauses.push_back({ it->clause->caseToken, it->clause->statements });
+ if (block->defaultClause)
+ clauses.push_back({ block->defaultClause->defaultToken, block->defaultClause->statements });
+ for (CaseClauses *it = block->moreClauses; it; it = it->next)
+ clauses.push_back({ it->clause->caseToken, it->clause->statements });
+
+ // check all but the last clause for fallthrough
+ for (size_t i = 0; i < clauses.size() - 1; ++i) {
+ const SourceLocation nextToken = clauses[i + 1].first;
+ checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
+ }
+ return true;
+}
+
} // namespace QQmlJS
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljslintervisitor_p.h b/src/qmlcompiler/qqmljslintervisitor_p.h
index c485428af4..48e9372887 100644
--- a/src/qmlcompiler/qqmljslintervisitor_p.h
+++ b/src/qmlcompiler/qqmljslintervisitor_p.h
@@ -16,6 +16,8 @@
#include <private/qqmljsimportvisitor_p.h>
+#include <private/qqmljsengine_p.h>
+
QT_BEGIN_NAMESPACE
namespace QQmlJS {
@@ -29,7 +31,9 @@ namespace QQmlJS {
class LinterVisitor final : public QQmlJSImportVisitor
{
public:
- using QQmlJSImportVisitor::QQmlJSImportVisitor;
+ LinterVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger,
+ const QString &implicitImportDirectory,
+ const QStringList &qmldirFiles = QStringList(), QQmlJS::Engine *engine = nullptr);
protected:
using QQmlJSImportVisitor::endVisit;
@@ -46,6 +50,7 @@ protected:
bool visit(QQmlJS::AST::BinaryExpression *) override;
bool visit(QQmlJS::AST::UiImport *import) override;
bool visit(QQmlJS::AST::UiEnumDeclaration *uied) override;
+ bool visit(QQmlJS::AST::CaseBlock *) override;
private:
struct SeenImport
@@ -75,11 +80,15 @@ private:
return qHashMulti(seed, i.filename, i.uri, i.version, i.id);
}
};
+ QQmlJS::Engine *m_engine = nullptr;
QSet<SeenImport> m_seenImports;
std::vector<QQmlJS::AST::Node *> m_ancestryIncludingCurrentNode;
void handleDuplicateEnums(QQmlJS::AST::UiEnumMemberList *members, QStringView key,
const QQmlJS::SourceLocation &location);
+ void warnCaseNoFlowControl(QQmlJS::SourceLocation caseToken) const;
+ void checkCaseFallthrough(QQmlJS::AST::StatementList *statements, SourceLocation errorLoc,
+ SourceLocation nextLoc);
};
} // namespace QQmlJS
diff --git a/src/qmlcompiler/qqmljslogger.cpp b/src/qmlcompiler/qqmljslogger.cpp
index 77d9bfc1e9..c01ae212f0 100644
--- a/src/qmlcompiler/qqmljslogger.cpp
+++ b/src/qmlcompiler/qqmljslogger.cpp
@@ -88,6 +88,9 @@ using namespace Qt::StringLiterals;
"Warn about non-list properties", QtWarningMsg, false, false) \
X(qmlNonRootEnums, "non-root-enum", "NonRootEnum", \
"Warn about enums defined outside the root component", QtWarningMsg, false, false) \
+ X(qmlUnterminatedCase, "unterminated-case", "UnterminatedCase", "Warn about non-empty case " \
+ "blocks that are not terminated by control flow or by a fallthrough comment", \
+ QtWarningMsg, false, false) \
X(qmlPlugin, "plugin", "LintPluginWarnings", "Warn if a qmllint plugin finds an issue", \
QtWarningMsg, true, false) \
X(qmlPrefixedImportType, "prefixed-import-type", "PrefixedImportType", \
@@ -139,7 +142,6 @@ using namespace Qt::StringLiterals;
"positives when checking for unqualified access", \
QtWarningMsg, false, false)
-
#define X(category, name, setting, description, level, ignored, isDefault) \
const QQmlSA::LoggerWarningId category{ name };
QMLLINT_DEFAULT_CATEGORIES
diff --git a/src/qmlcompiler/qqmljsloggingutils.h b/src/qmlcompiler/qqmljsloggingutils.h
index fe3a76c742..f87f76d414 100644
--- a/src/qmlcompiler/qqmljsloggingutils.h
+++ b/src/qmlcompiler/qqmljsloggingutils.h
@@ -88,6 +88,7 @@ extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnqualified;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnreachableCode;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnresolvedAlias;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnresolvedType;
+extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnterminatedCase;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUnusedImports;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlUseProperFunction;
extern const Q_QMLCOMPILER_EXPORT QQmlSA::LoggerWarningId qmlVarUsedBeforeDeclaration;