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 QQmlJSFixSuggestion suggestion = {
"Use a template literal instead."_L1, sl->literalToken,
87 u"`" % templateString % u"`" };
88 suggestion.setAutoApplicable();
89 m_logger->log(QStringLiteral(
"String contains unescaped line terminator which is "
91 qmlMultilineStrings, sl->literalToken,
true,
true, suggestion);
98 m_ancestryIncludingCurrentNode.push_back(n);
104 Q_ASSERT(m_ancestryIncludingCurrentNode.back() == n);
105 m_ancestryIncludingCurrentNode.pop_back();
110 if (m_ancestryIncludingCurrentNode.size() < 2)
112 return m_ancestryIncludingCurrentNode[m_ancestryIncludingCurrentNode.size() - 2];
115bool LinterVisitor::
visit(CommaExpression *expression)
117 QQmlJSImportVisitor::visit(expression);
118 if (!expression->left || !expression->right)
122 if (cast<ForStatement *>(astParentOfVisitedNode()))
125 m_logger->log(
"Do not use comma expressions."_L1, qmlComma, expression->commaToken);
131 static constexpr std::array literals{
"Boolean"_L1,
"Function"_L1,
"JSON"_L1,
132 "Math"_L1,
"Number"_L1,
"String"_L1 };
134 const IdentifierExpression *identifier = cast<IdentifierExpression *>(expression->base);
138 if (std::find(literals.cbegin(), literals.cend(), identifier->name) != literals.cend()) {
139 logger->log(
"Do not use '%1' as a constructor."_L1.arg(identifier->name),
140 qmlLiteralConstructor, identifier->identifierToken);
142 if (identifier->name ==
"Array"_L1 && expression->arguments && expression->arguments->next) {
143 const auto fullRange = combine(expression->newToken, expression->rparenToken);
144 const auto parensRange = combine(expression->lparenToken, expression->rparenToken);
145 const auto parens = QStringView(logger->code()).mid(parensRange.offset, parensRange.length);
146 const auto insideParens = parens.mid(1, parens.length() - 2);
147 const QString newCode = u'[' + insideParens + u']';
148 QQmlJSFixSuggestion fix(
"Replace with array literal"_L1, fullRange, newCode);
149 fix.setAutoApplicable(
true);
150 logger->log(
"Array has confusing semantics, use an array literal ([]) instead."_L1,
151 qmlLiteralConstructor, identifier->identifierToken,
true,
true, fix);
155bool LinterVisitor::
visit(NewMemberExpression *expression)
157 QQmlJSImportVisitor::visit(expression);
158 warnAboutLiteralConstructors(expression, m_logger);
162bool LinterVisitor::
visit(VoidExpression *ast)
164 QQmlJSImportVisitor::visit(ast);
165 m_logger->log(
"Do not use void expressions."_L1, qmlVoid, ast->voidToken);
171 Q_ASSERT(exp->op == QSOperator::Add);
173 SourceLocation location = exp->operatorToken;
176 if (
auto increment = cast<PostIncrementExpression *>(exp->left))
177 location = combine(increment->incrementToken, location);
179 if (
auto unary = cast<UnaryPlusExpression *>(exp->right))
180 location = combine(location, unary->plusToken);
182 if (
auto increment = cast<PreIncrementExpression *>(exp->right))
183 location = combine(location, increment->incrementToken);
185 if (location == exp->operatorToken)
186 return SourceLocation{};
193 Q_ASSERT(exp->op == QSOperator::Sub);
195 SourceLocation location = exp->operatorToken;
198 if (
auto decrement = cast<PostDecrementExpression *>(exp->left))
199 location = combine(decrement->decrementToken, location);
201 if (
auto unary = cast<UnaryMinusExpression *>(exp->right))
202 location = combine(location, unary->minusToken);
204 if (
auto decrement = cast<PreDecrementExpression *>(exp->right))
205 location = combine(location, decrement->decrementToken);
207 if (location == exp->operatorToken)
208 return SourceLocation{};
213bool LinterVisitor::
visit(BinaryExpression *exp)
215 QQmlJSImportVisitor::visit(exp);
217 case QSOperator::Add:
218 if (SourceLocation loc = confusingPluses(exp); loc.isValid())
219 m_logger->log(
"Confusing pluses."_L1, qmlConfusingPluses, loc);
221 case QSOperator::Sub:
222 if (SourceLocation loc = confusingMinuses(exp); loc.isValid())
223 m_logger->log(
"Confusing minuses."_L1, qmlConfusingMinuses, loc);
232bool LinterVisitor::
visit(QQmlJS::AST::UiImport *import)
234 QQmlJSImportVisitor::visit(import);
236 const auto locAndName = [](
const UiImport *i) {
238 return std::make_pair(i->fileNameToken, i->fileName.toString());
240 QQmlJS::SourceLocation l = i->importUri->firstSourceLocation();
241 if (i->importIdToken.isValid())
242 l = combine(l, i->importIdToken);
244 l = combine(l, i->version->minorToken);
246 l = combine(l, i->importUri->lastSourceLocation());
248 return std::make_pair(l, i->importUri->toString());
251 SeenImport i(import);
252 if (
const auto it = m_seenImports.constFind(i); it != m_seenImports.constEnd()) {
253 const auto locAndNameImport = locAndName(import);
254 const auto locAndNameSeen = locAndName(it->uiImport);
255 m_logger->log(
"Duplicate import '%1'"_L1.arg(locAndNameImport.second),
256 qmlDuplicateImport, locAndNameImport.first);
257 m_logger->log(
"Note: previous import '%1' here"_L1.arg(locAndNameSeen.second),
258 qmlDuplicateImport, locAndNameSeen.first,
true,
true, {},
259 locAndName(import).first.startLine);
262 m_seenImports.insert(i);
266void LinterVisitor::handleDuplicateEnums(UiEnumMemberList *members, QStringView key,
267 const QQmlJS::SourceLocation &location)
269 m_logger->log(u"Enum key '%1' has already been declared"_s.arg(key), qmlDuplicateEnumEntries,
271 for (
const auto *member = members; member; member = member->next) {
272 if (member->member.toString() == key) {
273 m_logger->log(u"Note: previous declaration of '%1' here"_s.arg(key),
274 qmlDuplicateEnumEntries, member->memberToken);
280bool LinterVisitor::
visit(QQmlJS::AST::UiEnumDeclaration *uied)
282 QQmlJSImportVisitor::visit(uied);
284 if (m_currentScope->isInlineComponent()) {
285 m_logger->log(u"Enums declared inside of inline component are ignored."_s,
286 qmlInlineComponentEnums, uied->firstSourceLocation());
287 }
else if (m_currentScope->componentRootStatus() == QQmlJSScope::IsComponentRoot::No
288 && !m_currentScope->isFileRootComponent()) {
289 m_logger->log(u"Enum declared outside the root element. It won't be accessible."_s,
290 qmlNonRootEnums, uied->firstSourceLocation());
293 QHash<QStringView,
const QQmlJS::AST::UiEnumMemberList *> seen;
294 for (
const auto *member = uied->members; member; member = member->next) {
295 QStringView key = member->member;
296 if (!key.front().isUpper()) {
297 m_logger->log(u"Enum keys should start with an uppercase."_s, qmlEnumKeyCase,
298 member->memberToken);
301 if (seen.contains(key))
302 handleDuplicateEnums(uied->members, key, member->memberToken);
304 seen[member->member] = member;
306 if (uied->name == key) {
307 m_logger->log(
"Enum entry should be named differently than the enum itself to avoid "
308 "confusion."_L1, qmlEnumEntryMatchesEnum, member->firstSourceLocation());
321 switch (statement->kind) {
322 case Node::Kind_Block: {
323 return allCodePathsReturnInsideCase(cast<Block *>(statement)->statements);
325 case Node::Kind_BreakStatement:
327 case Node::Kind_CaseBlock: {
328 const CaseBlock *caseBlock = cast<CaseBlock *>(statement);
329 if (caseBlock->defaultClause)
330 return allCodePathsReturnInsideCase(caseBlock->defaultClause);
331 return allCodePathsReturnInsideCase(caseBlock->clauses);
333 case Node::Kind_CaseClause:
334 return allCodePathsReturnInsideCase(cast<CaseClause *>(statement)->statements);
335 case Node::Kind_CaseClauses: {
336 for (CaseClauses *caseClauses = cast<CaseClauses *>(statement); caseClauses;
337 caseClauses = caseClauses->next) {
338 if (!allCodePathsReturnInsideCase(caseClauses->clause))
343 case Node::Kind_ContinueStatement:
347 case Node::Kind_DefaultClause:
348 return allCodePathsReturnInsideCase(cast<DefaultClause *>(statement)->statements);
349 case Node::Kind_IfStatement: {
350 const auto *ifStatement = cast<IfStatement *>(statement);
351 return allCodePathsReturnInsideCase(ifStatement->ok)
352 && allCodePathsReturnInsideCase(ifStatement->ko);
354 case Node::Kind_LabelledStatement:
355 return allCodePathsReturnInsideCase(cast<LabelledStatement *>(statement)->statement);
356 case Node::Kind_ReturnStatement:
358 case Node::Kind_StatementList: {
359 for (StatementList *list = cast<StatementList *>(statement); list; list = list->next) {
360 if (allCodePathsReturnInsideCase(list->statement))
365 case Node::Kind_SwitchStatement:
366 return allCodePathsReturnInsideCase(cast<SwitchStatement *>(statement)->block);
367 case Node::Kind_ThrowStatement:
369 case Node::Kind_TryStatement: {
370 auto *tryStatement = cast<TryStatement *>(statement);
371 if (allCodePathsReturnInsideCase(tryStatement->statement))
373 return allCodePathsReturnInsideCase(tryStatement->finallyExpression->statement);
375 case Node::Kind_WithStatement:
376 return allCodePathsReturnInsideCase(cast<WithStatement *>(statement)->statement);
383void LinterVisitor::checkCaseFallthrough(StatementList *statements, SourceLocation errorLoc,
384 SourceLocation nextLoc)
386 if (!statements || !nextLoc.isValid())
389 if (allCodePathsReturnInsideCase(statements))
392 quint32 afterLastStatement = 0;
393 for (StatementList *it = statements; it; it = it->next) {
395 afterLastStatement = it->statement->lastSourceLocation().end();
399 const auto &comments = m_engine->comments();
400 auto it = std::find_if(comments.cbegin(), comments.cend(),
401 [&](
auto c) {
return afterLastStatement < c.offset; });
402 auto end = std::find_if(it, comments.cend(),
403 [&](
auto c) {
return c.offset >= nextLoc.offset; });
405 for (; it != end; ++it) {
406 const QString &commentText = m_engine->code().mid(it->offset, it->length);
407 if (commentText.contains(
"fall through"_L1)
408 || commentText.contains(
"fall-through"_L1)
409 || commentText.contains(
"fallthrough"_L1)) {
415 "Non-empty case block potentially falls through to the next case or default statement. "
416 "Add \"// fallthrough\" at the end of the block to silence this warning."_L1,
417 qmlUnterminatedCase, errorLoc);
420bool LinterVisitor::
visit(QQmlJS::AST::CaseBlock *block)
422 QQmlJSImportVisitor::visit(block);
424 std::vector<std::pair<SourceLocation, StatementList *>> clauses;
425 for (CaseClauses *it = block->clauses; it; it = it->next)
426 clauses.push_back({ it->clause->caseToken, it->clause->statements });
427 if (block->defaultClause)
428 clauses.push_back({ block->defaultClause->defaultToken, block->defaultClause->statements });
429 for (CaseClauses *it = block->moreClauses; it; it = it->next)
430 clauses.push_back({ it->clause->caseToken, it->clause->statements });
433 for (size_t i = 0; i < clauses.size() - 1; ++i) {
434 const SourceLocation nextToken = clauses[i + 1].first;
435 checkCaseFallthrough(clauses[i].second, clauses[i].first, nextToken);
443 QList<
const Statement *> lasts;
445 for (
const auto *clause = ast->clauses; clause; clause = clause->next)
446 lasts << possibleLastStatements(clause->clause->statements);
447 if (ast->defaultClause)
448 lasts << possibleLastStatements(ast->defaultClause->statements);
449 for (
const auto *clause = ast->moreClauses; clause; clause = clause->next)
450 lasts << possibleLastStatements(clause->clause->statements);
457 if (
const auto *s = cast<
const Block *>(ast))
458 return possibleLastStatements(s->statements) << s;
459 if (
const auto *s = cast<
const BreakStatement *>(ast))
461 if (
const auto *s = cast<
const ContinueStatement *>(ast))
463 if (
const auto *s = cast<
const DebuggerStatement *>(ast))
465 if (
const auto *s = cast<
const DoWhileStatement *>(ast))
466 return possibleLastStatements(s->statement) << s;
467 if (
const auto *s = cast<
const EmptyStatement *>(ast))
469 if (
const auto *s = cast<
const ExportDeclaration *>(ast))
471 if (
const auto *s = cast<
const ExpressionStatement *>(ast))
473 if (
const auto *s = cast<
const ForEachStatement *>(ast))
474 return possibleLastStatements(s->statement) << s;
475 if (
const auto *s = cast<
const ForStatement *>(ast))
476 return possibleLastStatements(s->statement) << s;
477 if (
const auto *s = cast<
const IfStatement *>(ast)) {
478 auto lasts = possibleLastStatements(s->ok);
480 lasts << possibleLastStatements(s->ko);
483 if (
const auto *s = cast<
const ImportDeclaration *>(ast))
485 if (
const auto *s = cast<
const LabelledStatement *>(ast))
486 return possibleLastStatements(s->statement) << ast;
487 if (
const auto *s = cast<
const ReturnStatement *>(ast))
489 if (
const auto *s = cast<
const SwitchStatement *>(ast))
490 return possibleLastStatements(s->block) << s;
491 if (
const auto *s = cast<
const ThrowStatement *>(ast))
493 if (
const auto *s = cast<
const TryStatement *>(ast))
495 if (
const auto *s = cast<
const VariableStatement *>(ast))
497 if (
const auto *s = cast<
const WhileStatement *>(ast))
498 return possibleLastStatements(s->statement) << s;
499 if (
const auto *s = cast<
const WithStatement *>(ast))
500 return possibleLastStatements(s->statement) << s;
502 Q_UNREACHABLE_RETURN({});
508 for (; ast->next; ast = ast->next) { }
509 const auto *statement = ast->statement;
512 if (cast<
const FunctionDeclaration *>(statement))
515 return possibleLastStatements(
static_cast<
const Statement *>(statement));
521 case Node::Kind_CallExpression:
522 case Node::Kind_DeleteExpression:
523 case Node::Kind_NewExpression:
524 case Node::Kind_PreDecrementExpression:
525 case Node::Kind_PreIncrementExpression:
526 case Node::Kind_PostDecrementExpression:
527 case Node::Kind_PostIncrementExpression:
528 case Node::Kind_YieldExpression:
529 case Node::Kind_FunctionExpression:
531 case Node::Kind_NumericLiteral:
532 case Node::Kind_StringLiteral:
533 case Node::Kind_FalseLiteral:
534 case Node::Kind_TrueLiteral:
535 case Node::Kind_NullExpression:
536 case Node::Kind_Undefined:
537 case Node::Kind_RegExpLiteral:
538 case Node::Kind_SuperLiteral:
539 case Node::Kind_ThisExpression:
540 case Node::Kind_FieldMemberExpression:
541 case Node::Kind_IdentifierExpression:
542 case Node::Kind_TypeOfExpression:
548 if (
const auto *e = cast<
const NestedExpression *>(ast))
549 return isUselessExpressionStatement_impl(e->expression);
550 if (
const auto *e = cast<
const NotExpression *>(ast))
551 return isUselessExpressionStatement_impl(e->expression);
552 if (
const auto *e = cast<
const TildeExpression *>(ast))
553 return isUselessExpressionStatement_impl(e->expression);
554 if (
const auto *e = cast<
const UnaryMinusExpression *>(ast))
555 return isUselessExpressionStatement_impl(e->expression);
556 if (
const auto *e = cast<
const UnaryPlusExpression *>(ast))
557 return isUselessExpressionStatement_impl(e->expression);
558 if (
const auto *e = cast<
const ConditionalExpression *>(ast))
559 return isUselessExpressionStatement_impl(e->ok) && isUselessExpressionStatement_impl(e->ko);
561 if (
const BinaryExpression *binary = cast<
const BinaryExpression *>(ast)) {
562 switch (binary->op) {
563 case QSOperator::InplaceAnd:
564 case QSOperator::Assign:
565 case QSOperator::InplaceSub:
566 case QSOperator::InplaceDiv:
567 case QSOperator::InplaceExp:
568 case QSOperator::InplaceAdd:
569 case QSOperator::InplaceLeftShift:
570 case QSOperator::InplaceMod:
571 case QSOperator::InplaceMul:
572 case QSOperator::InplaceOr:
573 case QSOperator::InplaceRightShift:
574 case QSOperator::InplaceURightShift:
575 case QSOperator::InplaceXor:
578 return isUselessExpressionStatement_impl(binary->left)
579 && isUselessExpressionStatement_impl(binary->right);
587
588
589
590
593 return isUselessExpressionStatement_impl(ast->expression);
596void LinterVisitor::handleUselessExpressionStatement(
const ExpressionStatement *ast)
599 const auto it = std::find_if(m_ancestryIncludingCurrentNode.crbegin(),
600 m_ancestryIncludingCurrentNode.crend(),
602 return it->kind == Node::Kind_UiPublicMember
603 || it->kind == Node::Kind_FunctionDeclaration
604 || it->kind == Node::Kind_UiScriptBinding;
607 if (it == m_ancestryIncludingCurrentNode.crend())
611 const auto isLastExprStat = [](
const ExpressionStatement *statement,
const Statement *base) {
612 const auto lasts = possibleLastStatements(base);
613 return lasts.contains(statement);
616 if (
const auto *usb = cast<UiScriptBinding *>(*it); usb && usb->qualifiedId) {
617 if (usb->qualifiedId->toString() ==
"id"_L1)
619 if (usb->qualifiedId->next)
621 if (m_savedBindingOuterScope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope)
624 QQmlJSScope::Ptr object = m_currentScope;
625 while (object && object->scopeType() != QQmlSA::ScopeType::QMLScope)
626 object = object->parentScope();
631 if (m_propertyBindings.contains(object)) {
632 for (
const auto &entry : m_propertyBindings[object]) {
633 if (entry.data == usb->qualifiedId->toString()) {
634 if (isLastExprStat(ast, usb->statement))
643 const auto *upm = cast<
const UiPublicMember *>(*it);
644 if (upm && upm->type == AST::UiPublicMember::Property && upm->statement) {
645 if (isLastExprStat(ast, upm->statement))
649 if (isUselessExpressionStatement(ast)) {
650 m_logger->log(
"Expression statement has no obvious effect."_L1,
651 qmlConfusingExpressionStatement,
652 combine(ast->firstSourceLocation(), ast->lastSourceLocation()));
656bool LinterVisitor::
visit(ExpressionStatement *ast)
658 QQmlJSImportVisitor::visit(ast);
659 handleUselessExpressionStatement(ast);
663bool LinterVisitor::
safeInsertJSIdentifier(QQmlJSScope::Ptr &scope,
const QString &name,
const QQmlJSScope::JavaScriptIdentifier &identifier)
665 if (scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope &&
666 identifier.kind == QQmlJSScope::JavaScriptIdentifier::FunctionScoped) {
669 Q_ASSERT(!scope->parentScope().isNull());
670 auto parentScopeType = scope->parentScope()->scopeType();
671 bool inTopLevelBindingBlockScope = parentScopeType == QQmlSA::ScopeType::BindingFunctionScope
672 || parentScopeType == QQmlSA::ScopeType::SignalHandlerFunctionScope;
673 if (!inTopLevelBindingBlockScope) {
674 m_logger->log(u"var declaration in block scope is hoisted to function scope\n"_s
675 u"Replace it with const or let to silence the warning\n"_s,
676 qmlBlockScopeVarDeclaration, identifier.location);
678 }
else if (scope->scopeType() == QQmlSA::ScopeType::QMLScope) {
679 const QQmlJSScope *scopePtr = scope.get();
680 std::pair<
const QQmlJSScope*, QString> misplaced { scopePtr, name };
681 if (misplacedJSIdentifiers.contains(misplaced))
683 misplacedJSIdentifiers.insert(misplaced);
684 m_logger->log(u"JavaScript declarations are not allowed in QML elements"_s, qmlSyntax,
685 identifier.location);
688 return QQmlJSImportVisitor::safeInsertJSIdentifier(scope, name, identifier);
692 const QString &name,
const QQmlJS::AST::Statement *statement,
693 const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition)
695 if (statement && statement->kind == (
int)AST::Node::Kind::Kind_Block) {
696 const auto *block =
static_cast<
const AST::Block *>(statement);
697 if (!block->statements && associatedPropertyDefinition) {
698 m_logger->log(
"Unintentional empty block, use ({}) for empty object literal"_L1,
699 qmlUnintentionalEmptyBlock,
700 combine(block->lbraceToken, block->rbraceToken));
704 return QQmlJSImportVisitor::parseBindingExpression(name, statement, associatedPropertyDefinition);
708 const UiPublicMember *associatedPropertyDefinition)
710 if (!m_currentScope->hasOwnProperty(binding.propertyName()))
713 if (!associatedPropertyDefinition->isReadonly())
716 const auto &prop = m_currentScope->property(binding.propertyName());
717 const auto log = [&](
const QString &preferredType) {
718 m_logger->log(
"Prefer more specific type %1 over var"_L1.arg(preferredType),
719 qmlPreferNonVarProperties, prop.sourceLocation());
722 if (prop.typeName() !=
"QVariant"_L1)
725 switch (binding.bindingType()) {
726 case QQmlSA::BindingType::BoolLiteral: {
730 case QQmlSA::BindingType::NumberLiteral: {
731 double v = binding.numberValue();
732 auto loc = binding.sourceLocation();
733 QStringView literal = QStringView(m_engine->code()).mid(loc.offset, loc.length);
734 if (literal.contains(u'.') ||
double(
int(v)) != v)
735 log(
"real or double"_L1);
740 case QQmlSA::BindingType::StringLiteral: {
758 const QQmlJS::SourceLocation &location,
759 QQmlJSLogger *logger)
762 if (!base->hasMethod(name))
765 static constexpr QLatin1String warningMessage =
766 "%1 \"%2\" already exists in base type \"%3\", use a different name."_L1;
767 const auto owner = QQmlJSScope::ownerOfMethod(base, name).scope;
768 const bool isSignal = owner->methods(name).front().methodType() == QQmlJSMetaMethodType::Signal;
769 logger->log(warningMessage.arg(isSignal ?
"Signal"_L1 :
"Method"_L1, name,
770 QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
771 qmlShadow, location);
775 const QQmlJS::SourceLocation &location,
776 OverrideInformations overrideFlags, QQmlJSLogger *logger)
779 const bool hasOverride = overrideFlags.testFlag(
WithOverride);
780 if (!base->hasProperty(name)) {
784 "Member \"%1\" does not override anything. Consider removing \"override\"."_L1.arg(
786 qmlPropertyOverride, location);
790 const auto owner = QQmlJSScope::ownerOfProperty(base, name).scope;
791 const auto shadowedProperty = owner->ownProperty(name);
792 if (shadowedProperty.isFinal()) {
795 ?
"Member \"%1\" shadows final member \"%1\" from base type \"%2\", use a different name."_L1
796 :
"Member \"%1\" overrides final member \"%1\" from base type \"%2\", use a different name and remove the \"override\"."_L1)
797 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
798 qmlPropertyOverride, location);
802 if (shadowedProperty.isVirtual() || shadowedProperty.isOverride()) {
803 if (hasOverride || overrideFlags.testFlag(
WithFinal))
807 "Member \"%1\" shadows member \"%1\" from base type \"%2\", use a different name or add a final or override specifier."_L1
808 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
809 qmlPropertyOverride, location);
815 "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
816 .arg(name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
817 qmlPropertyOverride, location);
820 logger->log(
"Property \"%2\" already exists in base type \"%3\", use a different name."_L1.arg(
821 name, QQmlJSUtils::getScopeName(owner, QQmlSA::ScopeType::QMLScope)),
822 qmlPropertyOverride, location);
826 QLatin1String type,
const QQmlJS::SourceLocation &location,
827 OverrideInformations overrideFlags, QQmlJSLogger *logger)
829 static constexpr QLatin1String duplicateMessage =
830 "Duplicated %1 name \"%2\", \"%2\" is already a %3."_L1;
831 if (
const auto methods = scope->ownMethods(name); !methods.isEmpty()) {
832 logger->log(duplicateMessage.arg(type, name,
833 methods.front().methodType() == QQmlSA::MethodType::Signal
836 qmlDuplicatedName, location);
838 if (scope->hasOwnProperty(name))
839 logger->log(duplicateMessage.arg(type, name, s_property), qmlDuplicatedName, location);
841 const QQmlJSScope::ConstPtr base = scope->baseType();
845 warnForMethodShadowingInBase(base, name, location, logger);
846 warnForPropertyShadowingInBase(base, name, location, overrideFlags, logger);
849bool LinterVisitor::
visit(UiPublicMember *publicMember)
851 switch (publicMember->type) {
852 case UiPublicMember::Signal: {
853 const QString signalName = publicMember->name.toString();
854 warnForDuplicates(m_currentScope, signalName, s_signal, publicMember->identifierToken,
858 case QQmlJS::AST::UiPublicMember::Property: {
859 const QString propertyName = publicMember->name.toString();
860 OverrideInformations flags;
861 flags.setFlag(
WithOverride, publicMember->isOverride());
862 flags.setFlag(
WithFinal, publicMember->isFinal());
863 warnForDuplicates(m_currentScope, propertyName, s_property, publicMember->identifierToken,
868 return QQmlJSImportVisitor::visit(publicMember);
871bool LinterVisitor::
visit(FunctionExpression *fexpr)
873 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
874 warnForDuplicates(m_currentScope, fexpr->name.toString(), s_method, fexpr->identifierToken,
877 return QQmlJSImportVisitor::visit(fexpr);
880bool LinterVisitor::
visit(FunctionDeclaration *fdecl)
882 if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) {
883 warnForDuplicates(m_currentScope, fdecl->name.toString(), s_method, fdecl->identifierToken,
886 return QQmlJSImportVisitor::visit(fdecl);
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
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 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.