// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljslintervisitor_p.h" QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; using namespace QQmlJS::AST; namespace QQmlJS { /*! \internal \class QQmlJS::LinterVisitor Extends QQmlJSImportVisitor with extra warnings that are required for linting but unrelated to QQmlJSImportVisitor actual task that is constructing QQmlJSScopes. One example of such warnings are purely syntactic checks, or style-checks warnings that don't make sense during compilation. */ bool LinterVisitor::visit(StringLiteral *sl) { QQmlJSImportVisitor::visit(sl); const QString s = m_logger->code().mid(sl->literalToken.begin(), sl->literalToken.length); if (s.contains(QLatin1Char('\r')) || s.contains(QLatin1Char('\n')) || s.contains(QChar(0x2028u)) || s.contains(QChar(0x2029u))) { QString templateString; bool escaped = false; const QChar stringQuote = s[0]; for (qsizetype i = 1; i < s.size() - 1; i++) { const QChar c = s[i]; if (c == u'\\') { escaped = !escaped; } else if (escaped) { // If we encounter an escaped quote, unescape it since we use backticks here if (c == stringQuote) templateString.chop(1); escaped = false; } else { if (c == u'`') templateString += u'\\'; if (c == u'$' && i + 1 < s.size() - 1 && s[i + 1] == u'{') templateString += u'\\'; } templateString += c; } QQmlJSFixSuggestion suggestion = { "Use a template literal instead."_L1, sl->literalToken, u"`" % templateString % u"`" }; suggestion.setAutoApplicable(); m_logger->log(QStringLiteral("String contains unescaped line terminator which is " "deprecated."), qmlMultilineStrings, sl->literalToken, true, true, suggestion); } return true; } bool LinterVisitor::preVisit(Node *n) { m_ancestryIncludingCurrentNode.push_back(n); return true; } void LinterVisitor::postVisit(Node *n) { Q_ASSERT(m_ancestryIncludingCurrentNode.back() == n); m_ancestryIncludingCurrentNode.pop_back(); } Node *LinterVisitor::astParentOfVisitedNode() const { if (m_ancestryIncludingCurrentNode.size() < 2) return nullptr; return m_ancestryIncludingCurrentNode[m_ancestryIncludingCurrentNode.size() - 2]; } } // namespace QQmlJS QT_END_NAMESPACE