1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
// 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
|