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
qqmldomoutwriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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
10
11#include <QtCore/QLoggingCategory>
12
13QT_BEGIN_NAMESPACE
14namespace QQmlJS {
15namespace Dom {
16
17using DisabledRegionIt = OutWriter::OffsetToDisabledRegionMap::const_iterator;
18
19static inline OutWriter::RegionToCommentMap extractComments(const DomItem &it)
20{
21 OutWriter::RegionToCommentMap comments;
22 if (const RegionComments *cRegionsPtr = it.field(Fields::comments).as<RegionComments>()) {
23 comments = cRegionsPtr->regionComments();
24 }
25 return comments;
26}
27
28/*
29\internal
30\brief Utility function to determine if two source locations overlap
31*/
32static inline bool overlaps(const SourceLocation &a, const SourceLocation &b)
33{
34 return a.isValid() && b.isValid() && (a.begin() < b.end() && b.begin() < a.end());
35}
36
37/*
38\internal
39\brief Utility function to determine the indent after skipping to format a piece of code
40*/
41static inline int indentAfterPartialFormatting(int initialIndent, QStringView code,
42 LineWriterOptions options)
43{
44 FormatTextStatus initialState = FormatTextStatus::initialStatus(initialIndent);
45 FormatPartialStatus partialStatus({}, options.formatOptions, initialState);
46 IndentingLineWriter indentingLineWriter([](QStringView line) { Q_UNUSED(line) }, QString(),
47 options, partialStatus.currentStatus);
48 OutWriter indentTracker(indentingLineWriter);
49 const auto commentLines = code.split(u'\n');
50 for (const auto &line : commentLines) {
51 if (!line.isEmpty()) {
52 partialStatus =
53 formatCodeLine(line, options.formatOptions, partialStatus.currentStatus);
54 indentTracker.write(line);
55 }
56 }
57
58 return indentTracker.indent;
59}
60
61/*
62\internal
63\brief Utility function to determine if a given location overlapping disabled region, returns an
64iterator to the region if found, or end() if not found
65*/
66static inline DisabledRegionIt
67findOverlappingRegion(const SourceLocation &loc,
68 const OutWriter::OffsetToDisabledRegionMap &formatDisabledRegions)
69{
70 if (!loc.isValid())
71 return formatDisabledRegions.cend();
72
73 return std::find_if(formatDisabledRegions.cbegin(), formatDisabledRegions.cend(),
74 [&loc](const auto &it) { return it.isValid() && overlaps(loc, it); });
75}
76
77QStringView OutWriter::attachedDisableCode(quint32 offset) const
78{
79 if (formatDisabledRegions.contains(offset)) {
80 const auto &loc = formatDisabledRegions.value(offset);
81 return code.mid(loc.offset, loc.length);
82 }
83 return {};
84}
85
86// This function examines the provided SourceLocation to determine if it overlaps with any regions
87// where formatting is disabled. If such a region is found and the formatter is currently enabled,
88// it writes the disabled region and disables the formatter. If no overlapping region is found,
89// the formatter is enabled.
90void OutWriter::maybeWriteDisabledRegion(const SourceLocation &loc)
91{
92 if (!loc.isValid())
93 return;
94 if (formatDisabledRegions.isEmpty())
95 return;
96 if (const auto foundRegionIt = findOverlappingRegion(loc, formatDisabledRegions);
97 foundRegionIt != formatDisabledRegions.end()) {
98 if (isFormatterEnabled) {
99 writeDisabledRegion(loc);
100 isFormatterEnabled = false;
101 }
102 } else {
103 isFormatterEnabled = true;
104 }
105}
106
107// Decides whether the given region should be formatted or not, based on the
108// disabled regions found in the file and updates and returns the formatting enabled state.
109bool OutWriter::shouldFormat(const FileLocations::Tree &fLoc, FileLocationRegion region)
110{
111 if (!fLoc || formatDisabledRegions.isEmpty())
112 return isFormatterEnabled;
113
114 if (const auto regions = fLoc->info().regions; regions.contains(region)) {
115 isFormatterEnabled = findOverlappingRegion(regions.value(region), formatDisabledRegions)
116 == formatDisabledRegions.end();
117 }
118 return isFormatterEnabled;
119}
120
121void OutWriter::scanFormatDirectives(QStringView code, const QList<SourceLocation> &comments)
122{
123 // Disabled regions cannot be effective if the line writer options
124 // are set to normalize or sort imports.
125 const auto shouldScanDirectives = lineWriter.options().attributesSequence
126 != LineWriterOptions::AttributesSequence::Normalize
127 && !lineWriter.options().sortImports;
128 if (!shouldScanDirectives)
129 return;
130
131 formatDisabledRegions = QmlFormat::identifyDisabledRegions(code, comments);
132}
133
134bool OutWriter::formatterEnabled() const
135{
136 return isFormatterEnabled;
137}
138
139void OutWriter::writeDisabledRegion(const SourceLocation &loc)
140{
141 const auto disabledCode = attachedDisableCode(loc.offset);
142 int newIndent = indentAfterPartialFormatting(indent, disabledCode, lineWriter.options());
143 lineWriter.ensureNewline();
144 lineWriter.setLineIndent(0);
145 indentNextlines = false;
146 lineWriter.write(disabledCode);
147 lineWriter.setLineIndent(newIndent);
148 indentNextlines = true;
149}
150
151void OutWriter::maybeWriteComment(const Comment &comment)
152{
153 maybeWriteDisabledRegion(comment.sourceLocation());
154
155 if (!skipComments && formatterEnabled()) {
156 comment.write(*this);
157 }
158
159 // if disabled, maybe reenabled with this comment
160 if (!formatterEnabled()) {
161 auto directive = QmlFormat::directiveFromComment(comment.rawComment());
162 if (directive == QmlFormat::Directive::On)
163 isFormatterEnabled = true;
164 }
165}
166
167void OutWriter::itemStart(const DomItem &it)
168{
169 if (skipComments)
170 return;
171
172 pendingComments.push(extractComments(it));
173 writePreComment(MainRegion);
174}
175
176void OutWriter::itemEnd()
177{
178 if (skipComments)
179 return;
180
181 Q_ASSERT(!pendingComments.isEmpty());
182 writePostComment(MainRegion);
183 pendingComments.pop();
184}
185
186void OutWriter::writePreComment(FileLocationRegion region)
187{
188 if (skipComments)
189 return;
190
191 const auto &comments = pendingComments.top();
192 if (comments.contains(region)) {
193 const auto attachedComments = comments[region];
194 for (const auto &comment : attachedComments.preComments())
195 maybeWriteComment(comment);
196 }
197}
198
199void OutWriter::writePostComment(FileLocationRegion region)
200{
201 if (skipComments)
202 return;
203
204 auto &comments = pendingComments.top();
205 if (comments.contains(region)) {
206 const auto attachedComments = comments[region];
207 for (const auto &comment : attachedComments.postComments())
208 maybeWriteComment(comment);
209 comments.remove(region);
210 }
211}
212
213static bool regionIncreasesIndentation(FileLocationRegion region)
214{
215 switch (region) {
216 case LeftBraceRegion:
217 return true;
218 case LeftBracketRegion:
219 return true;
220 default:
221 return false;
222 }
223 Q_UNREACHABLE_RETURN(false);
224}
225
226static bool regionDecreasesIndentation(FileLocationRegion region)
227{
228 switch (region) {
229 case RightBraceRegion:
230 return true;
231 case RightBracketRegion:
232 return true;
233 default:
234 return false;
235 }
236 Q_UNREACHABLE_RETURN(false);
237}
238
239/*!
240\internal
241Helper method for writeRegion(FileLocationRegion region) that allows to use
242\c{writeRegion(ColonTokenRegion);} instead of having to write out the more error-prone
243\c{writeRegion(ColonTokenRegion, ":");} for tokens and keywords.
244*/
245OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region)
246{
247 using namespace Qt::Literals::StringLiterals;
248 QString codeForRegion;
249 switch (region) {
250 case ComponentKeywordRegion:
251 codeForRegion = u"component"_s;
252 break;
253 case IdColonTokenRegion:
254 case ColonTokenRegion:
255 codeForRegion = u":"_s;
256 break;
257 case ImportTokenRegion:
258 codeForRegion = u"import"_s;
259 break;
260 case AsTokenRegion:
261 codeForRegion = u"as"_s;
262 break;
263 case OnTokenRegion:
264 codeForRegion = u"on"_s;
265 break;
266 case IdTokenRegion:
267 codeForRegion = u"id"_s;
268 break;
269 case LeftBraceRegion:
270 codeForRegion = u"{"_s;
271 break;
272 case RightBraceRegion:
273 codeForRegion = u"}"_s;
274 break;
275 case LeftBracketRegion:
276 codeForRegion = u"["_s;
277 break;
278 case RightBracketRegion:
279 codeForRegion = u"]"_s;
280 break;
281 case LeftParenthesisRegion:
282 codeForRegion = u"("_s;
283 break;
284 case RightParenthesisRegion:
285 codeForRegion = u")"_s;
286 break;
287 case EnumKeywordRegion:
288 codeForRegion = u"enum"_s;
289 break;
290 case DefaultKeywordRegion:
291 codeForRegion = u"default"_s;
292 break;
293 case RequiredKeywordRegion:
294 codeForRegion = u"required"_s;
295 break;
296 case ReadonlyKeywordRegion:
297 codeForRegion = u"readonly"_s;
298 break;
299 case PropertyKeywordRegion:
300 codeForRegion = u"property"_s;
301 break;
302 case FunctionKeywordRegion:
303 codeForRegion = u"function"_s;
304 break;
305 case SignalKeywordRegion:
306 codeForRegion = u"signal"_s;
307 break;
308 case ReturnKeywordRegion:
309 codeForRegion = u"return"_s;
310 break;
311 case EllipsisTokenRegion:
312 codeForRegion = u"..."_s;
313 break;
314 case EqualTokenRegion:
315 codeForRegion = u"="_s;
316 break;
317 case PragmaKeywordRegion:
318 codeForRegion = u"pragma"_s;
319 break;
320 case CommaTokenRegion:
321 codeForRegion = u","_s;
322 break;
323 case ForKeywordRegion:
324 codeForRegion = u"for"_s;
325 break;
326 case ElseKeywordRegion:
327 codeForRegion = u"else"_s;
328 break;
329 case DoKeywordRegion:
330 codeForRegion = u"do"_s;
331 break;
332 case WhileKeywordRegion:
333 codeForRegion = u"while"_s;
334 break;
335 case TryKeywordRegion:
336 codeForRegion = u"try"_s;
337 break;
338 case CatchKeywordRegion:
339 codeForRegion = u"catch"_s;
340 break;
341 case FinallyKeywordRegion:
342 codeForRegion = u"finally"_s;
343 break;
344 case CaseKeywordRegion:
345 codeForRegion = u"case"_s;
346 break;
347 case ThrowKeywordRegion:
348 codeForRegion = u"throw"_s;
349 break;
350 case ContinueKeywordRegion:
351 codeForRegion = u"continue"_s;
352 break;
353 case BreakKeywordRegion:
354 codeForRegion = u"break"_s;
355 break;
356 case QuestionMarkTokenRegion:
357 codeForRegion = u"?"_s;
358 break;
359 case SemicolonTokenRegion:
360 codeForRegion = u";"_s;
361 break;
362 case IfKeywordRegion:
363 codeForRegion = u"if"_s;
364 break;
365 case SwitchKeywordRegion:
366 codeForRegion = u"switch"_s;
367 break;
368 case YieldKeywordRegion:
369 codeForRegion = u"yield"_s;
370 break;
371 case NewKeywordRegion:
372 codeForRegion = u"new"_s;
373 break;
374 case ThisKeywordRegion:
375 codeForRegion = u"this"_s;
376 break;
377 case SuperKeywordRegion:
378 codeForRegion = u"super"_s;
379 break;
380 case StarTokenRegion:
381 codeForRegion = u"*"_s;
382 break;
383 case DollarLeftBraceTokenRegion:
384 codeForRegion = u"${"_s;
385 break;
386 case LeftBacktickTokenRegion:
387 case RightBacktickTokenRegion:
388 codeForRegion = u"`"_s;
389 break;
390 case FinalKeywordRegion:
391 codeForRegion = u"final"_s;
392 break;
393 // not keywords:
394 case ImportUriRegion:
395 case IdNameRegion:
396 case IdentifierRegion:
397 case PragmaValuesRegion:
398 case MainRegion:
399 case OnTargetRegion:
400 case TypeIdentifierRegion:
401 case TypeModifierRegion:
402 case FirstSemicolonTokenRegion:
403 case SecondSemicolonRegion:
404 case InOfTokenRegion:
405 case OperatorTokenRegion:
406 case VersionRegion:
407 case EnumValueRegion:
408 Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!");
409 return *this;
410 }
411
412 return writeRegion(fLoc, region, codeForRegion);
413}
414
415OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region,
416 QStringView toWrite)
417{
418 writePreComment(region);
419 if (regionDecreasesIndentation(region))
420 decreaseIndent(1);
421 if (shouldFormat(fLoc, region))
422 lineWriter.write(toWrite);
423 if (regionIncreasesIndentation(region))
424 increaseIndent(1);
425 writePostComment(region);
426 return *this;
427}
428
429} // namespace Dom
430} // namespace QQmlJS
431QT_END_NAMESPACE