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*/
245// TODO(QTBUG-138020)
246OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region)
247{
248 using namespace Qt::Literals::StringLiterals;
249 QString codeForRegion;
250 switch (region) {
251 case ComponentKeywordRegion:
252 codeForRegion = u"component"_s;
253 break;
254 case IdColonTokenRegion:
255 case ColonTokenRegion:
256 codeForRegion = u":"_s;
257 break;
258 case ImportTokenRegion:
259 codeForRegion = u"import"_s;
260 break;
261 case AsTokenRegion:
262 codeForRegion = u"as"_s;
263 break;
264 case OnTokenRegion:
265 codeForRegion = u"on"_s;
266 break;
267 case IdTokenRegion:
268 codeForRegion = u"id"_s;
269 break;
270 case LeftBraceRegion:
271 codeForRegion = u"{"_s;
272 break;
273 case RightBraceRegion:
274 codeForRegion = u"}"_s;
275 break;
276 case LeftBracketRegion:
277 codeForRegion = u"["_s;
278 break;
279 case RightBracketRegion:
280 codeForRegion = u"]"_s;
281 break;
282 case LeftParenthesisRegion:
283 codeForRegion = u"("_s;
284 break;
285 case RightParenthesisRegion:
286 codeForRegion = u")"_s;
287 break;
288 case EnumKeywordRegion:
289 codeForRegion = u"enum"_s;
290 break;
291 case DefaultKeywordRegion:
292 codeForRegion = u"default"_s;
293 break;
294 case RequiredKeywordRegion:
295 codeForRegion = u"required"_s;
296 break;
297 case ReadonlyKeywordRegion:
298 codeForRegion = u"readonly"_s;
299 break;
300 case PropertyKeywordRegion:
301 codeForRegion = u"property"_s;
302 break;
303 case FunctionKeywordRegion:
304 codeForRegion = u"function"_s;
305 break;
306 case SignalKeywordRegion:
307 codeForRegion = u"signal"_s;
308 break;
309 case ReturnKeywordRegion:
310 codeForRegion = u"return"_s;
311 break;
312 case EllipsisTokenRegion:
313 codeForRegion = u"..."_s;
314 break;
315 case EqualTokenRegion:
316 codeForRegion = u"="_s;
317 break;
318 case PragmaKeywordRegion:
319 codeForRegion = u"pragma"_s;
320 break;
321 case CommaTokenRegion:
322 codeForRegion = u","_s;
323 break;
324 case ForKeywordRegion:
325 codeForRegion = u"for"_s;
326 break;
327 case ElseKeywordRegion:
328 codeForRegion = u"else"_s;
329 break;
330 case DoKeywordRegion:
331 codeForRegion = u"do"_s;
332 break;
333 case WhileKeywordRegion:
334 codeForRegion = u"while"_s;
335 break;
336 case TryKeywordRegion:
337 codeForRegion = u"try"_s;
338 break;
339 case CatchKeywordRegion:
340 codeForRegion = u"catch"_s;
341 break;
342 case FinallyKeywordRegion:
343 codeForRegion = u"finally"_s;
344 break;
345 case CaseKeywordRegion:
346 codeForRegion = u"case"_s;
347 break;
348 case ThrowKeywordRegion:
349 codeForRegion = u"throw"_s;
350 break;
351 case ContinueKeywordRegion:
352 codeForRegion = u"continue"_s;
353 break;
354 case BreakKeywordRegion:
355 codeForRegion = u"break"_s;
356 break;
357 case QuestionMarkTokenRegion:
358 codeForRegion = u"?"_s;
359 break;
360 case SemicolonTokenRegion:
361 codeForRegion = u";"_s;
362 break;
363 case IfKeywordRegion:
364 codeForRegion = u"if"_s;
365 break;
366 case SwitchKeywordRegion:
367 codeForRegion = u"switch"_s;
368 break;
369 case YieldKeywordRegion:
370 codeForRegion = u"yield"_s;
371 break;
372 case NewKeywordRegion:
373 codeForRegion = u"new"_s;
374 break;
375 case ThisKeywordRegion:
376 codeForRegion = u"this"_s;
377 break;
378 case SuperKeywordRegion:
379 codeForRegion = u"super"_s;
380 break;
381 case StarTokenRegion:
382 codeForRegion = u"*"_s;
383 break;
384 case DollarLeftBraceTokenRegion:
385 codeForRegion = u"${"_s;
386 break;
387 case LeftBacktickTokenRegion:
388 case RightBacktickTokenRegion:
389 codeForRegion = u"`"_s;
390 break;
391 case VirtualKeywordRegion:
392 codeForRegion = u"virtual"_s;
393 break;
394 case OverrideKeywordRegion:
395 codeForRegion = u"override"_s;
396 break;
397 case FinalKeywordRegion:
398 codeForRegion = u"final"_s;
399 break;
400 // not keywords:
401 case ImportUriRegion:
402 case IdNameRegion:
403 case IdentifierRegion:
404 case PragmaValuesRegion:
405 case MainRegion:
406 case OnTargetRegion:
407 case TypeIdentifierRegion:
408 case TypeModifierRegion:
409 case FirstSemicolonTokenRegion:
410 case SecondSemicolonRegion:
411 case InOfTokenRegion:
412 case OperatorTokenRegion:
413 case VersionRegion:
414 case EnumValueRegion:
415 Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!");
416 return *this;
417 }
418
419 return writeRegion(fLoc, region, codeForRegion);
420}
421
422OutWriter &OutWriter::writeRegion(const FileLocations::Tree &fLoc, FileLocationRegion region,
423 QStringView toWrite)
424{
425 writePreComment(region);
426 if (regionDecreasesIndentation(region))
427 decreaseIndent(1);
428 if (shouldFormat(fLoc, region))
429 lineWriter.write(toWrite);
430 if (regionIncreasesIndentation(region))
431 increaseIndent(1);
432 writePostComment(region);
433 return *this;
434}
435
436} // namespace Dom
437} // namespace QQmlJS
438QT_END_NAMESPACE