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
302 if (seen.contains(key))
303 handleDuplicateEnums(uied->members, key, member->memberToken);
304 else
305 seen[member->member] = member;
306
307 if (uied->name == key) {
308 m_logger->log("Enum entry should be named differently than the enum itself to avoid "
309 "confusion."_L1, qmlEnumEntryMatchesEnum, member->firstSourceLocation());
310 }
311 }
312
313 return true;
314}
315
316static bool allCodePathsReturnInsideCase(Node *statement)
317{
318 using namespace AST;
319 if (!statement)
320 return false;
321
322 switch (statement->kind) {
323 case Node::Kind_Block: {
324 return allCodePathsReturnInsideCase(cast<Block *>(statement)->statements);
325 }
326 case Node::Kind_BreakStatement:
327 return true;
328 case Node::Kind_CaseBlock: {
329 const CaseBlock *caseBlock = cast<CaseBlock *>(statement);
330 if (caseBlock->defaultClause)
331 return allCodePathsReturnInsideCase(caseBlock->defaultClause);
332 return allCodePathsReturnInsideCase(caseBlock->clauses);
333 }
334 case Node::Kind_CaseClause:
335 return allCodePathsReturnInsideCase(cast<CaseClause *>(statement)->statements);
336 case Node::Kind_CaseClauses: {
337 for (CaseClauses *caseClauses = cast<CaseClauses *>(statement); caseClauses;
338 caseClauses = caseClauses->next) {
339 if (!allCodePathsReturnInsideCase(caseClauses->clause))
340 return false;
341 }
342 return true;
343 }
344 case Node::Kind_ContinueStatement:
345 // allCodePathsReturn() doesn't recurse into loops, so any encountered `continue` should
346 // belong to a loop outside the switch statement.
347 return true;
348 case Node::Kind_DefaultClause:
349 return allCodePathsReturnInsideCase(cast<DefaultClause *>(statement)->statements);
350 case Node::Kind_IfStatement: {
351 const auto *ifStatement = cast<IfStatement *>(statement);
352 return allCodePathsReturnInsideCase(ifStatement->ok)
353 && allCodePathsReturnInsideCase(ifStatement->ko);
354 }
355 case Node::Kind_LabelledStatement:
356 return allCodePathsReturnInsideCase(cast<LabelledStatement *>(statement)->statement);
357 case Node::Kind_ReturnStatement:
358 return true;
359 case Node::Kind_StatementList: {
360 for (StatementList *list = cast<StatementList *>(statement); list; list = list->next) {
361 if (allCodePathsReturnInsideCase(list->statement))
362 return true;
363 }
364 return false;
365 }
366 case Node::Kind_SwitchStatement:
367 return allCodePathsReturnInsideCase(cast<SwitchStatement *>(statement)->block);
368 case Node::Kind_ThrowStatement:
369 return true;
370 case Node::Kind_TryStatement: {
371 auto *tryStatement = cast<TryStatement *>(statement);
372 if (allCodePathsReturnInsideCase(tryStatement->statement))
373 return true;
374 return allCodePathsReturnInsideCase(tryStatement->finallyExpression->statement);
375 }
376 case Node::Kind_WithStatement:
377 return allCodePathsReturnInsideCase(cast<WithStatement *>(statement)->statement);
378 default:
379 break;
380 }
381 return false;
382}
383
384void LinterVisitor::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc,
385 SourceLocation nextLoc)
386{
387 if (!statements || !nextLoc.isValid())
388 return;
389
390 if (allCodePathsReturnInsideCase(statements))
391 return;
392
393 quint32 afterLastStatement = 0;
394 for (StatementList *it = statements; it; it = it->next) {
395 if (!it->next) {
396 afterLastStatement = it->statement->lastSourceLocation().end();
397 }
398 }
399
400 const auto &comments = m_engine->comments();
401 auto it = std::find_if(comments.cbegin(), comments.cend(),
402 [&](auto c) { return afterLastStatement < c.offset; });
403 auto end = std::find_if(it, comments.cend(),
404 [&](auto c) { return c.offset >= nextLoc.offset; });
405
406 for (; it != end; ++it) {
407 const QString &commentText = m_engine->code().mid(it->offset, it->length);
408 if (commentText.contains("fall through"_L1, Qt::CaseInsensitive)
409 || commentText.contains("fall-through"_L1, Qt::CaseInsensitive)
410 || commentText.contains("fallthrough"_L1, Qt::CaseInsensitive)) {
411 return;
412 }
413 }
414
415 m_logger->log(
416 "Non-empty case block potentially falls through to the next case or default statement. "
417 "Add \"// fallthrough\" at the end of the block to silence this warning."_L1,
418 qmlUnterminatedCase, errorLoc);
419}
420
421bool LinterVisitor::visit(QQmlJS::AST::CaseBlock *block)
422{
423 QQmlJSImportVisitor::visit(block);
424
425 std::vector<std::pair<SourceLocation, StatementList *>> clauses;
426 for (CaseClauses *it = block->clauses; it; it = it->next)
427 clauses.push_back({ it->clause->caseToken, it->clause->statements });
428 if (block->defaultClause)
429 clauses.push_back({ block->defaultClause->defaultToken, block->defaultClause->statements });
430 for (CaseClauses *it = block->moreClauses; it; it = it->next)
431 clauses.push_back({ it->clause->caseToken, it->clause->statements });
432
433 // check all but the last clause for fallthrough
434 for (size_t i = 0; i < clauses.size() - 1; ++i) {
435 const SourceLocation nextToken = clauses[i + 1].first;
436 checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
437 }
438 return true;
439}
440
441static QList<const Statement *> possibleLastStatements(const StatementList *ast);
442static QList<const Statement *> possibleLastStatements(const CaseBlock *ast)
443{
444 QList<const Statement *> lasts;
445
446 for (const auto *clause = ast->clauses; clause; clause = clause->next)
447 lasts << possibleLastStatements(clause->clause->statements);
448 if (ast->defaultClause)
449 lasts << possibleLastStatements(ast->defaultClause->statements);
450 for (const auto *clause = ast->moreClauses; clause; clause = clause->next)
451 lasts << possibleLastStatements(clause->clause->statements);
452
453 return lasts;
454}
455
456static QList<const Statement *> possibleLastStatements(const Statement *ast)
457{
458 if (const auto *s = cast<const Block *>(ast))
459 return possibleLastStatements(s->statements) << s;
460 if (const auto *s = cast<const BreakStatement *>(ast))
461 return { s };
462 if (const auto *s = cast<const ContinueStatement *>(ast))
463 return { s };
464 if (const auto *s = cast<const DebuggerStatement *>(ast))
465 return { s };
466 if (const auto *s = cast<const DoWhileStatement *>(ast))
467 return possibleLastStatements(s->statement) << s;
468 if (const auto *s = cast<const EmptyStatement *>(ast))
469 return { s };
470 if (const auto *s = cast<const ExportDeclaration *>(ast))
471 return { s };
472 if (const auto *s = cast<const ExpressionStatement *>(ast))
473 return { s };
474 if (const auto *s = cast<const ForEachStatement *>(ast))
475 return possibleLastStatements(s->statement) << s;
476 if (const auto *s = cast<const ForStatement *>(ast))
477 return possibleLastStatements(s->statement) << s;
478 if (const auto *s = cast<const IfStatement *>(ast)) {
479 auto lasts = possibleLastStatements(s->ok);
480 if (s->ko)
481 lasts << possibleLastStatements(s->ko);
482 return lasts << s;
483 }
484 if (const auto *s = cast<const ImportDeclaration *>(ast))
485 return { s };
486 if (const auto *s = cast<const LabelledStatement *>(ast))
487 return possibleLastStatements(s->statement) << ast;
488 if (const auto *s = cast<const ReturnStatement *>(ast))
489 return { s };
490 if (const auto *s = cast<const SwitchStatement *>(ast))
491 return possibleLastStatements(s->block) << s;
492 if (const auto *s = cast<const ThrowStatement *>(ast))
493 return { s };
494 if (const auto *s = cast<const TryStatement *>(ast))
495 return { s };
496 if (const auto *s = cast<const VariableStatement *>(ast))
497 return { s };
498 if (const auto *s = cast<const WhileStatement *>(ast))
499 return possibleLastStatements(s->statement) << s;
500 if (const auto *s = cast<const WithStatement *>(ast))
501 return possibleLastStatements(s->statement) << s;
502
503 Q_UNREACHABLE_RETURN({});
504}
505
506static QList<const Statement *> possibleLastStatements(const StatementList *ast)
507{
508 if (!ast)
509 return {};
510 for (; ast->next; ast = ast->next) { }
511 const auto *statement = ast->statement;
512
513 // Can't store FunctionDeclaration as a statement. See StatementList.
514 if (cast<const FunctionDeclaration *>(statement))
515 return {};
516
517 return possibleLastStatements(static_cast<const Statement *>(statement));
518}
519
520static bool isUselessExpressionStatement_impl(const ExpressionNode *ast)
521{
522 switch (ast->kind) {
523 case Node::Kind_CallExpression:
524 case Node::Kind_DeleteExpression:
525 case Node::Kind_NewExpression:
526 case Node::Kind_PreDecrementExpression:
527 case Node::Kind_PreIncrementExpression:
528 case Node::Kind_PostDecrementExpression:
529 case Node::Kind_PostIncrementExpression:
530 case Node::Kind_YieldExpression:
531 case Node::Kind_FunctionExpression:
532 return false;
533 case Node::Kind_NumericLiteral:
534 case Node::Kind_StringLiteral:
535 case Node::Kind_FalseLiteral:
536 case Node::Kind_TrueLiteral:
537 case Node::Kind_NullExpression:
538 case Node::Kind_Undefined:
539 case Node::Kind_RegExpLiteral:
540 case Node::Kind_SuperLiteral:
541 case Node::Kind_ThisExpression:
542 case Node::Kind_FieldMemberExpression:
543 case Node::Kind_IdentifierExpression:
544 case Node::Kind_TypeOfExpression:
545 return true;
546 default:
547 break;
548 }
549
550 if (const auto *e = cast<const NestedExpression *>(ast))
551 return isUselessExpressionStatement_impl(e->expression);
552 if (const auto *e = cast<const NotExpression *>(ast))
553 return isUselessExpressionStatement_impl(e->expression);
554 if (const auto *e = cast<const TildeExpression *>(ast))
555 return isUselessExpressionStatement_impl(e->expression);
556 if (const auto *e = cast<const UnaryMinusExpression *>(ast))
557 return isUselessExpressionStatement_impl(e->expression);
558 if (const auto *e = cast<const UnaryPlusExpression *>(ast))
559 return isUselessExpressionStatement_impl(e->expression);
560 if (const auto *e = cast<const ConditionalExpression *>(ast))
561 return isUselessExpressionStatement_impl(e->ok) && isUselessExpressionStatement_impl(e->ko);
562
563 if (const BinaryExpression *binary = cast<const BinaryExpression *>(ast)) {
564 switch (binary->op) {
565 case QSOperator::InplaceAnd:
566 case QSOperator::Assign:
567 case QSOperator::InplaceSub:
568 case QSOperator::InplaceDiv:
569 case QSOperator::InplaceExp:
570 case QSOperator::InplaceAdd:
571 case QSOperator::InplaceLeftShift:
572 case QSOperator::InplaceMod:
573 case QSOperator::InplaceMul:
574 case QSOperator::InplaceOr:
575 case QSOperator::InplaceRightShift:
576 case QSOperator::InplaceURightShift:
577 case QSOperator::InplaceXor:
578 return false;
579 default:
580 return isUselessExpressionStatement_impl(binary->left)
581 && isUselessExpressionStatement_impl(binary->right);
582 }
583 }
584
585 return false;
586}
587
588/*!
589\internal
590
591This assumes that there is no custom coercion enabled via \c Symbol.toPrimitive or similar.
592*/
593static bool isUselessExpressionStatement(const ExpressionStatement *ast)
594{
595 return isUselessExpressionStatement_impl(ast->expression);
596}
597
598void LinterVisitor::handleUselessExpressionStatement(const ExpressionStatement *ast)
599{
600 // property binding, signal handler, or function declaration
601 const auto it = std::find_if(m_ancestryIncludingCurrentNode.crbegin(),
602 m_ancestryIncludingCurrentNode.crend(),
603 [](auto it) {
604 return it->kind == Node::Kind_UiPublicMember
605 || it->kind == Node::Kind_FunctionDeclaration
606 || it->kind == Node::Kind_UiScriptBinding;
607 });
608
609 if (it == m_ancestryIncludingCurrentNode.crend())
610 return;
611
612 // A useless ExpressionStatement in *last position* inside a property binding is not useless
613 const auto isLastExprStat = [](const ExpressionStatement *statement, const Statement *base) {
614 const auto lasts = possibleLastStatements(base);
615 return lasts.contains(statement);
616 };
617
618 if (const auto *usb = cast<UiScriptBinding *>(*it); usb && usb->qualifiedId) {
619 if (usb->qualifiedId->toString() == "id"_L1)
620 return;
621 if (usb->qualifiedId->next)
622 return; // group/attached property, give up
623 if (m_savedBindingOuterScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope)
624 return; // grouped property, give up
625
626 QQmlJSScope::Ptr object = m_currentScope;
627 while (object && object->scopeType() != QQmlSA::ScopeType::QMLScope)
628 object = object->parentScope();
629
630 if (!object)
631 return;
632
633 if (m_propertyBindings.contains(object)) {
634 for (const auto &entry : m_propertyBindings[object]) {
635 if (entry.data == usb->qualifiedId->toString()) {
636 if (isLastExprStat(ast, usb->statement))
637 return;
638 else
639 break;
640 }
641 }
642 }
643 }
644
645 const auto *upm = cast<const UiPublicMember *>(*it);
646 if (upm && upm->type == AST::UiPublicMember::Property && upm->statement) {
647 if (isLastExprStat(ast, upm->statement))
648 return;
649 }
650
651 if (isUselessExpressionStatement(ast)) {
652 m_logger->log("Expression statement has no obvious effect."_L1,
653 qmlConfusingExpressionStatement,
654 combine(ast->firstSourceLocation(), ast->lastSourceLocation()));
655 }
656}
657
658bool LinterVisitor::visit(ExpressionStatement *ast)
659{
660 QQmlJSImportVisitor::visit(ast);
661 handleUselessExpressionStatement(ast);
662 return true;
663}
664
665bool LinterVisitor::safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const QString &name, const QQmlJSScope::JavaScriptIdentifier &identifier)
666{
667 if (scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope &&
668 identifier.kind == QQmlJSScope::JavaScriptIdentifier::FunctionScoped) {
669 // var is generally not great, but we don't want to emit this warning if you
670 // are in the single, toplevel block of a binding
671 Q_ASSERT(!scope->parentScope().isNull()); // lexical scope should always have a parent
672 auto parentScopeType = scope->parentScope()->scopeType();
673 bool inTopLevelBindingBlockScope = parentScopeType == QQmlSA::ScopeType::BindingFunctionScope
674 || parentScopeType == QQmlSA::ScopeType::SignalHandlerFunctionScope;
675 if (!inTopLevelBindingBlockScope) {
676 m_logger->log(u"var declaration in block scope is hoisted to function scope\n"_s
677 u"Replace it with const or let to silence the warning\n"_s,
678 qmlBlockScopeVarDeclaration, identifier.location);
679 }
680 } else if (scope->scopeType() == QQmlSA::ScopeType::QMLScope) {
681 // we already warn in IRBuilder
682 return false;
683 }
684 return QQmlJSImportVisitor::safeInsertJSIdentifier(scope, name, identifier);
685}
686
688 const QString &name, const QQmlJS::AST::Statement *statement,
689 const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition)
690{
691 if (statement && statement->kind == (int)AST::Node::Kind::Kind_Block) {
692 const auto *block = static_cast<const AST::Block *>(statement);
693 if (!block->statements && associatedPropertyDefinition) {
694 m_logger->log("Unintentional empty block, use ({}) for empty object literal"_L1,
695 qmlUnintentionalEmptyBlock,
696 combine(block->lbraceToken, block->rbraceToken));
697 }
698 }
699
700 return QQmlJSImportVisitor::parseBindingExpression(name, statement, associatedPropertyDefinition);
701}
702
703void LinterVisitor::handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding,
704 const UiPublicMember *associatedPropertyDefinition)
705{
706 if (!m_currentScope->hasOwnProperty(binding.propertyName()))
707 return;
708
709 if (!associatedPropertyDefinition->isReadonly())
710 return;
711
712 const auto &prop = m_currentScope->property(binding.propertyName());
713 const auto log = [&](const QString &preferredType) {
714 m_logger->log("Prefer more specific type %1 over var"_L1.arg(preferredType),
715 qmlPreferNonVarProperties, prop.sourceLocation());
716 };
717
718 if (prop.typeName() != "QVariant"_L1)
719 return;
720
721 switch (binding.bindingType()) {
722 case QQmlSA::BindingType::BoolLiteral: {
723 log("bool"_L1);
724 break;
725 }
726 case QQmlSA::BindingType::NumberLiteral: {
727 double v = binding.numberValue();
728 auto loc = binding.sourceLocation();
729 QStringView literal = QStringView(m_engine->code()).mid(loc.offset, loc.length);
730 if (literal.contains(u'.') || double(int(v)) != v)
731 log("real or double"_L1);
732 else
733 log("int"_L1);
734 break;
735 }
736 case QQmlSA::BindingType::StringLiteral: {
737 log("string"_L1);
738 break;
739 }
740 default: {
741 break;
742 }
743 }
744}
745
746bool LinterVisitor::visit(UiProgram *ast)
747{
748 const bool result = QQmlJSImportVisitor::visit(ast);
749
750 m_renamedComponents.setScopeToName(&m_rootScopeImports.names());
751
752 return result;
753}
754
755void LinterVisitor::checkUnusedImports()
756{
757 // skip warnings on unresolved types, the import might import unregistered types
758 if (m_hasMaybeUnresolvedTypes || m_unresolvedTypes.begin() != m_unresolvedTypes.end())
759 return;
760
761 auto unusedImports = m_importLocations;
762 for (const QString &type : std::as_const(m_usedTypes)) {
763 const auto &importLocations = m_importTypeLocationMap.values(type);
764 for (const auto &importLocation : importLocations)
765 unusedImports.remove(importLocation);
766
767 // If there are no more unused imports left we can abort early
768 if (unusedImports.isEmpty())
769 break;
770 }
771
772 const auto &imports = m_importStaticModuleLocationMap.values();
773 for (const QQmlJS::SourceLocation &import : imports)
774 unusedImports.remove(import);
775
776 for (const auto &import : unusedImports) {
777 m_logger->log(QString::fromLatin1("Unused import"), qmlUnusedImports, import);
778 }
779}
780
781void LinterVisitor::endVisit(UiProgram *ast)
782{
783 QQmlJSImportVisitor::endVisit(ast);
784 checkFileSelections();
785 checkUnusedImports();
786 checkSignalHandlerNameClashes();
787}
788
789static constexpr QLatin1String s_method = "method"_L1;
790static constexpr QLatin1String s_signal = "signal"_L1;
791static constexpr QLatin1String s_property = "property"_L1;
792
795
796static void warnForMethodShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name,
797 const QQmlJS::SourceLocation &location,
798 QQmlJSLogger *logger)
799{
800 Q_ASSERT(base);
801 if (!base->hasMethod(name))
802 return;
803
804 static constexpr QLatin1String warningMessage =
805 "%1 \"%2\" already exists in base type \"%3\", use a different name."_L1;
806 const auto owner = QQmlJSScope::ownerOfMethod(base, name).scope;
807 const bool isSignal = owner->methods(name).front().methodType() == QQmlJSMetaMethodType::Signal;
808 logger->log(warningMessage.arg(isSignal ? "Signal"_L1 : "Method"_L1, name,
809 QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
810 qmlShadow, location);
811}
812
813static void warnForPropertyShadowingInBase(const QQmlJSScope::ConstPtr &base, const QString &name,
814 const QQmlJS::SourceLocation &location,
815 OverrideInformations overrideFlags, QQmlJSLogger *logger)
816{
817 Q_ASSERT(base);
818 const bool hasOverride = overrideFlags.testFlag(WithOverride);
819 if (!base->hasProperty(name)) {
820 if (!hasOverride)
821 return;
822 logger->log(
823 "Member \"%1\" does not override anything. Consider removing \"override\"."_L1.arg(
824 name),
825 qmlPropertyOverride, location);
826 return;
827 }
828
829 const auto owner = QQmlJSScope::ownerOfProperty(base, name).scope;
830 const auto shadowedProperty = owner->ownProperty(name);
831 if (shadowedProperty.isFinal()) {
832 logger->log(
833 (!hasOverride
834 ? "Member \"%1\" shadows final member \"%1\" from base type \"%2\", use a different name."_L1
835 : "Member \"%1\" overrides final member \"%1\" from base type \"%2\", use a different name and remove the \"override\"."_L1)
836 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
837 qmlPropertyOverride, location);
838 return;
839 }
840
841 if (shadowedProperty.isVirtual() || shadowedProperty.isOverride()) {
842 if (hasOverride || overrideFlags.testFlag(WithFinal))
843 return;
844
845 logger->log(
846 "Member \"%1\" shadows member \"%1\" from base type \"%2\", use a different name or add a final or override specifier."_L1
847 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
848 qmlPropertyOverride, location);
849 return;
850 }
851
852 if (hasOverride) {
853 logger->log(
854 "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
855 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
856 qmlPropertyOverride, location);
857 return;
858 }
859 logger->log("Property \"%2\" already exists in base type \"%3\", use a different name."_L1.arg(
860 name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
861 qmlPropertyOverride, location);
862}
863
864static void warnForDuplicates(const QQmlJSScope::ConstPtr &scope, const QString &name,
865 QLatin1String type, const QQmlJS::SourceLocation &location,
866 OverrideInformations overrideFlags, QQmlJSLogger *logger)
867{
868 static constexpr QLatin1String duplicateMessage =
869 "Duplicated %1 name \"%2\", \"%2\" is already a %3."_L1;
870 if (const auto methods = scope->ownMethods(name); !methods.isEmpty()) {
871 const bool isSignal = methods.front().methodType() == QQmlSA::MethodType::Signal;
872 const bool alreadyWarnedByIrBuilder =
873 (type == s_signal && isSignal) || (type == s_method && !isSignal);
874 if (!alreadyWarnedByIrBuilder) {
875 logger->log(duplicateMessage.arg(type, name, isSignal ? s_signal : s_method),
876 qmlDuplicatedName, location);
877 }
878 }
879 // note: IRBuilder already warns about property name duplicating others property name
880 if (type != s_property && scope->hasOwnProperty(name))
881 logger->log(duplicateMessage.arg(type, name, s_property), qmlDuplicatedName, location);
882
883 const QQmlJSScope::ConstPtr base = scope->baseType();
884 if (!base)
885 return;
886
887 warnForMethodShadowingInBase(base, name, location, logger);
888 warnForPropertyShadowingInBase(base, name, location, overrideFlags, logger);
889}
890
891static bool hasSignal(const QQmlJSScope::ConstPtr &scope, const QString &name)
892{
893 const auto methods = scope->methods(name);
894 return std::any_of(methods.cbegin(), methods.cend(), [](const QQmlJSMetaMethod &method) {
895 return method.methodType() == QQmlSA::MethodType::Signal;
896 });
897}
898
899/*!
900\internal
901Warns about properties whose names are also signal handler names for another member of the same
902object. The QML engine gives the signal handler interpretation precedence, so such properties can
903never be bound: a literal initializer is an error and an expression initializer is run as a signal
904handler instead. This mirrors SignalHandlerResolver, which accepts signals no matter which type
905declares them, but plain properties only from the object the binding appears in.
906*/
907void LinterVisitor::checkSignalHandlerNameClashes()
908{
909 static constexpr QLatin1String clashMessage =
910 "Property \"%1\" is interpreted as a signal handler for %2 \"%3\", "
911 "use a different name."_L1;
912
913 for (const auto &[scope, name, location] : std::as_const(m_handlerLikeProperties)) {
914 QString changedPropertyName;
915 QString signalName;
916 if (const auto propertyName = QQmlSignalNames::changedHandlerNameToPropertyName(name)) {
917 changedPropertyName = *propertyName;
918 signalName = *QQmlSignalNames::changedHandlerNameToSignalName(name);
919 } else {
920 signalName = QQmlSignalNames::handlerNameToSignalName(name).value_or(QString());
921 }
922
923 if (signalName.isEmpty())
924 continue;
925
926 if (hasSignal(scope, signalName)) {
927 m_logger->log(clashMessage.arg(name, s_signal, signalName), qmlSignalHandlerNameClash,
928 location);
929 } else if (!changedPropertyName.isEmpty() && scope->hasProperty(changedPropertyName)) {
930 m_logger->log(clashMessage.arg(name, s_property, changedPropertyName),
931 qmlSignalHandlerNameClash, location);
932 } else if (scope->hasOwnProperty(signalName)) {
933 m_logger->log(clashMessage.arg(name, s_property, signalName), qmlSignalHandlerNameClash,
934 location);
935 }
936 }
937}
938
939void LinterVisitor::handleRenamedType(UiQualifiedId *qualifiedId)
940{
941 m_renamedComponents.handleRenamedType(
942 m_rootScopeImports.type(qualifiedId->name.toString()).scope, qualifiedId->name,
943 qualifiedId->identifierToken, m_logger);
944}
945
946bool LinterVisitor::visit(Type *type)
947{
948 const bool result = QQmlJSImportVisitor::visit(type);
949
950 handleRenamedType(type->typeId);
951
952 return result;
953}
954
955void LinterVisitor::handleRecursivelyInstantiatedType(UiQualifiedId *qualifiedId)
956{
957 // It should be ok to reference inline components or enums inside of the current file
958 if (qualifiedId->next)
959 return;
960
961 auto logWarning = [&qualifiedId, this]() {
962 m_logger->log("Type \"%1\" can't be instantiated recursively"_L1.arg(qualifiedId->name),
963 qmlTypeInstantiatedRecursively, qualifiedId->identifierToken);
964 };
965
966 const QString name = qualifiedId->name.toString();
967 if (m_rootScopeImports.names().contains(m_exportedRootScope, name))
968 logWarning();
969
970 // note: inline components can't be renamed via qmldir entries
971 if (const auto inlineComponentName = std::get_if<InlineComponentNameType>(&m_currentRootName);
972 inlineComponentName && name == *inlineComponentName) {
973 logWarning();
974 }
975}
976
977bool LinterVisitor::visit(QQmlJS::AST::UiPragma *pragma)
978{
979 const bool result = QQmlJSImportVisitor::visit(pragma);
980
981 // The QML Engine ignores this pragma, so __don't__ set m_exportedRootScope's singleton flag with its value.
982 if (pragma->name == u"Singleton")
983 m_rootIsSingleton = true;
984
985 return result;
986}
987
988void LinterVisitor::checkSingletonRoot()
989{
990 const bool hasQmldirSingletonEntry = m_exportedRootScope->isSingleton(); // set by importer
991 const bool hasSingletonPragma = m_rootIsSingleton;
992
993 if (hasQmldirSingletonEntry == hasSingletonPragma)
994 return;
995
996 if (hasQmldirSingletonEntry && !hasSingletonPragma) {
997 m_logger->log("Type %1 declared as singleton in qmldir but missing pragma Singleton"_L1.arg(
998 m_exportedRootScope->internalName()),
999 qmlImport, QQmlJS::SourceLocation());
1000 return;
1001 }
1002 Q_ASSERT(!hasQmldirSingletonEntry && hasSingletonPragma);
1003 m_logger->log("Type %1 not declared as singleton in qmldir but using pragma Singleton"_L1.arg(
1004 m_exportedRootScope->internalName()),
1005 qmlImport, QQmlJS::SourceLocation());
1006}
1007
1008bool LinterVisitor::visit(QQmlJS::AST::UiObjectDefinition *objectDefinition)
1009{
1010 handleRenamedType(objectDefinition->qualifiedTypeNameId);
1011 handleRecursivelyInstantiatedType(objectDefinition->qualifiedTypeNameId);
1012 if (!rootScopeIsValid() && !objectDefinition->qualifiedTypeNameId->name.front().isLower())
1013 checkSingletonRoot();
1014
1015 return QQmlJSImportVisitor::visit(objectDefinition);
1016}
1017
1018bool LinterVisitor::visit(UiPublicMember *publicMember)
1019{
1020 switch (publicMember->type) {
1021 case UiPublicMember::Signal: {
1022 const QString signalName = publicMember->name.toString();
1023 warnForDuplicates(m_currentScope, signalName, s_signal, publicMember->identifierToken,
1024 WithoutOverride, m_logger);
1025 break;
1026 }
1027 case QQmlJS::AST::UiPublicMember::Property: {
1028 const QString propertyName = publicMember->name.toString();
1029 OverrideInformations flags;
1030 flags.setFlag(WithOverride, publicMember->isOverride());
1031 flags.setFlag(WithFinal, publicMember->isFinal());
1032 warnForDuplicates(m_currentScope, propertyName, s_property, publicMember->identifierToken,
1033 flags, m_logger);
1034 if (QQmlSignalNames::isHandlerName(propertyName)) {
1035 m_handlerLikeProperties.append(
1036 { m_currentScope, propertyName, publicMember->identifierToken });
1037 }
1038 handleRenamedType(publicMember->memberType);
1039
1040 const QString typeName = publicMember->memberType->toString();
1041 if (typeName != "alias"_L1) {
1042 if (m_rootScopeImports.hasType(typeName)
1043 && !m_rootScopeImports.type(typeName).scope.isNull()) {
1044 if (m_importTypeLocationMap.contains(typeName))
1045 m_usedTypes.insert(typeName);
1046 }
1047 }
1048 break;
1049 }
1050 }
1051 return QQmlJSImportVisitor::visit(publicMember);
1052}
1053
1054bool LinterVisitor::visit(FunctionExpression *fexpr)
1055{
1056 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
1057 warnForDuplicates(m_currentScope, fexpr->name.toString(), s_method, fexpr->identifierToken,
1058 WithoutOverride, m_logger);
1059 }
1060 return QQmlJSImportVisitor::visit(fexpr);
1061}
1062
1063bool LinterVisitor::visit(FunctionDeclaration *fdecl)
1064{
1065 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
1066 warnForDuplicates(m_currentScope, fdecl->name.toString(), s_method, fdecl->identifierToken,
1067 WithoutOverride, m_logger);
1068 }
1069 return QQmlJSImportVisitor::visit(fdecl);
1070}
1071
1072/* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about common constructs.
1073 We might want to improve it in the future if it causes issues
1074*/
1075static bool compatibilityHeuristicForFileSelector(const QQmlJSScope::ConstPtr &scope1,
1076 const QQmlJSScope::ConstPtr &scope2)
1077{
1078 for (const auto &[propertyName, prop] : scope1->properties().asKeyValueRange())
1079 if (!scope2->hasProperty(propertyName))
1080 return false;
1081 for (const auto &[methodName, method] : scope1->methods().asKeyValueRange())
1082 if (!scope2->hasMethod(methodName))
1083 return false;
1084 return true;
1085}
1086
1087// heuristic to check file selected files for "compability" to the unselected file.
1088void LinterVisitor::checkFileSelections()
1089{
1090 const QQmlJS::FileSelectorInfo info =
1091 m_rootScopeImports.contextualTypes().fileSelectorInfoFor(m_exportedRootScope);
1092
1093 if (info.fileSelectedTypes.isEmpty() || info.mainType.isNull())
1094 return;
1095
1096 const QString name = m_rootScopeImports.name(m_exportedRootScope);
1097
1098 if (info.mainType == m_exportedRootScope) {
1099 // current has fileselectors -> check all fileselectors for compatiblity
1100 for (const auto &fileSelected : info.fileSelectedTypes) {
1101 if (compatibilityHeuristicForFileSelector(m_exportedRootScope,
1102 fileSelected.type.scope)) {
1103 m_logger->log(
1104 "Type %1 is ambiguous due to file selector usage, ignoring %2."_L1.arg(
1105 name, fileSelected.type.scope->filePath()),
1106 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1107 continue;
1108 }
1109 m_logger->log("Type %1 has a potentially incompatible file-selected variant %2."_L1.arg(
1110 name, fileSelected.type.scope->filePath()),
1111 qmlImport, m_exportedRootScope->sourceLocation());
1112 }
1113 return;
1114 }
1115
1116 // current is fileselected -> only check against "main" type for compatibility
1117 if (compatibilityHeuristicForFileSelector(info.mainType, m_exportedRootScope)) {
1118 m_logger->log(
1119 "File-selected type %1 is ambiguous due to file selector usage, this file will be ignored in favour of %2."_L1
1120 .arg(name, info.mainType->filePath()),
1121 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1122 return;
1123 }
1124 m_logger->log("File-selected type %1 is potentially incompatible with %2."_L1.arg(
1125 name, info.mainType->filePath()),
1126 qmlImport, m_exportedRootScope->sourceLocation());
1127}
1128
1129void LinterVisitor::addImportWithLocation(const QString &name, const QQmlJS::SourceLocation &loc,
1130 bool hadWarnings)
1131{
1132 if (m_importTypeLocationMap.contains(name)
1133 && m_importTypeLocationMap.values(name).contains(loc)) {
1134 return;
1135 }
1136
1137 m_importTypeLocationMap.insert(name, loc);
1138
1139 // If the import had warnings it may be "unused" because we haven't found all of its types.
1140 // If the type's location is not valid it's a builtin.
1141 // We don't need to complain about those being unused.
1142 if (!hadWarnings && loc.isValid())
1143 m_importLocations.insert(loc);
1144}
1145
1146void LinterVisitor::addStaticImportWithLocation(const QString &name,
1147 const QQmlJS::SourceLocation &loc,
1148 bool isDependency)
1149{
1150 // Always prefer a direct import of static module to it being imported as a dependency
1151 if (isDependency && m_importStaticModuleLocationMap.contains(name))
1152 return;
1153 m_importStaticModuleLocationMap[name] = loc;
1154}
1155
1156bool LinterVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp)
1157{
1158 const QString name = idexp->name.toString();
1159 if (m_importTypeLocationMap.contains(name)) {
1160 m_usedTypes.insert(name);
1161 } else {
1162 m_hasMaybeUnresolvedTypes = m_hasMaybeUnresolvedTypes || name.front().isUpper();
1163 }
1164
1165 return true;
1166}
1167
1168void LinterVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember)
1169{
1170 // This is a rather rough approximation of "used type" but the "unused import"
1171 // info message doesn't have to be 100% accurate.
1172 const QString name = fieldMember->name.toString();
1173 if (m_importTypeLocationMap.contains(name)) {
1174 const QQmlJSImportedScope type = m_rootScopeImports.type(name);
1175 if (type.scope.isNull()) {
1176 if (m_rootScopeImports.hasType(name))
1177 m_usedTypes.insert(name);
1178 } else if (!type.scope->attachedTypeName().isEmpty()) {
1179 m_usedTypes.insert(name);
1180 }
1181 } else {
1182 m_hasMaybeUnresolvedTypes = m_hasMaybeUnresolvedTypes || name.front().isUpper();
1183 }
1184}
1185
1187{
1188 for (const auto &scope : std::as_const(m_objectDefinitionScopes))
1189 checkGroupedAndAttachedScope(scope);
1190}
1191
1192void LinterVisitor::checkGroupedAndAttachedScope(const QQmlJSScope::ConstPtr &scope)
1193{
1194 // These warnings do not apply for custom parsers and their children and need to be handled on a
1195 // case by case basis
1196 if (checkCustomParser(scope))
1197 return;
1198
1199 if (!checkTypeResolved(scope))
1200 return;
1201
1202 auto children = scope->childScopes();
1203 while (!children.isEmpty()) {
1204 auto childScope = children.takeFirst();
1205 const auto type = childScope->scopeType();
1206 switch (type) {
1207 case QQmlSA::ScopeType::GroupedPropertyScope:
1208 case QQmlSA::ScopeType::AttachedPropertyScope:
1209 if (!childScope->baseType() && !m_unresolvedTypes.hasSeen(childScope)) {
1210 // note: if we already warn about the unknown property scope then we don't have to
1211 // warn again about it being unresolved later
1212 m_logger->log(QStringLiteral("unknown %1 property scope %2.")
1213 .arg(type == QQmlSA::ScopeType::GroupedPropertyScope
1214 ? QStringLiteral("grouped")
1215 : QStringLiteral("attached"),
1216 childScope->internalName()),
1217 qmlUnqualified, childScope->sourceLocation());
1218 }
1219 children.append(childScope->childScopes());
1220 break;
1221 default:
1222 break;
1223 }
1224 }
1225}
1226
1227} // namespace QQmlJS
1228
1229QT_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.