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
qqmldomformatdirectivescanner.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/QLoggingCategory>
7
9namespace QmlFormat {
10
11static constexpr auto directive = QLatin1StringView("qmlformat");
12static constexpr auto onCmd = QLatin1StringView("on");
13static constexpr auto offCmd = QLatin1StringView("off");
14static constexpr auto commentStartString = QLatin1StringView("//");
15
16// Newline will be detected from document content
17static QLatin1StringView detectNewLine(QStringView code) {
18 // Look for first newline in document
19 qsizetype idx = code.indexOf(u'\n');
20 if (idx > 0 && code[idx - 1] == u'\r')
21 return QLatin1StringView("\r\n");
22
23 return QLatin1StringView("\n");
24}
25
26static quint32 findLineStartOffset(QStringView m_code, qsizetype pos, QLatin1StringView newLine)
27{
28 const auto startOffset = m_code.lastIndexOf(newLine, pos);
29 // If not found, return start of document
30 if (startOffset == -1)
31 return 0;
32 return quint32(startOffset + newLine.length());
33}
34
35static quint32 findDirectiveEndOffset(QStringView m_code, qsizetype offset, QLatin1StringView newLine)
36{
37 auto endOffset = m_code.indexOf(onCmd, offset) + onCmd.size();
38 if (endOffset < onCmd.size())
39 endOffset = m_code.size();
40
41 // Also contain up to two newlines following the directive
42 int newlineCount = 0;
43 while (endOffset + newLine.size() <= m_code.size() &&
44 m_code.mid(endOffset, newLine.size()) == newLine && newlineCount < 2) {
45 endOffset += newLine.size();
46 ++newlineCount;
47 }
48 return quint32(endOffset);
49}
50
51static QQmlJS::SourceLocation formRegion(QStringView code, const QQmlJS::SourceLocation &off,
52 const QQmlJS::SourceLocation &on, QLatin1StringView newline)
53{
54 const auto startOffset = findLineStartOffset(code, off.offset, newline);
55 const auto endOffset = findDirectiveEndOffset(code, on.offset, newline);
56 return QQmlJS::SourceLocation{ startOffset, endOffset - startOffset, 0, 0 };
57}
58
59static QStringView commentLineFromLoc(QStringView code, const QQmlJS::SourceLocation &loc, QLatin1StringView newline)
60{
61 const auto lineStartOffset = findLineStartOffset(code, loc.offset, newline);
62 return code.mid(lineStartOffset, loc.offset - lineStartOffset + loc.length);
63};
64
65DisabledRegions identifyDisabledRegions(QStringView code, const QList<QQmlJS::SourceLocation> &comments)
66{
67 const auto newline = detectNewLine(code);
68 const auto isOffDirective = [&code, newline](const QQmlJS::SourceLocation &loc) {
69 const auto commentLineString = commentLineFromLoc(code, loc, newline);
70 const auto directive = directiveFromComment(commentLineString);
71 return directive && *directive == Directive::Off;
72 };
73
74 const auto isOnDirective = [&code, newline](const QQmlJS::SourceLocation &loc) {
75 const auto commentLineString = commentLineFromLoc(code, loc, newline);
76 const auto directive = directiveFromComment(commentLineString);
77 return directive && *directive == Directive::On;
78 };
79
80 DisabledRegions result;
81 auto it = comments.begin();
82 const auto end = comments.end();
83
84 while (it != end) {
85 // 1. Find the next "formatter-off" directive
86 it = std::find_if(it, end, isOffDirective);
87
88 if (it == end) break;
89 const auto& off = *it;
90
91 // 2. Find the next "formatter-on" directive
92 it = std::find_if(it, end, isOnDirective);
93
94 // 3. form region
95 if (it == end) {
96 // No corresponding "on"
97 auto &&on = QQmlJS::SourceLocation(code.size(), 0, 0, 0);
98 result.insert(
99 off.offset,
100 formRegion(code, off, std::move(on), newline));
101 break;
102 }
103 const auto& on = *it;
104 result.insert(off.offset, formRegion(code, off, on, newline));
105 }
106
107 return result;
108}
109
110
111
112std::optional<Directive> directiveFromComment(QStringView commentLine)
113{
114 // Split into words (directive and command)
115 // It should be on its own line,
116 const auto words = commentLine.trimmed().split(u' ', Qt::SkipEmptyParts);
117 if (words.size() != 3)
118 return std::nullopt;
119
120 // Validate directive
121 if (words.at(0) != commentStartString || words.at(1) != directive)
122 return std::nullopt;
123
124 return (words.at(2) == onCmd) ? std::make_optional(Directive::On)
125 : (words.at(2) == offCmd) ? std::make_optional(Directive::Off)
126 : std::nullopt;
127}
128
129} // namespace QmlFormat
130
131QT_END_NAMESPACE
static constexpr auto directive
static QStringView commentLineFromLoc(QStringView code, const QQmlJS::SourceLocation &loc, QLatin1StringView newline)
static constexpr auto onCmd
DisabledRegions identifyDisabledRegions(QStringView code, const QList< QQmlJS::SourceLocation > &comments)
static constexpr auto offCmd
std::optional< Directive > directiveFromComment(QStringView commentLine)
static QQmlJS::SourceLocation formRegion(QStringView code, const QQmlJS::SourceLocation &off, const QQmlJS::SourceLocation &on, QLatin1StringView newline)
static constexpr auto commentStartString
static quint32 findDirectiveEndOffset(QStringView m_code, qsizetype offset, QLatin1StringView newLine)
static quint32 findLineStartOffset(QStringView m_code, qsizetype pos, QLatin1StringView newLine)
static QLatin1StringView detectNewLine(QStringView code)