7#include <private/qqmljsutils_p.h>
11using namespace Qt::StringLiterals;
12using namespace QQmlJS::AST;
16
17
18
19
20
21
24 QQmlJSImporter *importer, QQmlJSLogger *logger,
25 const QString &implicitImportDirectory,
const QStringList &qmldirFiles,
26 QQmlJS::Engine *engine)
34 const auto leaveEnv = qScopeGuard([
this] { QQmlJSImportVisitor::leaveEnvironment(); });
36 if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope)
39 if (
auto base = m_currentScope->baseType()) {
40 if (base->internalName() == u"QQmlComponent"_s) {
41 const auto nChildren = std::count_if(
42 m_currentScope->childScopesBegin(), m_currentScope->childScopesEnd(),
43 [](
const QQmlJSScope::ConstPtr &scope) {
44 return scope->scopeType() == QQmlSA::ScopeType::QMLScope;
47 m_logger->log(
"Components must have exactly one child"_L1,
48 qmlComponentChildrenCount, m_currentScope->sourceLocation());
54bool LinterVisitor::
visit(StringLiteral *sl)
56 QQmlJSImportVisitor::visit(sl);
57 const QString s = m_logger->code().mid(sl->literalToken.begin(), sl->literalToken.length);
59 if (s.contains(QLatin1Char(
'\r')) || s.contains(QLatin1Char(
'\n')) || s.contains(QChar(0x2028u))
60 || s.contains(QChar(0x2029u))) {
61 QString templateString;
64 const QChar stringQuote = s[0];
65 for (qsizetype i = 1; i < s.size() - 1; i++) {
73 templateString.chop(1);
78 templateString += u'\\';
79 if (c == u'$' && i + 1 < s.size() - 1 && s[i + 1] == u'{')
80 templateString += u'\\';
86 QQmlJSDocumentEdit documentEdit{
87 m_logger->filePath(), sl->literalToken, u"`" % templateString % u"`"
89 QQmlJSFixSuggestion suggestion = {
"Use a template literal instead."_L1, sl->literalToken,
91 suggestion.setAutoApplicable();
92 m_logger->log(QStringLiteral(
"String contains unescaped line terminator which is "
94 qmlMultilineStrings, sl->literalToken,
true,
true, suggestion);
101 m_ancestryIncludingCurrentNode.push_back(n);
107 Q_ASSERT(m_ancestryIncludingCurrentNode.back() == n);
108 m_ancestryIncludingCurrentNode.pop_back();
113 if (m_ancestryIncludingCurrentNode.size() < 2)
115 return m_ancestryIncludingCurrentNode[m_ancestryIncludingCurrentNode.size() - 2];
118bool LinterVisitor::
visit(CommaExpression *expression)
120 QQmlJSImportVisitor::visit(expression);
121 if (!expression->left || !expression->right)
125 if (cast<ForStatement *>(astParentOfVisitedNode()))
128 m_logger->log(
"Do not use comma expressions."_L1, qmlComma, expression->commaToken);
134 static constexpr std::array literals{
"Boolean"_L1,
"Function"_L1,
"JSON"_L1,
135 "Math"_L1,
"Number"_L1,
"String"_L1 };
137 const IdentifierExpression *identifier = cast<IdentifierExpression *>(expression->base);
141 if (std::find(literals.cbegin(), literals.cend(), identifier->name) != literals.cend()) {
142 logger->log(
"Do not use '%1' as a constructor."_L1.arg(identifier->name),
143 qmlLiteralConstructor, identifier->identifierToken);
145 if (identifier->name ==
"Array"_L1 && expression->arguments && expression->arguments->next) {
146 const auto fullRange = combine(expression->newToken, expression->rparenToken);
147 const QList<QQmlJSDocumentEdit> edits = {
148 { logger->filePath(), combine(expression->newToken, expression->lparenToken),
"["_L1 },
149 { logger->filePath(), expression->rparenToken,
"]"_L1 },
151 QQmlJSFixSuggestion fix(
"Replace with array literal"_L1, fullRange, edits);
152 fix.setAutoApplicable(
true);
153 logger->log(
"Array has confusing semantics, use an array literal ([]) instead."_L1,
154 qmlLiteralConstructor, identifier->identifierToken,
true,
true, fix);
158bool LinterVisitor::
visit(NewMemberExpression *expression)
160 QQmlJSImportVisitor::visit(expression);
161 warnAboutLiteralConstructors(expression, m_logger);
165bool LinterVisitor::
visit(VoidExpression *ast)
167 QQmlJSImportVisitor::visit(ast);
168 m_logger->log(
"Do not use void expressions."_L1, qmlVoid, ast->voidToken);
174 Q_ASSERT(exp->op == QSOperator::Add);
176 SourceLocation location = exp->operatorToken;
179 if (
auto increment = cast<PostIncrementExpression *>(exp->left))
180 location = combine(increment->incrementToken, location);
182 if (
auto unary = cast<UnaryPlusExpression *>(exp->right))
183 location = combine(location, unary->plusToken);
185 if (
auto increment = cast<PreIncrementExpression *>(exp->right))
186 location = combine(location, increment->incrementToken);
188 if (location == exp->operatorToken)
189 return SourceLocation{};
196 Q_ASSERT(exp->op == QSOperator::Sub);
198 SourceLocation location = exp->operatorToken;
201 if (
auto decrement = cast<PostDecrementExpression *>(exp->left))
202 location = combine(decrement->decrementToken, location);
204 if (
auto unary = cast<UnaryMinusExpression *>(exp->right))
205 location = combine(location, unary->minusToken);
207 if (
auto decrement = cast<PreDecrementExpression *>(exp->right))
208 location = combine(location, decrement->decrementToken);
210 if (location == exp->operatorToken)
211 return SourceLocation{};
216bool LinterVisitor::
visit(BinaryExpression *exp)
218 QQmlJSImportVisitor::visit(exp);
220 case QSOperator::Add:
221 if (SourceLocation loc = confusingPluses(exp); loc.isValid())
222 m_logger->log(
"Confusing pluses."_L1, qmlConfusingPluses, loc);
224 case QSOperator::Sub:
225 if (SourceLocation loc = confusingMinuses(exp); loc.isValid())
226 m_logger->log(
"Confusing minuses."_L1, qmlConfusingMinuses, loc);
235bool LinterVisitor::
visit(QQmlJS::AST::UiImport *import)
237 QQmlJSImportVisitor::visit(import);
239 const auto locAndName = [](
const UiImport *i) {
241 return std::make_pair(i->fileNameToken, i->fileName.toString());
243 QQmlJS::SourceLocation l = i->importUri->firstSourceLocation();
244 if (i->importIdToken.isValid())
245 l = combine(l, i->importIdToken);
247 l = combine(l, i->version->minorToken);
249 l = combine(l, i->importUri->lastSourceLocation());
251 return std::make_pair(l, i->importUri->toString());
254 SeenImport i(import);
255 if (
const auto it = m_seenImports.constFind(i); it != m_seenImports.constEnd()) {
256 const auto locAndNameImport = locAndName(import);
257 const auto locAndNameSeen = locAndName(it->uiImport);
258 m_logger->log(
"Duplicate import '%1'"_L1.arg(locAndNameImport.second),
259 qmlDuplicateImport, locAndNameImport.first);
260 m_logger->log(
"Note: previous import '%1' here"_L1.arg(locAndNameSeen.second),
261 qmlDuplicateImport, locAndNameSeen.first,
true,
true, {},
262 locAndName(import).first.startLine);
265 m_seenImports.insert(i);
269void LinterVisitor::handleDuplicateEnums(UiEnumMemberList *members, QStringView key,
270 const QQmlJS::SourceLocation &location)
272 m_logger->log(u"Enum key '%1' has already been declared"_s.arg(key), qmlDuplicateEnumEntries,
274 for (
const auto *member = members; member; member = member->next) {
275 if (member->member.toString() == key) {
276 m_logger->log(u"Note: previous declaration of '%1' here"_s.arg(key),
277 qmlDuplicateEnumEntries, member->memberToken);
283bool LinterVisitor::
visit(QQmlJS::AST::UiEnumDeclaration *uied)
285 QQmlJSImportVisitor::visit(uied);
287 if (m_currentScope->isInlineComponent()) {
288 m_logger->log(u"Enums declared inside of inline component are ignored."_s,
289 qmlInlineComponentEnums, uied->firstSourceLocation());
290 }
else if (m_currentScope->componentRootStatus() == QQmlJSScope::IsComponentRoot::No
291 && !m_currentScope->isFileRootComponent()) {
292 m_logger->log(u"Enum declared outside the root element. It won't be accessible."_s,
293 qmlNonRootEnums, uied->firstSourceLocation());
296 QHash<QStringView,
const QQmlJS::AST::UiEnumMemberList *> seen;
297 for (
const auto *member = uied->members; member; member = member->next) {
298 QStringView key = member->member;
299 if (!key.front().isUpper()) {
300 m_logger->log(u"Enum keys should start with an uppercase."_s, qmlEnumKeyCase,
301 member->memberToken);
304 if (seen.contains(key))
305 handleDuplicateEnums(uied->members, key, member->memberToken);
307 seen[member->member] = member;
309 if (uied->name == key) {
310 m_logger->log(
"Enum entry should be named differently than the enum itself to avoid "
311 "confusion."_L1, qmlEnumEntryMatchesEnum, member->firstSourceLocation());
324 switch (statement->kind) {
325 case Node::Kind_Block: {
326 return allCodePathsReturnInsideCase(cast<Block *>(statement)->statements);
328 case Node::Kind_BreakStatement:
330 case Node::Kind_CaseBlock: {
331 const CaseBlock *caseBlock = cast<CaseBlock *>(statement);
332 if (caseBlock->defaultClause)
333 return allCodePathsReturnInsideCase(caseBlock->defaultClause);
334 return allCodePathsReturnInsideCase(caseBlock->clauses);
336 case Node::Kind_CaseClause:
337 return allCodePathsReturnInsideCase(cast<CaseClause *>(statement)->statements);
338 case Node::Kind_CaseClauses: {
339 for (CaseClauses *caseClauses = cast<CaseClauses *>(statement); caseClauses;
340 caseClauses = caseClauses->next) {
341 if (!allCodePathsReturnInsideCase(caseClauses->clause))
346 case Node::Kind_ContinueStatement:
350 case Node::Kind_DefaultClause:
351 return allCodePathsReturnInsideCase(cast<DefaultClause *>(statement)->statements);
352 case Node::Kind_IfStatement: {
353 const auto *ifStatement = cast<IfStatement *>(statement);
354 return allCodePathsReturnInsideCase(ifStatement->ok)
355 && allCodePathsReturnInsideCase(ifStatement->ko);
357 case Node::Kind_LabelledStatement:
358 return allCodePathsReturnInsideCase(cast<LabelledStatement *>(statement)->statement);
359 case Node::Kind_ReturnStatement:
361 case Node::Kind_StatementList: {
362 for (StatementList *list = cast<StatementList *>(statement); list; list = list->next) {
363 if (allCodePathsReturnInsideCase(list->statement))
368 case Node::Kind_SwitchStatement:
369 return allCodePathsReturnInsideCase(cast<SwitchStatement *>(statement)->block);
370 case Node::Kind_ThrowStatement:
372 case Node::Kind_TryStatement: {
373 auto *tryStatement = cast<TryStatement *>(statement);
374 if (allCodePathsReturnInsideCase(tryStatement->statement))
376 return allCodePathsReturnInsideCase(tryStatement->finallyExpression->statement);
378 case Node::Kind_WithStatement:
379 return allCodePathsReturnInsideCase(cast<WithStatement *>(statement)->statement);
386void LinterVisitor::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc,
387 SourceLocation nextLoc)
389 if (!statements || !nextLoc.isValid())
392 if (allCodePathsReturnInsideCase(statements))
395 quint32 afterLastStatement = 0;
396 for (StatementList *it = statements; it; it = it->next) {
398 afterLastStatement = it->statement->lastSourceLocation().end();
402 const auto &comments = m_engine->comments();
403 auto it = std::find_if(comments.cbegin(), comments.cend(),
404 [&](
auto c) {
return afterLastStatement < c.offset; });
405 auto end = std::find_if(it, comments.cend(),
406 [&](
auto c) {
return c.offset >= nextLoc.offset; });
408 for (; it != end; ++it) {
409 const QString &commentText = m_engine->code().mid(it->offset, it->length);
410 if (commentText.contains(
"fall through"_L1, Qt::CaseInsensitive)
411 || commentText.contains(
"fall-through"_L1, Qt::CaseInsensitive)
412 || commentText.contains(
"fallthrough"_L1, Qt::CaseInsensitive)) {
418 "Non-empty case block potentially falls through to the next case or default statement. "
419 "Add \"// fallthrough\" at the end of the block to silence this warning."_L1,
420 qmlUnterminatedCase, errorLoc);
423bool LinterVisitor::
visit(QQmlJS::AST::CaseBlock *block)
425 QQmlJSImportVisitor::visit(block);
427 std::vector<std::pair<SourceLocation, StatementList *>> clauses;
428 for (CaseClauses *it = block->clauses; it; it = it->next)
429 clauses.push_back({ it->clause->caseToken, it->clause->statements });
430 if (block->defaultClause)
431 clauses.push_back({ block->defaultClause->defaultToken, block->defaultClause->statements });
432 for (CaseClauses *it = block->moreClauses; it; it = it->next)
433 clauses.push_back({ it->clause->caseToken, it->clause->statements });
436 for (size_t i = 0; i < clauses.size() - 1; ++i) {
437 const SourceLocation nextToken = clauses[i + 1].first;
438 checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
446 QList<
const Statement *> lasts;
448 for (
const auto *clause = ast->clauses; clause; clause = clause->next)
449 lasts << possibleLastStatements(clause->clause->statements);
450 if (ast->defaultClause)
451 lasts << possibleLastStatements(ast->defaultClause->statements);
452 for (
const auto *clause = ast->moreClauses; clause; clause = clause->next)
453 lasts << possibleLastStatements(clause->clause->statements);
460 if (
const auto *s = cast<
const Block *>(ast))
461 return possibleLastStatements(s->statements) << s;
462 if (
const auto *s = cast<
const BreakStatement *>(ast))
464 if (
const auto *s = cast<
const ContinueStatement *>(ast))
466 if (
const auto *s = cast<
const DebuggerStatement *>(ast))
468 if (
const auto *s = cast<
const DoWhileStatement *>(ast))
469 return possibleLastStatements(s->statement) << s;
470 if (
const auto *s = cast<
const EmptyStatement *>(ast))
472 if (
const auto *s = cast<
const ExportDeclaration *>(ast))
474 if (
const auto *s = cast<
const ExpressionStatement *>(ast))
476 if (
const auto *s = cast<
const ForEachStatement *>(ast))
477 return possibleLastStatements(s->statement) << s;
478 if (
const auto *s = cast<
const ForStatement *>(ast))
479 return possibleLastStatements(s->statement) << s;
480 if (
const auto *s = cast<
const IfStatement *>(ast)) {
481 auto lasts = possibleLastStatements(s->ok);
483 lasts << possibleLastStatements(s->ko);
486 if (
const auto *s = cast<
const ImportDeclaration *>(ast))
488 if (
const auto *s = cast<
const LabelledStatement *>(ast))
489 return possibleLastStatements(s->statement) << ast;
490 if (
const auto *s = cast<
const ReturnStatement *>(ast))
492 if (
const auto *s = cast<
const SwitchStatement *>(ast))
493 return possibleLastStatements(s->block) << s;
494 if (
const auto *s = cast<
const ThrowStatement *>(ast))
496 if (
const auto *s = cast<
const TryStatement *>(ast))
498 if (
const auto *s = cast<
const VariableStatement *>(ast))
500 if (
const auto *s = cast<
const WhileStatement *>(ast))
501 return possibleLastStatements(s->statement) << s;
502 if (
const auto *s = cast<
const WithStatement *>(ast))
503 return possibleLastStatements(s->statement) << s;
505 Q_UNREACHABLE_RETURN({});
511 for (; ast->next; ast = ast->next) { }
512 const auto *statement = ast->statement;
515 if (cast<
const FunctionDeclaration *>(statement))
518 return possibleLastStatements(
static_cast<
const Statement *>(statement));
524 case Node::Kind_CallExpression:
525 case Node::Kind_DeleteExpression:
526 case Node::Kind_NewExpression:
527 case Node::Kind_PreDecrementExpression:
528 case Node::Kind_PreIncrementExpression:
529 case Node::Kind_PostDecrementExpression:
530 case Node::Kind_PostIncrementExpression:
531 case Node::Kind_YieldExpression:
532 case Node::Kind_FunctionExpression:
534 case Node::Kind_NumericLiteral:
535 case Node::Kind_StringLiteral:
536 case Node::Kind_FalseLiteral:
537 case Node::Kind_TrueLiteral:
538 case Node::Kind_NullExpression:
539 case Node::Kind_Undefined:
540 case Node::Kind_RegExpLiteral:
541 case Node::Kind_SuperLiteral:
542 case Node::Kind_ThisExpression:
543 case Node::Kind_FieldMemberExpression:
544 case Node::Kind_IdentifierExpression:
545 case Node::Kind_TypeOfExpression:
551 if (
const auto *e = cast<
const NestedExpression *>(ast))
552 return isUselessExpressionStatement_impl(e->expression);
553 if (
const auto *e = cast<
const NotExpression *>(ast))
554 return isUselessExpressionStatement_impl(e->expression);
555 if (
const auto *e = cast<
const TildeExpression *>(ast))
556 return isUselessExpressionStatement_impl(e->expression);
557 if (
const auto *e = cast<
const UnaryMinusExpression *>(ast))
558 return isUselessExpressionStatement_impl(e->expression);
559 if (
const auto *e = cast<
const UnaryPlusExpression *>(ast))
560 return isUselessExpressionStatement_impl(e->expression);
561 if (
const auto *e = cast<
const ConditionalExpression *>(ast))
562 return isUselessExpressionStatement_impl(e->ok) && isUselessExpressionStatement_impl(e->ko);
564 if (
const BinaryExpression *binary = cast<
const BinaryExpression *>(ast)) {
565 switch (binary->op) {
566 case QSOperator::InplaceAnd:
567 case QSOperator::Assign:
568 case QSOperator::InplaceSub:
569 case QSOperator::InplaceDiv:
570 case QSOperator::InplaceExp:
571 case QSOperator::InplaceAdd:
572 case QSOperator::InplaceLeftShift:
573 case QSOperator::InplaceMod:
574 case QSOperator::InplaceMul:
575 case QSOperator::InplaceOr:
576 case QSOperator::InplaceRightShift:
577 case QSOperator::InplaceURightShift:
578 case QSOperator::InplaceXor:
581 return isUselessExpressionStatement_impl(binary->left)
582 && isUselessExpressionStatement_impl(binary->right);
590
591
592
593
596 return isUselessExpressionStatement_impl(ast->expression);
599void LinterVisitor::handleUselessExpressionStatement(
const ExpressionStatement *ast)
602 const auto it = std::find_if(m_ancestryIncludingCurrentNode.crbegin(),
603 m_ancestryIncludingCurrentNode.crend(),
605 return it->kind == Node::Kind_UiPublicMember
606 || it->kind == Node::Kind_FunctionDeclaration
607 || it->kind == Node::Kind_UiScriptBinding;
610 if (it == m_ancestryIncludingCurrentNode.crend())
614 const auto isLastExprStat = [](
const ExpressionStatement *statement,
const Statement *base) {
615 const auto lasts = possibleLastStatements(base);
616 return lasts.contains(statement);
619 if (
const auto *usb = cast<UiScriptBinding *>(*it); usb && usb->qualifiedId) {
620 if (usb->qualifiedId->toString() ==
"id"_L1)
622 if (usb->qualifiedId->next)
624 if (m_savedBindingOuterScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope)
627 QQmlJSScope::Ptr object = m_currentScope;
628 while (object && object->scopeType() != QQmlSA::ScopeType::QMLScope)
629 object = object->parentScope();
634 if (m_propertyBindings.contains(object)) {
635 for (
const auto &entry : m_propertyBindings[object]) {
636 if (entry.data == usb->qualifiedId->toString()) {
637 if (isLastExprStat(ast, usb->statement))
646 const auto *upm = cast<
const UiPublicMember *>(*it);
647 if (upm && upm->type == AST::UiPublicMember::Property && upm->statement) {
648 if (isLastExprStat(ast, upm->statement))
652 if (isUselessExpressionStatement(ast)) {
653 m_logger->log(
"Expression statement has no obvious effect."_L1,
654 qmlConfusingExpressionStatement,
655 combine(ast->firstSourceLocation(), ast->lastSourceLocation()));
659bool LinterVisitor::
visit(ExpressionStatement *ast)
661 QQmlJSImportVisitor::visit(ast);
662 handleUselessExpressionStatement(ast);
666bool LinterVisitor::
safeInsertJSIdentifier(QQmlJSScope::Ptr &scope,
const QString &name,
const QQmlJSScope::JavaScriptIdentifier &identifier)
668 if (scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope &&
669 identifier.kind == QQmlJSScope::JavaScriptIdentifier::FunctionScoped) {
672 Q_ASSERT(!scope->parentScope().isNull());
673 auto parentScopeType = scope->parentScope()->scopeType();
674 bool inTopLevelBindingBlockScope = parentScopeType == QQmlSA::ScopeType::BindingFunctionScope
675 || parentScopeType == QQmlSA::ScopeType::SignalHandlerFunctionScope;
676 if (!inTopLevelBindingBlockScope) {
677 m_logger->log(u"var declaration in block scope is hoisted to function scope\n"_s
678 u"Replace it with const or let to silence the warning\n"_s,
679 qmlBlockScopeVarDeclaration, identifier.location);
681 }
else if (scope->scopeType() == QQmlSA::ScopeType::QMLScope) {
682 const QQmlJSScope *scopePtr = scope.get();
683 std::pair<
const QQmlJSScope*, QString> misplaced { scopePtr, name };
684 if (misplacedJSIdentifiers.contains(misplaced))
686 misplacedJSIdentifiers.insert(misplaced);
687 m_logger->log(u"JavaScript declarations are not allowed in QML elements"_s, qmlSyntax,
688 identifier.location);
691 return QQmlJSImportVisitor::safeInsertJSIdentifier(scope, name, identifier);
695 const QString &name,
const QQmlJS::AST::Statement *statement,
696 const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition)
698 if (statement && statement->kind == (
int)AST::Node::Kind::Kind_Block) {
699 const auto *block =
static_cast<
const AST::Block *>(statement);
700 if (!block->statements && associatedPropertyDefinition) {
701 m_logger->log(
"Unintentional empty block, use ({}) for empty object literal"_L1,
702 qmlUnintentionalEmptyBlock,
703 combine(block->lbraceToken, block->rbraceToken));
707 return QQmlJSImportVisitor::parseBindingExpression(name, statement, associatedPropertyDefinition);
711 const UiPublicMember *associatedPropertyDefinition)
713 if (!m_currentScope->hasOwnProperty(binding.propertyName()))
716 if (!associatedPropertyDefinition->isReadonly())
719 const auto &prop = m_currentScope->property(binding.propertyName());
720 const auto log = [&](
const QString &preferredType) {
721 m_logger->log(
"Prefer more specific type %1 over var"_L1.arg(preferredType),
722 qmlPreferNonVarProperties, prop.sourceLocation());
725 if (prop.typeName() !=
"QVariant"_L1)
728 switch (binding.bindingType()) {
729 case QQmlSA::BindingType::BoolLiteral: {
733 case QQmlSA::BindingType::NumberLiteral: {
734 double v = binding.numberValue();
735 auto loc = binding.sourceLocation();
736 QStringView literal = QStringView(m_engine->code()).mid(loc.offset, loc.length);
737 if (literal.contains(u'.') ||
double(
int(v)) != v)
738 log(
"real or double"_L1);
743 case QQmlSA::BindingType::StringLiteral: {
753bool LinterVisitor::
visit(UiProgram *ast)
755 const bool result = QQmlJSImportVisitor::visit(ast);
757 m_renamedComponents.setScopeToName(&m_rootScopeImports.names());
764 QQmlJSImportVisitor::endVisit(ast);
765 checkFileSelections();
776 const QQmlJS::SourceLocation &location,
777 QQmlJSLogger *logger)
780 if (!base->hasMethod(name))
783 static constexpr QLatin1String warningMessage =
784 "%1 \"%2\" already exists in base type \"%3\", use a different name."_L1;
785 const auto owner = QQmlJSScope::ownerOfMethod(base, name).scope;
786 const bool isSignal = owner->methods(name).front().methodType() == QQmlJSMetaMethodType::Signal;
787 logger->log(warningMessage.arg(isSignal ?
"Signal"_L1 :
"Method"_L1, name,
788 QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
789 qmlShadow, location);
793 const QQmlJS::SourceLocation &location,
794 OverrideInformations overrideFlags, QQmlJSLogger *logger)
797 const bool hasOverride = overrideFlags.testFlag(
WithOverride);
798 if (!base->hasProperty(name)) {
802 "Member \"%1\" does not override anything. Consider removing \"override\"."_L1.arg(
804 qmlPropertyOverride, location);
808 const auto owner = QQmlJSScope::ownerOfProperty(base, name).scope;
809 const auto shadowedProperty = owner->ownProperty(name);
810 if (shadowedProperty.isFinal()) {
813 ?
"Member \"%1\" shadows final member \"%1\" from base type \"%2\", use a different name."_L1
814 :
"Member \"%1\" overrides final member \"%1\" from base type \"%2\", use a different name and remove the \"override\"."_L1)
815 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
816 qmlPropertyOverride, location);
820 if (shadowedProperty.isVirtual() || shadowedProperty.isOverride()) {
821 if (hasOverride || overrideFlags.testFlag(
WithFinal))
825 "Member \"%1\" shadows member \"%1\" from base type \"%2\", use a different name or add a final or override specifier."_L1
826 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
827 qmlPropertyOverride, location);
833 "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
834 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
835 qmlPropertyOverride, location);
838 logger->log(
"Property \"%2\" already exists in base type \"%3\", use a different name."_L1.arg(
839 name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
840 qmlPropertyOverride, location);
844 QLatin1String type,
const QQmlJS::SourceLocation &location,
845 OverrideInformations overrideFlags, QQmlJSLogger *logger)
847 static constexpr QLatin1String duplicateMessage =
848 "Duplicated %1 name \"%2\", \"%2\" is already a %3."_L1;
849 if (
const auto methods = scope->ownMethods(name); !methods.isEmpty()) {
850 logger->log(duplicateMessage.arg(type, name,
851 methods.front().methodType() == QQmlSA::MethodType::Signal
854 qmlDuplicatedName, location);
856 if (scope->hasOwnProperty(name))
857 logger->log(duplicateMessage.arg(type, name, s_property), qmlDuplicatedName, location);
859 const QQmlJSScope::ConstPtr base = scope->baseType();
863 warnForMethodShadowingInBase(base, name, location, logger);
864 warnForPropertyShadowingInBase(base, name, location, overrideFlags, logger);
867void LinterVisitor::handleRenamedType(UiQualifiedId *qualifiedId)
869 m_renamedComponents.handleRenamedType(
870 m_rootScopeImports.type(qualifiedId->name.toString()).scope, qualifiedId->name,
871 qualifiedId->identifierToken, m_logger);
874bool LinterVisitor::
visit(Type *type)
876 const bool result = QQmlJSImportVisitor::visit(type);
878 handleRenamedType(type->typeId);
883void LinterVisitor::handleRecursivelyInstantiatedType(UiQualifiedId *qualifiedId)
886 if (qualifiedId->next)
889 auto logWarning = [&qualifiedId,
this]() {
890 m_logger->log(
"Type \"%1\" can't be instantiated recursively"_L1.arg(qualifiedId->name),
891 qmlTypeInstantiatedRecursively, qualifiedId->identifierToken);
894 const QString name = qualifiedId->name.toString();
895 if (m_rootScopeImports.names().contains(m_exportedRootScope, name))
899 if (
const auto inlineComponentName = std::get_if<InlineComponentNameType>(&m_currentRootName);
900 inlineComponentName && name == *inlineComponentName) {
905bool LinterVisitor::
visit(QQmlJS::AST::UiPragma *pragma)
907 const bool result = QQmlJSImportVisitor::visit(pragma);
910 if (pragma->name == u"Singleton")
911 m_rootIsSingleton =
true;
916void LinterVisitor::checkSingletonRoot()
918 const bool hasQmldirSingletonEntry = m_exportedRootScope->isSingleton();
919 const bool hasSingletonPragma = m_rootIsSingleton;
921 if (hasQmldirSingletonEntry == hasSingletonPragma)
924 if (hasQmldirSingletonEntry && !hasSingletonPragma) {
925 m_logger->log(
"Type %1 declared as singleton in qmldir but missing pragma Singleton"_L1.arg(
926 m_exportedRootScope->internalName()),
927 qmlImport, QQmlJS::SourceLocation());
930 Q_ASSERT(!hasQmldirSingletonEntry && hasSingletonPragma);
931 m_logger->log(
"Type %1 not declared as singleton in qmldir but using pragma Singleton"_L1.arg(
932 m_exportedRootScope->internalName()),
933 qmlImport, QQmlJS::SourceLocation());
936bool LinterVisitor::
visit(QQmlJS::AST::UiObjectDefinition *objectDefinition)
938 handleRenamedType(objectDefinition->qualifiedTypeNameId);
939 handleRecursivelyInstantiatedType(objectDefinition->qualifiedTypeNameId);
940 if (!rootScopeIsValid() && !objectDefinition->qualifiedTypeNameId->name.front().isLower())
941 checkSingletonRoot();
943 return QQmlJSImportVisitor::visit(objectDefinition);
946bool LinterVisitor::
visit(UiPublicMember *publicMember)
948 switch (publicMember->type) {
949 case UiPublicMember::Signal: {
950 const QString signalName = publicMember->name.toString();
951 warnForDuplicates(m_currentScope, signalName, s_signal, publicMember->identifierToken,
955 case QQmlJS::AST::UiPublicMember::Property: {
956 const QString propertyName = publicMember->name.toString();
957 OverrideInformations flags;
958 flags.setFlag(
WithOverride, publicMember->isOverride());
959 flags.setFlag(
WithFinal, publicMember->isFinal());
960 warnForDuplicates(m_currentScope, propertyName, s_property, publicMember->identifierToken,
962 handleRenamedType(publicMember->memberType);
966 return QQmlJSImportVisitor::visit(publicMember);
969bool LinterVisitor::
visit(FunctionExpression *fexpr)
971 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
972 warnForDuplicates(m_currentScope, fexpr->name.toString(), s_method, fexpr->identifierToken,
975 return QQmlJSImportVisitor::visit(fexpr);
978bool LinterVisitor::
visit(FunctionDeclaration *fdecl)
980 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
981 warnForDuplicates(m_currentScope, fdecl->name.toString(), s_method, fdecl->identifierToken,
984 return QQmlJSImportVisitor::visit(fdecl);
988
989
991 const QQmlJSScope::ConstPtr &scope2)
993 for (
const auto &[propertyName, prop] : scope1->properties().asKeyValueRange())
994 if (!scope2->hasProperty(propertyName))
996 for (
const auto &[methodName, method] : scope1->methods().asKeyValueRange())
997 if (!scope2->hasMethod(methodName))
1003void LinterVisitor::checkFileSelections()
1005 const QQmlJS::FileSelectorInfo info =
1006 m_rootScopeImports.contextualTypes().fileSelectorInfoFor(m_exportedRootScope);
1008 if (info.fileSelectedTypes.isEmpty() || info.mainType.isNull())
1011 const QString name = m_rootScopeImports.name(m_exportedRootScope);
1013 if (info.mainType == m_exportedRootScope) {
1015 for (
const auto &fileSelected : info.fileSelectedTypes) {
1016 if (compatibilityHeuristicForFileSelector(m_exportedRootScope,
1017 fileSelected.type.scope)) {
1019 "Type %1 is ambiguous due to file selector usage, ignoring %2."_L1.arg(
1020 name, fileSelected.type.scope->filePath()),
1021 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1024 m_logger->log(
"Type %1 has a potentially incompatible file-selected variant %2."_L1.arg(
1025 name, fileSelected.type.scope->filePath()),
1026 qmlImport, m_exportedRootScope->sourceLocation());
1032 if (compatibilityHeuristicForFileSelector(info.mainType, m_exportedRootScope)) {
1034 "File-selected type %1 is ambiguous due to file selector usage, this file will be ignored in favour of %2."_L1
1035 .arg(name, info.mainType->filePath()),
1036 qmlImportFileSelector, m_exportedRootScope->sourceLocation());
1039 m_logger->log(
"File-selected type %1 is potentially incompatible with %2."_L1.arg(
1040 name, info.mainType->filePath()),
1041 qmlImport, m_exportedRootScope->sourceLocation());
void postVisit(QQmlJS::AST::Node *) 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 leaveEnvironment() override
bool safeInsertJSIdentifier(QQmlJSScope::Ptr &scope, const QString &name, const QQmlJSScope::JavaScriptIdentifier &identifier) override
void endVisit(QQmlJS::AST::UiProgram *ast) override
bool visit(QQmlJS::AST::StringLiteral *) override
QQmlJS::AST::Node * astParentOfVisitedNode() const
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 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.