Revision control
++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qqmldomformatdirectivescanner_p.h"
++
++ #include <QtCore/QLoggingCategory>
++
++ QT_BEGIN_NAMESPACE
++ namespace QmlFormat {
++
++ static constexpr auto directive = QLatin1StringView("qmlformat");
++ static constexpr auto onCmd = QLatin1StringView("on");
++ static constexpr auto offCmd = QLatin1StringView("off");
++ static constexpr auto commentStartString = QLatin1StringView("//");
++
++ // Newline will be detected from document content
++ static QLatin1StringView detectNewLine(QStringView code) {
++ // Look for first newline in document
++ qsizetype idx = code.indexOf(u'\n');
++ if (idx > 0 && code[idx - 1] == u'\r')
++ return QLatin1StringView("\r\n");
++
++ return QLatin1StringView("\n");
++ }
++
++ static quint32 findLineStartOffset(QStringView m_code, qsizetype pos, QLatin1StringView newLine)
++ {
++ const auto startOffset = m_code.lastIndexOf(newLine, pos);
++ // If not found, return start of document
++ if (startOffset == -1)
++ return 0;
++ return quint32(startOffset + newLine.length());
++ }
++
++ static quint32 findDirectiveEndOffset(QStringView m_code, qsizetype offset, QLatin1StringView newLine)
++ {
++ auto endOffset = m_code.indexOf(onCmd, offset) + onCmd.size();
++ if (endOffset < onCmd.size())
++ endOffset = m_code.size();
++
++ // Also contain up to two newlines following the directive
++ int newlineCount = 0;
++ while (endOffset + newLine.size() <= m_code.size() &&
++ m_code.mid(endOffset, newLine.size()) == newLine && newlineCount < 2) {
++ endOffset += newLine.size();
++ ++newlineCount;
++ }
++ return quint32(endOffset);
++ }
++
++ static QQmlJS::SourceLocation formRegion(QStringView code, const QQmlJS::SourceLocation &off,
++ const QQmlJS::SourceLocation &on, QLatin1StringView newline)
++ {
++ const auto startOffset = findLineStartOffset(code, off.offset, newline);
++ const auto endOffset = findDirectiveEndOffset(code, on.offset, newline);
++ return QQmlJS::SourceLocation{ startOffset, endOffset - startOffset, 0, 0 };
++ }
++
++ static QStringView commentLineFromLoc(QStringView code, const QQmlJS::SourceLocation &loc, QLatin1StringView newline)
++ {
++ const auto lineStartOffset = findLineStartOffset(code, loc.offset, newline);
++ return code.mid(lineStartOffset, loc.offset - lineStartOffset + loc.length);
++ };
++
++ DisabledRegions identifyDisabledRegions(QStringView code, const QList<QQmlJS::SourceLocation> &comments)
++ {
++ const auto newline = detectNewLine(code);
++ const auto isOffDirective = [&code, newline](const QQmlJS::SourceLocation &loc) {
++ const auto commentLineString = commentLineFromLoc(code, loc, newline);
++ const auto directive = directiveFromComment(commentLineString);
++ return directive && *directive == Directive::Off;
++ };
++
++ const auto isOnDirective = [&code, newline](const QQmlJS::SourceLocation &loc) {
++ const auto commentLineString = commentLineFromLoc(code, loc, newline);
++ const auto directive = directiveFromComment(commentLineString);
++ return directive && *directive == Directive::On;
++ };
++
++ DisabledRegions result;
++ auto it = comments.begin();
++ const auto end = comments.end();
++
++ while (it != end) {
++ // 1. Find the next "formatter-off" directive
++ it = std::find_if(it, end, isOffDirective);
++
++ if (it == end) break;
++ const auto& off = *it;
++
++ // 2. Find the next "formatter-on" directive
++ it = std::find_if(it, end, isOnDirective);
++
++ // 3. form region
++ if (it == end) {
++ // No corresponding "on"
++ auto &&on = QQmlJS::SourceLocation(code.size(), 0, 0, 0);
++ result.insert(
++ off.offset,
++ formRegion(code, off, std::move(on), newline));
++ break;
++ }
++ const auto& on = *it;
++ result.insert(off.offset, formRegion(code, off, on, newline));
++ }
++
++ return result;
++ }
++
++
++
++ std::optional<Directive> directiveFromComment(QStringView commentLine)
++ {
++ // Split into words (directive and command)
++ // It should be on its own line,
++ const auto words = commentLine.trimmed().split(u' ', Qt::SkipEmptyParts);
++ if (words.size() != 3)
++ return std::nullopt;
++
++ // Validate directive
++ if (words.at(0) != commentStartString || words.at(1) != directive)
++ return std::nullopt;
++
++ return (words.at(2) == onCmd) ? std::make_optional(Directive::On)
++ : (words.at(2) == offCmd) ? std::make_optional(Directive::Off)
++ : std::nullopt;
++ }
++
++ } // namespace QmlFormat
++
++ QT_END_NAMESPACE