Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljslintervisitor.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljsutils_p.h>
8
9#include <QtQml/private/qqmlsignalnames_p.h>
10
12
13using namespace Qt::StringLiterals;
14using namespace QQmlJS::AST;
15
16namespace QQmlJS {
17/*!
18 \internal
19 \class QQmlJS::LinterVisitor
20 Extends QQmlJSImportVisitor with extra warnings that are required for linting but unrelated to
21 QQmlJSImportVisitor actual task that is constructing QQmlJSScopes. One example of such warnings
22 are purely syntactic checks, or style-checks warnings that don't make sense during compilation.
23 */
24
25LinterVisitor::LinterVisitor(
26 QQmlJSImporter *importer, QQmlJSLogger *logger,
27 const QString &implicitImportDirectory, const QStringList &qmldirFiles,
28 QQmlJS::Engine *engine)
31{
32}
33
34void LinterVisitor::leaveEnvironment()
35{
36 const auto leaveEnv = qScopeGuard([this] { QQmlJSImportVisitor::leaveEnvironment(); });
37
38 if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope)
39 return;
40
41 if (auto base = m_currentScope->baseType()) {
42 if (base->internalName() == u"QQmlComponent"_s) {
43 const auto nChildren = std::count_if(
44 m_currentScope->childScopesBegin(), m_currentScope->childScopesEnd(),
45 [](const QQmlJSScope::ConstPtr &scope) {
46 return scope->scopeType() == QQmlSA::ScopeType::QMLScope;
47 });
48 if (nChildren != 1) {
49 m_logger->log("Components must have exactly one child"_L1,
50 qmlComponentChildrenCount, m_currentScope->sourceLocation());
51 }
52 }
53 }
54}
55
56bool LinterVisitor::visit(StringLiteral *sl)
57{
58 QQmlJSImportVisitor::visit(sl);
59 const QString s = m_logger->code().mid(sl->literalToken.begin(), sl->literalToken.length);
60
61 if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u))
62 || s.contains(QChar(0x2029u))) {
63 QString templateString;
64
65 bool escaped = false;
66 const QChar stringQuote = s[0];
67 for (qsizetype i = 1; i < s.size() - 1; i++) {
68 const QChar c = s[i];
69
70 if (c == u'\\') {
71 escaped = !escaped;
72 } else if (escaped) {
73 // If we encounter an escaped quote, unescape it since we use backticks here
74 if (c == stringQuote)
75 templateString.chop(1);
76
77 escaped = false;
78 } else {
79 if (c == u'`')
80 templateString += u'\\';
81 if (c == u'$' && i + 1 < s.size() - 1 && s[i + 1] == u'{')
82 templateString += u'\\';
83 }
84
85 templateString += c;
86 }
87
88 QQmlJSDocumentEdit documentEdit{
89 m_logger->filePath(), sl->literalToken, u"`" % templateString % u"`"
90 };
91 QQmlJSFixSuggestion suggestion = { "Use a template literal instead."_L1, sl->literalToken,
92 documentEdit };
93 suggestion.setAutoApplicable();
94 m_logger->log(QStringLiteral("String contains unescaped line terminator which is "
95 "deprecated."),
96 qmlMultilineStrings, sl->literalToken, true, true, suggestion);
97 }
98 return true;
99}
100
101bool LinterVisitor::preVisit(Node *n)
102{
103 m_ancestryIncludingCurrentNode.push_back(n);
104 return true;
105}
106
107void LinterVisitor::postVisit(Node *n)
108{
109 Q_ASSERT(m_ancestryIncludingCurrentNode.back() == n);
110 m_ancestryIncludingCurrentNode.pop_back();
111}
112
113Node *LinterVisitor::astParentOfVisitedNode() const
114{
115 if (m_ancestryIncludingCurrentNode.size() < 2)
116 return nullptr;
117 return m_ancestryIncludingCurrentNode[m_ancestryIncludingCurrentNode.size() - 2];
118}
119
120bool LinterVisitor::visit(CommaExpression *expression)
121{
122 QQmlJSImportVisitor::visit(expression);
123 if (!expression->left || !expression->right)
124 return true;
125
126 // don't warn about commas in "for" statements
127 if (cast<ForStatement *>(astParentOfVisitedNode()))
128 return true;
129
130 m_logger->log("Do not use comma expressions."_L1, qmlComma, expression->commaToken);
131 return true;
132}
133
134static void warnAboutLiteralConstructors(NewMemberExpression *expression, QQmlJSLogger *logger)
135{
136 static constexpr std::array literals{ "Boolean"_L1, "Function"_L1, "JSON"_L1,
137 "Math"_L1, "Number"_L1, "String"_L1 };
138
139 const IdentifierExpression *identifier = cast<IdentifierExpression *>(expression->base);
140 if (!identifier)
141 return;
142
143 if (std::find(literals.cbegin(), literals.cend(), identifier->name) != literals.cend()) {
144 logger->log("Do not use '%1' as a constructor."_L1.arg(identifier->name),
145 qmlLiteralConstructor, identifier->identifierToken);
146 }
147 if (identifier->name == "Array"_L1 && expression->arguments && expression->arguments->next) {
148 const auto fullRange = combine(expression->newToken, expression->rparenToken);
149 const QList<QQmlJSDocumentEdit> edits = {
150 { logger->filePath(), combine(expression->newToken, expression->lparenToken), "["_L1 },
151 { logger->filePath(), expression->rparenToken, "]"_L1 },
152 };
153 QQmlJSFixSuggestion fix("Replace with array literal"_L1, fullRange, edits);
154 fix.setAutoApplicable(true);
155 logger->log("Array has confusing semantics, use an array literal ([]) instead."_L1,
156 qmlLiteralConstructor, identifier->identifierToken, true, true, fix);
157 }
158}
159
160bool LinterVisitor::visit(NewMemberExpression *expression)
161{
162 QQmlJSImportVisitor::visit(expression);
163 warnAboutLiteralConstructors(expression, m_logger);
164 return true;
165}
166
167bool LinterVisitor::visit(VoidExpression *ast)
168{
169 QQmlJSImportVisitor::visit(ast);
170 m_logger->log("Do not use void expressions."_L1, qmlVoid, ast->voidToken);
171 return true;
172}
173
174static SourceLocation confusingPluses(BinaryExpression *exp)
175{
176 Q_ASSERT(exp->op == QSOperator::Add);
177
178 SourceLocation location = exp->operatorToken;
179
180 // a++ + b
181 if (auto increment = cast<PostIncrementExpression *>(exp->left))
182 location = combine(increment->incrementToken, location);
183 // a + +b
184 if (auto unary = cast<UnaryPlusExpression *>(exp->right))
185 location = combine(location, unary->plusToken);
186 // a + ++b
187 if (auto increment = cast<PreIncrementExpression *>(exp->right))
188 location = combine(location, increment->incrementToken);
189
190 if (location == exp->operatorToken)
191 return SourceLocation{};
192
193 return location;
194}
195
196static SourceLocation confusingMinuses(BinaryExpression *exp)
197{
198 Q_ASSERT(exp->op == QSOperator::Sub);
199
200 SourceLocation location = exp->operatorToken;
201
202 // a-- - b
203 if (auto decrement = cast<PostDecrementExpression *>(exp->left))
204 location = combine(decrement->decrementToken, location);
205 // a - -b
206 if (auto unary = cast<UnaryMinusExpression *>(exp->right))
207 location = combine(location, unary->minusToken);
208 // a - --b
209 if (auto decrement = cast<PreDecrementExpression *>(exp->right))
210 location = combine(location, decrement->decrementToken);
211
212 if (location == exp->operatorToken)
213 return SourceLocation{};
214
215 return location;
216}
217
218bool LinterVisitor::visit(BinaryExpression *exp)
219{
220 QQmlJSImportVisitor::visit(exp);
221 switch (exp->op) {
222 case QSOperator::Add:
223 if (SourceLocation loc = confusingPluses(exp); loc.isValid())
224 m_logger->log("Confusing pluses."_L1, qmlConfusingPluses, loc);
225 break;
226 case QSOperator::Sub:
227 if (SourceLocation loc = confusingMinuses(exp); loc.isValid())
228 m_logger->log("Confusing minuses."_L1, qmlConfusingMinuses, loc);
229 break;
230 default:
231 break;
232 }
233
234 return true;
235}
236
237bool LinterVisitor::visit(QQmlJS::AST::UiImport *import)
238{
239 QQmlJSImportVisitor::visit(import);
240
241 const auto locAndName = [](const UiImport *i) {
242 if (!i->importUri)
243 return std::make_pair(i->fileNameToken, i->fileName.toString());
244
245 QQmlJS::SourceLocation l = i->importUri->firstSourceLocation();
246 if (i->importIdToken.isValid())
247 l = combine(l, i->importIdToken);
248 else if (i->version)
249 l = combine(l, i->version->minorToken);
250 else
251 l = combine(l, i->importUri->lastSourceLocation());
252
253 return std::make_pair(l, i->importUri->toString());
254 };
255
256 SeenImport i(import);
257 if (const auto it = m_seenImports.constFind(i); it != m_seenImports.constEnd()) {
258 const auto locAndNameImport = locAndName(import);
259 const auto locAndNameSeen = locAndName(it->uiImport);
260 m_logger->log("Duplicate import '%1'"_L1.arg(locAndNameImport.second),
261 qmlDuplicateImport, locAndNameImport.first);
262 m_logger->log("Note: previous import '%1' here"_L1.arg(locAndNameSeen.second),
263 qmlDuplicateImport, locAndNameSeen.first, true, true, {},
264 locAndName(import).first.startLine);
265 }
266
267 m_seenImports.insert(i);
268 return true;
269}
270
271void LinterVisitor::handleDuplicateEnums(UiEnumMemberList *members, QStringView key,
272 const QQmlJS::SourceLocation &location)
273{
274 m_logger->log(u"Enum key '%1' has already been declared"_s.arg(key), qmlDuplicateEnumEntries,
275 location);
276 for (const auto *member = members; member; member = member->next) {
277 if (member->member.toString() == key) {
278 m_logger->log(u"Note: previous declaration of '%1' here"_s.arg(key),
279 qmlDuplicateEnumEntries, member->memberToken);
280 return;
281 }
282 }
283}
284
285bool LinterVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied)
286{
287 QQmlJSImportVisitor::visit(uied);
288
289 if (m_currentScope->isInlineComponent()) {
290 m_logger->log(u"Enums declared inside of inline component are ignored."_s,
291 qmlInlineComponentEnums, uied->firstSourceLocation());
292 } else if (m_currentScope->componentRootStatus() == QQmlJSScope::IsComponentRoot::No
293 && !m_currentScope->isFileRootComponent()) {
294 m_logger->log(u"Enum declared outside the root element. It won't be accessible."_s,
295 qmlNonRootEnums, uied->firstSourceLocation());
296 }
297
298 QHash<QStringView, const QQmlJS::AST::UiEnumMemberList *> seen;
299 for (const auto *member = uied->members; member; member = member->next) {
300 QStringView key = member->member;
301 if (!key.front().isUpper()) {
302 m_logger->log(u"Enum keys should start with an uppercase."_s, qmlEnumKeyCase,
303 member->memberToken);
304 }
305
306 if (seen.contains(key))
307 handleDuplicateEnums(uied->members, key, member->memberToken);
308 else
309 seen[member->member] = member;
310
311 if (uied->name == key) {
312 m_logger->log("Enum entry should be named differently than the enum itself to avoid "
313 "confusion."_L1, qmlEnumEntryMatchesEnum, member->firstSourceLocation());
314 }
315 }
316
317 return true;
318}
319
320static bool allCodePathsReturnInsideCase(Node *statement)
321{
322 using namespace AST;
323 if (!statement)
324 return false;
325
326 switch (statement->kind) {
327 case Node::Kind_Block: {
328 return allCodePathsReturnInsideCase(cast<Block *>(statement)->statements);
329 }
330 case Node::Kind_BreakStatement:
331 return true;
332 case Node::Kind_CaseBlock: {
333 const CaseBlock *caseBlock = cast<CaseBlock *>(statement);
334 if (caseBlock->defaultClause)
335 return allCodePathsReturnInsideCase(caseBlock->defaultClause);
336 return allCodePathsReturnInsideCase(caseBlock->clauses);
337 }
338 case Node::Kind_CaseClause:
339 return allCodePathsReturnInsideCase(cast<CaseClause *>(statement)->statements);
340 case Node::Kind_CaseClauses: {
341 for (CaseClauses *caseClauses = cast<CaseClauses *>(statement); caseClauses;
342 caseClauses = caseClauses->next) {
343 if (!allCodePathsReturnInsideCase(caseClauses->clause))
344 return false;
345 }
346 return true;
347 }
348 case Node::Kind_ContinueStatement:
349 // allCodePathsReturn() doesn't recurse into loops, so any encountered `continue` should
350 // belong to a loop outside the switch statement.
351 return true;
352 case Node::Kind_DefaultClause:
353 return allCodePathsReturnInsideCase(cast<DefaultClause *>(statement)->statements);
354 case Node::Kind_IfStatement: {
355 const auto *ifStatement = cast<IfStatement *>(statement);
356 return allCodePathsReturnInsideCase(ifStatement->ok)
357 && allCodePathsReturnInsideCase(ifStatement->ko);
358 }
359 case Node::Kind_LabelledStatement:
360 return allCodePathsReturnInsideCase(cast<LabelledStatement *>(statement)->statement);
361 case Node::Kind_ReturnStatement:
362 return true;
363 case Node::Kind_StatementList: {
364 for (StatementList *list = cast<StatementList *>(statement); list; list = list->next) {
365 if (allCodePathsReturnInsideCase(list->statement))
366 return true;
367 }
368 return false;
369 }
370 case Node::Kind_SwitchStatement:
371 return allCodePathsReturnInsideCase(cast<SwitchStatement *>(statement)->block);
372 case Node::Kind_ThrowStatement:
373 return true;
374 case Node::Kind_TryStatement: {
375 auto *tryStatement = cast<TryStatement *>(statement);
376 if (allCodePathsReturnInsideCase(tryStatement->statement))
377 return true;
378 return allCodePathsReturnInsideCase(tryStatement->finallyExpression->statement);
379 }
380 case Node::Kind_WithStatement:
381 return allCodePathsReturnInsideCase(cast<WithStatement *>(statement)->statement);
382 default:
383 break;
384 }
385 return false;
386}
387
388void LinterVisitor::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc,
389 SourceLocation nextLoc)
390{
391 if (!statements || !nextLoc.isValid())
392 return;
393
394 if (allCodePathsReturnInsideCase(statements))
395 return;
396
397 quint32 afterLastStatement = 0;
398 for (StatementList *it = statements; it; it = it->next) {
399 if (!it->next) {
400 afterLastStatement = it->statement->lastSourceLocation().end();
401 }
402 }
403
404 const auto &comments = m_engine->comments();
405 auto it = std::find_if(comments.cbegin(), comments.cend(),
406 [&](auto c) { return afterLastStatement < c.offset; });
407 auto end = std::find_if(it, comments.cend(),
408 [&](auto c) { return c.offset >= nextLoc.offset; });
409
410 for (; it != end; ++it) {
411 const QString &commentText = m_engine->code().mid(it->offset, it->length);
412 if (commentText.contains("fall through"_L1, Qt::CaseInsensitive)
413 || commentText.contains("fall-through"_L1, Qt::CaseInsensitive)
414 || commentText.contains("fallthrough"_L1, Qt::CaseInsensitive)) {
415 return;
416 }
417 }
418
419 m_logger->log(
420 "Non-empty case block potentially falls through to the next case or default statement. "
421 "Add \"// fallthrough\" at the end of the block to silence this warning."_L1,
422 qmlUnterminatedCase, errorLoc);
423}
424
425bool LinterVisitor::visit(QQmlJS::AST::CaseBlock *block)
426{
427 QQmlJSImportVisitor::visit(block);
428
429 std::vector<std::pair<SourceLocation, StatementList *>> clauses;
430 for (CaseClauses *it = block->clauses; it; it = it->next)
431 clauses.push_back({ it->clause->caseToken, it->clause->statements });
432 if (block->defaultClause)
433 clauses.push_back({ block->defaultClause->defaultToken, block->defaultClause->statements });
434 for (CaseClauses *it = block->moreClauses; it; it = it->next)
435 clauses.push_back({ it->clause->caseToken, it->clause->statements });
436
437 // check all but the last clause for fallthrough
438 for (size_t i = 0; i < clauses.size() - 1; ++i) {
439 const SourceLocation nextToken = clauses[i + 1].first;
440 checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
441 }
442 return true;
443}
444
445static QList<const Statement *> possibleLastStatements(const StatementList *ast);
446static QList<const Statement *> possibleLastStatements(const CaseBlock *ast)
447{
448 QList<const Statement *> lasts;
449
450 for (const auto *clause = ast->clauses; clause; clause = clause->next)
451 lasts << possibleLastStatements(clause->clause->statements);
452 if (ast->defaultClause)
453 lasts << possibleLastStatements(ast->defaultClause->statements);
454 for (const auto *clause = ast->moreClauses; clause; clause = clause->next)
455 lasts << possibleLastStatements(clause->clause->statements);
456
457 return lasts;
458}
459
460static QList<const Statement *> possibleLastStatements(const Statement *ast)
461{
462 if (const auto *s = cast<const Block *>(ast))
463 return possibleLastStatements(s->statements) << s;
464 if (const auto *s = cast<const BreakStatement *>(ast))
465 return { s };
466 if (const auto *s = cast<const ContinueStatement *>(ast))
467 return { s };
468 if (const auto *s = cast<const DebuggerStatement *>(ast))
469 return { s };
470 if (const auto *s = cast<const DoWhileStatement *>(ast))
471 return possibleLastStatements(s->statement) << s;
472 if (const auto *s = cast<const EmptyStatement *>(ast))
473 return { s };
474 if (const auto *s = cast<const ExportDeclaration *>(ast))
475 return { s };
476 if (const auto *s = cast<const ExpressionStatement *>(ast))
477 return { s };
478 if (const auto *s = cast<const ForEachStatement *>(ast))
479 return possibleLastStatements(s->statement) << s;
480 if (const auto *s = cast<const ForStatement *>(ast))
481 return possibleLastStatements(s->statement) << s;
482 if (const auto *s = cast<const IfStatement *>(ast)) {
483 auto lasts = possibleLastStatements(s->ok);
484 if (s->ko)
485 lasts << possibleLastStatements(s->ko);
486 return lasts << s;
487 }
488 if (const auto *s = cast<const ImportDeclaration *>(ast))
489 return { s };
490 if (const auto *s = cast<const LabelledStatement *>(ast))
491 return possibleLastStatements(s->statement) << ast;
492 if (const auto *s = cast<const ReturnStatement *>(ast))
493 return { s };
494 if (const auto *s = cast<const SwitchStatement *>(ast))
495 return possibleLastStatements(s->block) << s;
496 if (const auto *s = cast<const ThrowStatement *>(ast))
497 return { s };
498 if (const auto *s = cast<const TryStatement *>(ast))
499 return { s };
500 if (const auto *s = cast<const VariableStatement *>(ast))
501 return { s };
502 if (const auto *s = cast<const WhileStatement *>(ast))
503 return possibleLastStatements(s->statement) << s;
504 if (const auto *s = cast<const WithStatement *>(ast))
505 return possibleLastStatements(s->statement) << s;
506
507 Q_UNREACHABLE_RETURN({});
508}
509
510static QList<const Statement *> possibleLastStatements(const StatementList *ast)
511{
512 if (!ast)
513 return {};
514 for (; ast->next; ast = ast->next) { }
515 const auto *statement = ast->statement;
516
517 // Can't store FunctionDeclaration as a statement. See StatementList.
518 if (cast<const FunctionDeclaration *>(statement))
519 return {};
520
521 return possibleLastStatements(static_cast<const Statement *>(statement));
522}
523
524static bool isUselessExpressionStatement_impl(const ExpressionNode *ast)
525{
526 switch (ast->kind) {
527 case Node::Kind_CallExpression:
528 case Node::Kind_DeleteExpression:
529 case Node::Kind_NewExpression:
530 case Node::Kind_PreDecrementExpression:
531 case Node::Kind_PreIncrementExpression:
532 case Node::Kind_PostDecrementExpression:
533 case Node::Kind_PostIncrementExpression:
534 case Node::Kind_YieldExpression:
535 case Node::Kind_FunctionExpression:
536 return false;
537 case Node::Kind_NumericLiteral:
538 case Node::Kind_StringLiteral:
539 case Node::Kind_FalseLiteral:
540 case Node::Kind_TrueLiteral:
541 case Node::Kind_NullExpression:
542 case Node::Kind_Undefined:
543 case Node::Kind_RegExpLiteral:
544 case Node::Kind_SuperLiteral:
545 case Node::Kind_ThisExpression:
546 case Node::Kind_FieldMemberExpression:
547 case Node::Kind_IdentifierExpression:
548 case Node::Kind_TypeOfExpression:
549 return true;
550 default:
551 break;
552 }
553
554 if (const auto *e = cast<const NestedExpression *>(ast))
555 return isUselessExpressionStatement_impl(e->expression);
556 if (const auto *e = cast<const NotExpression *>(ast))
557 return isUselessExpressionStatement_impl(e->expression);
558 if (const auto *e = cast<const TildeExpression *>(ast))
559 return isUselessExpressionStatement_impl(e->expression);
560 if (const auto *e = cast<const UnaryMinusExpression *>(ast))
561 return isUselessExpressionStatement_impl(e->expression);
562 if (const auto *e = cast<const UnaryPlusExpression *>(ast))
563 return isUselessExpressionStatement_impl(e->expression);
564 if (const auto *e = cast<const ConditionalExpression *>(ast))
565 return isUselessExpressionStatement_impl(e->ok) && isUselessExpressionStatement_impl(e->ko);
566
567 if (const BinaryExpression *binary = cast<const BinaryExpression *>(ast)) {
568 switch (binary->op) {
569 case QSOperator::InplaceAnd:
570 case QSOperator::Assign:
571 case QSOperator::InplaceSub:
572 case QSOperator::InplaceDiv:
573 case QSOperator::InplaceExp:
574 case QSOperator::InplaceAdd:
575 case QSOperator::InplaceLeftShift:
576 case QSOperator::InplaceMod:
577 case QSOperator::InplaceMul:
578 case QSOperator::InplaceOr:
579 case QSOperator::InplaceRightShift:
580 case QSOperator::InplaceURightShift:
581 case QSOperator::InplaceXor:
582 return false;
583 default:
584 return isUselessExpressionStatement_impl(binary->left)
585 && isUselessExpressionStatement_impl(binary->right);
586 }
587 }
588
589 return false;
590}
591
592/*!
593\internal
594
595This assumes that there is no custom coercion enabled via \c Symbol.toPrimitive or similar.
596*/
597static bool isUselessExpressionStatement(const ExpressionStatement *ast)
598{
599 return isUselessExpressionStatement_impl(ast->expression);
600}
601
602void LinterVisitor::handleUselessExpressionStatement(const ExpressionStatement *ast)
603{
604 // property binding, signal handler, or function declaration
605 const auto it = std::find_if(m_ancestryIncludingCurrentNode.crbegin(),
606 m_ancestryIncludingCurrentNode.crend(),
607 [](auto it) {
608 return it->kind == Node::Kind_UiPublicMember
609 || it->kind == Node::Kind_FunctionDeclaration
610 || it->kind == Node::Kind_UiScriptBinding;
611 });
612
613 if (it == m_ancestryIncludingCurrentNode.crend())
614 return;
615
616 // A useless ExpressionStatement in *last position* inside a property binding is not useless
617 const auto isLastExprStat = [](const ExpressionStatement *statement, const Statement *base) {
618 const auto lasts = possibleLastStatements(base);
619 return lasts.contains(statement);
620 };
621
622 if (const auto *usb = cast<UiScriptBinding *>(*it); usb && usb->qualifiedId) {
623 if (usb->qualifiedId->toString() == "id"_L1)
624 return;
625 if (usb->qualifiedId->next)
626 return; // group/attached property, give up
627 if (m_savedBindingOuterScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope)
628 return; // grouped property, give up
629
630 QQmlJSScope::Ptr object = m_currentScope;
631 while (object && object->scopeType() != QQmlSA::ScopeType::QMLScope)
632 object = object->parentScope();
633
634 if (!object)
635 return;
636
637 if (m_propertyBindings.contains(object)) {
638 for (const auto &entry : m_propertyBindings[object]) {
639 if (entry.data == usb->qualifiedId->toString()) {
640 if (isLastExprStat(ast, usb->statement))
641 return;
642 else
643 break;
644 }
645 }
646 }
647 }
648
649 const auto *upm = cast<const UiPublicMember *>(*it);
650 if (upm && upm->type == AST::UiPublicMember::Property && upm->statement) {
651 if (isLastExprStat(ast, upm->statement))
652 return;
653 }
654
655 if (isUselessExpressionStatement(ast)) {
656 m_logger->log("Expression statement has no obvious effect."_L1,
657 qmlConfusingExpressionStatement,
658 combine(ast->firstSourceLocation(), ast->lastSourceLocation()));
659 }
660}
661
662bool LinterVisitor::visit(ExpressionStatement *ast)
663{
664 QQmlJSImportVisitor::visit(ast);
665 handleUselessExpressionStatement(ast);
666 return true;
667}
668
669bool LinterVisitor::safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const QString &name, const QQmlJSScope::JavaScriptIdentifier &identifier)
670{
671 if (scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope &&
672 identifier.kind == QQmlJSScope::JavaScriptIdentifier::FunctionScoped) {
673 // var is generally not great, but we don't want to emit this warning if you
674 // are in the single, toplevel block of a binding
675 Q_ASSERT(!scope->parentScope().isNull()); // lexical scope should always have a parent
676 auto parentScopeType = scope->parentScope()->scopeType();
677 bool inTopLevelBindingBlockScope = parentScopeType == QQmlSA::ScopeType::BindingFunctionScope
678 || parentScopeType == QQmlSA::ScopeType::SignalHandlerFunctionScope;
679 if (!inTopLevelBindingBlockScope) {
680 m_logger->log(u"var declaration in block scope is hoisted to function scope\n"_s
681 u"Replace it with const or let to silence the warning\n"_s,
682 qmlBlockScopeVarDeclaration, identifier.location);
683 }
684 } else if (scope->scopeType() == QQmlSA::ScopeType::QMLScope) {
685 const QQmlJSScope *scopePtr = scope.get();
686 std::pair<const QQmlJSScope*, QString> misplaced { scopePtr, name };
687 if (misplacedJSIdentifiers.contains(misplaced))
688 return false; // we only want to warn once
689 misplacedJSIdentifiers.insert(misplaced);
690 m_logger->log(u"JavaScript declarations are not allowed in QML elements"_s, qmlSyntax,
691 identifier.location);
692 return false;
693 }
694 return QQmlJSImportVisitor::safeInsertJSIdentifier(scope, name, identifier);
695}
696
698 const QString &name, const QQmlJS::AST::Statement *statement,
699 const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition)
700{
701 if (statement && statement->kind == (int)AST::Node::Kind::Kind_Block) {
702 const auto *block = static_cast<const AST::Block *>(statement);
703 if (!block->statements && associatedPropertyDefinition) {
704 m_logger->log("Unintentional empty block, use ({}) for empty object literal"_L1,
705 qmlUnintentionalEmptyBlock,
706 combine(block->lbraceToken, block->rbraceToken));
707 }
708 }
709
710 return QQmlJSImportVisitor::parseBindingExpression(name, statement, associatedPropertyDefinition);
711}
712
713void LinterVisitor::handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding,
714 const UiPublicMember *associatedPropertyDefinition)
715{
716 if (!m_currentScope->hasOwnProperty(binding.propertyName()))
717 return;
718
719 if (!associatedPropertyDefinition->isReadonly())
720 return;
721
722 const auto &prop = m_currentScope->property(binding.propertyName());
723 const auto log = [&](const QString &preferredType) {
724 m_logger->log("Prefer more specific type %1 over var"_L1.arg(preferredType),
725 qmlPreferNonVarProperties, prop.sourceLocation());
726 };
727
728 if (prop.typeName() != "QVariant"_L1)
729 return;
730
731 switch (binding.bindingType()) {
732 case QQmlSA::BindingType::BoolLiteral: {
733 log("bool"_L1);
734 break;
735 }
736 case QQmlSA::BindingType::NumberLiteral: {
737 double v = binding.numberValue();
738 auto loc = binding.sourceLocation();
739 QStringView literal = QStringView(m_engine->code()).mid(loc.offset, loc.length);
740 if (literal.contains(u'.') || double(int(v)) != v)
741 log("real or double"_L1);
742 else
743 log("int"_L1);
744 break;
745 }
746 case QQmlSA::BindingType::StringLiteral: {
747 log("string"_L1);
748 break;
749 }
750 default: {
751 break;
752 }
753 }
754}
755
756bool LinterVisitor::visit(UiProgram *ast)
757{
758 const bool result = QQmlJSImportVisitor::visit(ast);
759
760 m_renamedComponents.setScopeToName(&m_rootScopeImports.names());
761
762 return result;
763}
764
765void LinterVisitor::checkUnusedImports()
766{
767 auto unusedImports = m_importLocations;
768 for (const QString &type : std::as_const(m_usedTypes)) {
769 const auto &importLocations = m_importTypeLocationMap.values(type);
770 for (const auto &importLocation : importLocations)
771 unusedImports.remove(importLocation);
772
773 // If there are no more unused imports left we can abort early
774 if (unusedImports.isEmpty())
775 break;
776 }
777
778 const auto &imports = m_importStaticModuleLocationMap.values();
779 for (const QQmlJS::SourceLocation &import : imports)
780 unusedImports.remove(import);
781
782 for (const auto &import : unusedImports) {
783 m_logger->log(QString::fromLatin1("Unused import"), qmlUnusedImports, import);
784 }
785}
786
787void LinterVisitor::endVisit(UiProgram *ast)
788{
789 QQmlJSImportVisitor::endVisit(ast);
790 checkFileSelections();
791 checkUnusedImports();
792 checkSignalHandlerNameClashes();
793}
794
795static constexpr QLatin1String s_method = "method"_L1;
796static constexpr QLatin1String s_signal = "signal"_L1;
797static constexpr QLatin1String s_property = "property"_L1;
798
801
802static void warnForMethodShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name,
803 const QQmlJS::SourceLocation &location,
804 QQmlJSLogger *logger)
805{
806 Q_ASSERT(base);
807 if (!base->hasMethod(name))
808 return;
809
810 static constexpr QLatin1String warningMessage =
811 "%1 \"%2\" already exists in base type \"%3\", use a different name."_L1;
812 const auto owner = QQmlJSScope::ownerOfMethod(base, name).scope;
813 const bool isSignal = owner->methods(name).front().methodType() == QQmlJSMetaMethodType::Signal;
814 logger->log(warningMessage.arg(isSignal ? "Signal"_L1 : "Method"_L1, name,
815 QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
816 qmlShadow, location);
817}
818
819static void warnForPropertyShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name,
820 const QQmlJS::SourceLocation &location,
821 OverrideInformations overrideFlags, QQmlJSLogger *logger)
822{
823 Q_ASSERT(base);
824 const bool hasOverride = overrideFlags.testFlag(WithOverride);
825 if (!base->hasProperty(name)) {
826 if (!hasOverride)
827 return;
828 logger->log(
829 "Member \"%1\" does not override anything. Consider removing \"override\"."_L1.arg(
830 name),
831 qmlPropertyOverride, location);
832 return;
833 }
834
835 const auto owner = QQmlJSScope::ownerOfProperty(base, name).scope;
836 const auto shadowedProperty = owner->ownProperty(name);
837 if (shadowedProperty.isFinal()) {
838 logger->log(
839 (!hasOverride
840 ? "Member \"%1\" shadows final member \"%1\" from base type \"%2\", use a different name."_L1
841 : "Member \"%1\" overrides final member \"%1\" from base type \"%2\", use a different name and remove the \"override\"."_L1)
842 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
843 qmlPropertyOverride, location);
844 return;
845 }
846
847 if (shadowedProperty.isVirtual() || shadowedProperty.isOverride()) {
848 if (hasOverride || overrideFlags.testFlag(WithFinal))
849 return;
850
851 logger->log(
852 "Member \"%1\" shadows member \"%1\" from base type \"%2\", use a different name or add a final or override specifier."_L1
853 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
854 qmlPropertyOverride, location);
855 return;
856 }
857
858 if (hasOverride) {
859 logger->log(
860 "Member \"%1\" overrides a non-virtual member from base type \"%2\", use a different name or mark the property as virtual in the base type."_L1
861 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
862 qmlPropertyOverride, location);
863 return;
864 }
865 logger->log("Property \"%2\" already exists in base type \"%3\", use a different name."_L1.arg(
866 name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
867 qmlPropertyOverride, location);
868}
869
870static void warnForDuplicates(const QQmlJSScope::ConstPtr &scope, const QString &name,
871 QLatin1String type, const QQmlJS::SourceLocation &location,
872 OverrideInformations overrideFlags, QQmlJSLogger *logger)
873{
874 static constexpr QLatin1String duplicateMessage =
875 "Duplicated %1 name \"%2\", \"%2\" is already a %3."_L1;
876 if (const auto methods = scope->ownMethods(name); !methods.isEmpty()) {
877 logger->log(duplicateMessage.arg(type, name,
878 methods.front().methodType() == QQmlSA::MethodType::Signal
879 ? s_signal
880 : s_method),
881 qmlDuplicatedName, location);
882 }
883 if (scope->hasOwnProperty(name))
884 logger->log(duplicateMessage.arg(type, name, s_property), qmlDuplicatedName, location);
885
886 const QQmlJSScope::ConstPtr base = scope->baseType();
887 if (!base)
888 return;
889
890 warnForMethodShadowingInBase(base, name, location, logger);
891 warnForPropertyShadowingInBase(base, name, location, overrideFlags, logger);
892}
893
894static bool hasSignal(const QQmlJSScope::ConstPtr &scope, const QString &name)
895{
896 const auto methods = scope->methods(name);
897 return std::any_of(methods.cbegin(), methods.cend(), [](const QQmlJSMetaMethod &method) {
898 return method.methodType() == QQmlSA::MethodType::Signal;
899 });
900}
901
902/*!
903\internal
904Warns about properties whose names are also signal handler names for another member of the same
905object. The QML engine gives the signal handler interpretation precedence, so such properties can
906never be bound: a literal initializer is an error and an expression initializer is run as a signal
907handler instead. This mirrors SignalHandlerResolver, which accepts signals no matter which type
908declares them, but plain properties only from the object the binding appears in.
909*/
910void LinterVisitor::checkSignalHandlerNameClashes()
911{
912 static constexpr QLatin1String clashMessage =
913 "Property \"%1\" is interpreted as a signal handler for %2 \"%3\", "
914 "use a different name."_L1;
915
916 for (const auto &[scope, name, location] : std::as_const(m_handlerLikeProperties)) {
917 QString changedPropertyName;
918 QString signalName;
919 if (const auto propertyName = QQmlSignalNames::changedHandlerNameToPropertyName(name)) {
920 changedPropertyName = *propertyName;
921 signalName = *QQmlSignalNames::changedHandlerNameToSignalName(name);
922 } else {
923 signalName = QQmlSignalNames::handlerNameToSignalName(name).value_or(QString());
924 }
925
926 if (signalName.isEmpty())
927 continue;
928
929 if (hasSignal(scope, signalName)) {
930 m_logger->log(clashMessage.arg(name, s_signal, signalName), qmlSignalHandlerNameClash,
931 location);
932 } else if (!changedPropertyName.isEmpty() && scope->hasProperty(changedPropertyName)) {
933 m_logger->log(clashMessage.arg(name, s_property, changedPropertyName),
934 qmlSignalHandlerNameClash, location);
935 } else if (scope->hasOwnProperty(signalName)) {
936 m_logger->log(clashMessage.arg(name, s_property, signalName), qmlSignalHandlerNameClash,
937 location);
938 }
939 }
940}
941
942void LinterVisitor::handleRenamedType(UiQualifiedId *qualifiedId)
943{
944 m_renamedComponents.handleRenamedType(
945 m_rootScopeImports.type(qualifiedId->name.toString()).scope, qualifiedId->name,
946 qualifiedId->identifierToken, m_logger);
947}
948
949bool LinterVisitor::visit(Type *type)
950{
951 const bool result = QQmlJSImportVisitor::visit(type);
952
953 handleRenamedType(type->typeId);
954
955 return result;
956}
957
958void LinterVisitor::handleRecursivelyInstantiatedType(UiQualifiedId *qualifiedId)
959{
960 // It should be ok to reference inline components or enums inside of the current file
961 if (qualifiedId->next)
962 return;
963
964 auto logWarning = [&qualifiedId, this]() {
965 m_logger->log("Type \"%1\" can't be instantiated recursively"_L1.arg(qualifiedId->name),
966 qmlTypeInstantiatedRecursively, qualifiedId->identifierToken);
967 };
968
969 const QString name = qualifiedId->name.toString();
970 if (m_rootScopeImports.names().contains(m_exportedRootScope, name))
971 logWarning();
972
973 // note: inline components can't be renamed via qmldir entries
974 if (const auto inlineComponentName = std::get_if<InlineComponentNameType>(&m_currentRootName);
975 inlineComponentName && name == *inlineComponentName) {
976 logWarning();
977 }
978}
979
980bool LinterVisitor::visit(QQmlJS::AST::UiPragma *pragma)
981{
982 const bool result = QQmlJSImportVisitor::visit(pragma);
983
984 // The QML Engine ignores this pragma, so __don't__ set m_exportedRootScope's singleton flag with its value.
985 if (pragma->name == u"Singleton")
986 m_rootIsSingleton = true;
987
988 return result;
989}
990
991void LinterVisitor::checkSingletonRoot()
992{
993 const bool hasQmldirSingletonEntry = m_exportedRootScope->isSingleton(); // set by importer
994 const bool hasSingletonPragma = m_rootIsSingleton;
995
996 if (hasQmldirSingletonEntry == hasSingletonPragma)
997 return;
998
999 if (hasQmldirSingletonEntry && !hasSingletonPragma) {
1000 m_logger->log("Type %1 declared as singleton in qmldir but missing pragma Singleton"_L1.arg(
1001 m_exportedRootScope->internalName()),
1002 qmlImport, QQmlJS::SourceLocation());
1003 return;
1004 }
1005 Q_ASSERT(!hasQmldirSingletonEntry && hasSingletonPragma);
1006 m_logger->log("Type %1 not declared as singleton in qmldir but using pragma Singleton"_L1.arg(
1007 m_exportedRootScope->internalName()),
1008 qmlImport, QQmlJS::SourceLocation());
1009}
1010
1011bool LinterVisitor::visit(QQmlJS::AST::UiObjectDefinition *objectDefinition)
1012{
1013 handleRenamedType(objectDefinition->qualifiedTypeNameId);
1014 handleRecursivelyInstantiatedType(objectDefinition->qualifiedTypeNameId);
1015 if (!rootScopeIsValid() && !objectDefinition->qualifiedTypeNameId->name.front().isLower())
1016 checkSingletonRoot();
1017
1018 return QQmlJSImportVisitor::visit(objectDefinition);
1019}
1020
1021bool LinterVisitor::visit(UiPublicMember *publicMember)
1022{
1023 switch (publicMember->type) {
1024 case UiPublicMember::Signal: {
1025 const QString signalName = publicMember->name.toString();
1026 warnForDuplicates(m_currentScope, signalName, s_signal, publicMember->identifierToken,
1027 WithoutOverride, m_logger);
1028 break;
1029 }
1030 case QQmlJS::AST::UiPublicMember::Property: {
1031 const QString propertyName = publicMember->name.toString();
1032 OverrideInformations flags;
1033 flags.setFlag(WithOverride, publicMember->isOverride());
1034 flags.setFlag(WithFinal, publicMember->isFinal());
1035 warnForDuplicates(m_currentScope, propertyName, s_property, publicMember->identifierToken,
1036 flags, m_logger);
1037 if (QQmlSignalNames::isHandlerName(propertyName)) {
1038 m_handlerLikeProperties.append(
1039 { m_currentScope, propertyName, publicMember->identifierToken });
1040 }
1041 handleRenamedType(publicMember->memberType);
1042
1043 const QString typeName = publicMember->memberType->toString();
1044 if (typeName != "alias"_L1) {
1045 if (m_rootScopeImports.hasType(typeName)
1046 && !m_rootScopeImports.type(typeName).scope.isNull()) {
1047 if (m_importTypeLocationMap.contains(typeName))
1048 m_usedTypes.insert(typeName);
1049 }
1050 }
1051 break;
1052 }
1053 }
1054 return QQmlJSImportVisitor::visit(publicMember);
1055}
1056
1057bool LinterVisitor::visit(FunctionExpression *fexpr)
1058{
1059 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
1060 warnForDuplicates(m_currentScope, fexpr->name.toString(), s_method, fexpr->identifierToken,
1061 WithoutOverride, m_logger);
1062 }
1063 return QQmlJSImportVisitor::visit(fexpr);
1064}
1065
1066bool LinterVisitor::visit(FunctionDeclaration *fdecl)
1067{
1068 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
1069 warnForDuplicates(m_currentScope, fdecl->name.toString(), s_method, fdecl->identifierToken,
1070 WithoutOverride, m_logger);
1071 }
1072 return QQmlJSImportVisitor::visit(fdecl);
1073}
1074
1075/* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about common constructs.
1076 We might want to improve it in the future if it causes issues
1077*/
1078static bool compatibilityHeuristicForFileSelector(const QQmlJSScope::ConstPtr &scope1,
1079 const QQmlJSScope::ConstPtr &scope2)
1080{
1081 for (const auto &[propertyName, prop] : scope1->properties().asKeyValueRange())
1082 if (!scope2->hasProperty(propertyName))
1083 return false;
1084 for (const auto &[methodName, method] : scope1->methods().asKeyValueRange())
1085 if (!scope2->hasMethod(methodName))
1086 return false;
1087 return true;
1088}
1089
1090// heuristic to check file selected files for "compability" to the unselected file.
1091void LinterVisitor::checkFileSelections()
1092{
1093 const QQmlJS::FileSelectorInfo info =
1094 m_rootScopeImports.contextualTypes().fileSelectorInfoFor(m_exportedRootScope);
1095
1096 if (info.fileSelectedTypes.isEmpty() || info.mainType.isNull())
1097 return;
1098
1099 const QString name = m_rootScopeImports.name(m_exportedRootScope);
1100
1101 if (info.mainType == m_exportedRootScope) {
1102 // current has fileselectors -> check all fileselectors for compatiblity
1103 for (const auto &fileSelected : info.fileSelectedTypes) {
1104 if (compatibilityHeuristicForFileSelector(m_exportedRootScope,
1105 fileSelected.type.scope)) {
1106 m_logger->log(
1107 "Type %1 is ambiguous due to file selector usage, ignoring %2."_L1.arg(
1108 name, fileSelected.type.scope->filePath()),
1109 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1110 continue;
1111 }
1112 m_logger->log("Type %1 has a potentially incompatible file-selected variant %2."_L1.arg(
1113 name, fileSelected.type.scope->filePath()),
1114 qmlImport, m_exportedRootScope->sourceLocation());
1115 }
1116 return;
1117 }
1118
1119 // current is fileselected -> only check against "main" type for compatibility
1120 if (compatibilityHeuristicForFileSelector(info.mainType, m_exportedRootScope)) {
1121 m_logger->log(
1122 "File-selected type %1 is ambiguous due to file selector usage, this file will be ignored in favour of %2."_L1
1123 .arg(name, info.mainType->filePath()),
1124 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1125 return;
1126 }
1127 m_logger->log("File-selected type %1 is potentially incompatible with %2."_L1.arg(
1128 name, info.mainType->filePath()),
1129 qmlImport, m_exportedRootScope->sourceLocation());
1130}
1131
1132void LinterVisitor::addImportWithLocation(const QString &name, const QQmlJS::SourceLocation &loc,
1133 bool hadWarnings)
1134{
1135 if (m_importTypeLocationMap.contains(name)
1136 && m_importTypeLocationMap.values(name).contains(loc)) {
1137 return;
1138 }
1139
1140 m_importTypeLocationMap.insert(name, loc);
1141
1142 // If the import had warnings it may be "unused" because we haven't found all of its types.
1143 // If the type's location is not valid it's a builtin.
1144 // We don't need to complain about those being unused.
1145 if (!hadWarnings && loc.isValid())
1146 m_importLocations.insert(loc);
1147}
1148
1149void LinterVisitor::addStaticImportWithLocation(const QString &name,
1150 const QQmlJS::SourceLocation &loc,
1151 bool isDependency)
1152{
1153 // Always prefer a direct import of static module to it being imported as a dependency
1154 if (isDependency && m_importStaticModuleLocationMap.contains(name))
1155 return;
1156 m_importStaticModuleLocationMap[name] = loc;
1157}
1158
1159bool LinterVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
1160{
1161 const QString name = idexp->name.toString();
1162 if (m_importTypeLocationMap.contains(name)) {
1163 m_usedTypes.insert(name);
1164 }
1165
1166 return true;
1167}
1168
1169void LinterVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
1170{
1171 // This is a rather rough approximation of "used type" but the "unused import"
1172 // info message doesn't have to be 100% accurate.
1173 const QString name = fieldMember->name.toString();
1174 if (m_importTypeLocationMap.contains(name)) {
1175 const QQmlJSImportedScope type = m_rootScopeImports.type(name);
1176 if (type.scope.isNull()) {
1177 if (m_rootScopeImports.hasType(name))
1178 m_usedTypes.insert(name);
1179 } else if (!type.scope->attachedTypeName().isEmpty()) {
1180 m_usedTypes.insert(name);
1181 }
1182 }
1183}
1184
1186{
1187 for (const auto &scope : std::as_const(m_objectDefinitionScopes))
1188 checkGroupedAndAttachedScope(scope);
1189}
1190
1191void LinterVisitor::checkGroupedAndAttachedScope(const QQmlJSScope::ConstPtr &scope)
1192{
1193 // These warnings do not apply for custom parsers and their children and need to be handled on a
1194 // case by case basis
1195 if (checkCustomParser(scope))
1196 return;
1197
1198 if (!checkTypeResolved(scope))
1199 return;
1200
1201 auto children = scope->childScopes();
1202 while (!children.isEmpty()) {
1203 auto childScope = children.takeFirst();
1204 const auto type = childScope->scopeType();
1205 switch (type) {
1206 case QQmlSA::ScopeType::GroupedPropertyScope:
1207 case QQmlSA::ScopeType::AttachedPropertyScope:
1208 if (!childScope->baseType() && !m_unresolvedTypes.hasSeen(childScope)) {
1209 // note: if we already warn about the unknown property scope then we don't have to
1210 // warn again about it being unresolved later
1211 m_logger->log(QStringLiteral("unknown %1 property scope %2.")
1212 .arg(type == QQmlSA::ScopeType::GroupedPropertyScope
1213 ? QStringLiteral("grouped")
1214 : QStringLiteral("attached"),
1215 childScope->internalName()),
1216 qmlUnqualified, childScope->sourceLocation());
1217 }
1218 children.append(childScope->childScopes());
1219 break;
1220 default:
1221 break;
1222 }
1223 }
1224}
1225
1226} // namespace QQmlJS
1227
1228QT_END_NAMESPACE
void postVisit(QQmlJS::AST::Node *) override
void checkGroupedAndAttachedScopes() override
void addStaticImportWithLocation(const QString &name, const QQmlJS::SourceLocation &loc, bool isDependency) override
void handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding, const AST::UiPublicMember *associatedPropertyDefinition) override
LinterVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, const QStringList &qmldirFiles=QStringList(), QQmlJS::Engine *engine=nullptr)
BindingExpressionParseResult parseBindingExpression(const QString &name, const QQmlJS::AST::Statement *statement, const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition=nullptr) override
void addImportWithLocation(const QString &name, const QQmlJS::SourceLocation &loc, bool hadWarnings) override
bool safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const QString &name, const QQmlJSScope::JavaScriptIdentifier &identifier) override
bool visit(QQmlJS::AST::StringLiteral *) override
QQmlJS::AST::Node * astParentOfVisitedNode() const
void endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) override
bool preVisit(QQmlJS::AST::Node *) override
static void warnForMethodShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name, const QQmlJS::SourceLocation &location, QQmlJSLogger *logger)
static bool isUselessExpressionStatement_impl(const ExpressionNode *ast)
static void warnForDuplicates(const QQmlJSScope::ConstPtr &scope, const QString &name, QLatin1String type, const QQmlJS::SourceLocation &location, OverrideInformations overrideFlags, QQmlJSLogger *logger)
static bool hasSignal(const QQmlJSScope::ConstPtr &scope, const QString &name)
static constexpr QLatin1String s_method
static void warnAboutLiteralConstructors(NewMemberExpression *expression, QQmlJSLogger *logger)
static SourceLocation confusingPluses(BinaryExpression *exp)
static bool isUselessExpressionStatement(const ExpressionStatement *ast)
static bool allCodePathsReturnInsideCase(Node *statement)
Q_DECLARE_FLAGS(OverrideInformations, OverrideInformation)
static bool compatibilityHeuristicForFileSelector(const QQmlJSScope::ConstPtr &scope1, const QQmlJSScope::ConstPtr &scope2)
static SourceLocation confusingMinuses(BinaryExpression *exp)
static QList< const Statement * > possibleLastStatements(const StatementList *ast)
static constexpr QLatin1String s_property
static constexpr QLatin1String s_signal
static void warnForPropertyShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name, const QQmlJS::SourceLocation &location, OverrideInformations overrideFlags, QQmlJSLogger *logger)
Combined button and popup list for selecting options.