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