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
4#include "qqmldomoutwriter_p.h"
5#include "qqmldomattachedinfo_p.h"
6#include "qqmldomlinewriter_p.h"
7#include "qqmldomitem_p.h"
8#include "qqmldomcomments_p.h"
9#include "qqmldomexternalitems_p.h"
10#include "qqmldomtop_p.h"
11
12#include <QtCore/QLoggingCategory>
13
14QT_BEGIN_NAMESPACE
15namespace QQmlJS {
16namespace Dom {
17
18OutWriterState::OutWriterState(
19 const Path &itCanonicalPath, const DomItem &it, const FileLocations::Tree &fLoc)
20 : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc)
21{
22 DomItem cRegions = it.field(name: Fields::comments);
23 if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>())
24 pendingComments = cRegionsPtr->regionComments();
25}
26
27void OutWriterState::closeState(OutWriter &w)
28{
29 if (w.lineWriter.options().updateOptions & LineWriterOptions::Update::Locations)
30 w.lineWriter.endSourceLocation(fullRegionId);
31 if (!pendingRegions.isEmpty()) {
32 qCWarning(writeOutLog) << "PendingRegions non empty when closing item"
33 << pendingRegions.keys();
34 auto iend = pendingRegions.end();
35 auto it = pendingRegions.begin();
36 while (it == iend) {
37 w.lineWriter.endSourceLocation(it.value());
38 ++it;
39 }
40 }
41 if (!w.skipComments && !pendingComments.isEmpty())
42 qCWarning(writeOutLog) << "PendingComments when closing item "
43 << item.canonicalPath().toString() << "for regions"
44 << pendingComments.keys();
45}
46
47OutWriterState &OutWriter::state(int i)
48{
49 return states[states.size() - 1 - i];
50}
51
52void OutWriter::itemStart(const DomItem &it)
53{
54 if (!topLocation->path())
55 topLocation->setPath(it.canonicalPath());
56 bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
57 FileLocations::Tree newFLoc = topLocation;
58 Path itP = it.canonicalPath();
59 if (updateLocs) {
60 if (!states.isEmpty()
61 && states.last().itemCanonicalPath
62 == itP.mid(offset: 0, length: states.last().itemCanonicalPath.length())) {
63 int oldL = states.last().itemCanonicalPath.length();
64 newFLoc = FileLocations::ensure(base: states.last().currentMap,
65 basePath: itP.mid(offset: oldL, length: itP.length() - oldL),
66 pType: AttachedInfo::PathType::Relative);
67
68 } else {
69 newFLoc = FileLocations::ensure(base: topLocation, basePath: itP, pType: AttachedInfo::PathType::Canonical);
70 }
71 }
72 states.append(t: OutWriterState(itP, it, newFLoc));
73 if (updateLocs)
74 state().fullRegionId = lineWriter.startSourceLocation(
75 [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(fLoc: newFLoc, loc: l); });
76 regionStart(region: MainRegion);
77}
78
79void OutWriter::itemEnd(const DomItem &it)
80{
81 Q_ASSERT(states.size() > 0);
82 Q_ASSERT(state().item == it);
83 regionEnd(regino: MainRegion);
84 state().closeState(w&: *this);
85 states.removeLast();
86}
87
88void OutWriter::regionStart(FileLocationRegion region)
89{
90 Q_ASSERT(!state().pendingRegions.contains(region));
91 FileLocations::Tree fMap = state().currentMap;
92 if (!skipComments && state().pendingComments.contains(key: region)) {
93 bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
94 QList<SourceLocation> *cLocs =
95 (updateLocs ? &(fMap->info().preCommentLocations[region]) : nullptr);
96 state().pendingComments[region].writePre(lw&: *this, locations: cLocs);
97 }
98 state().pendingRegions[region] = lineWriter.startSourceLocation(
99 [region, fMap](SourceLocation l) { FileLocations::addRegion(fLoc: fMap, region, loc: l); });
100}
101
102void OutWriter::regionEnd(FileLocationRegion region)
103{
104 Q_ASSERT(state().pendingRegions.contains(region));
105 FileLocations::Tree fMap = state().currentMap;
106 lineWriter.endSourceLocation(state().pendingRegions.value(key: region));
107 state().pendingRegions.remove(key: region);
108 if (state().pendingComments.contains(key: region)) {
109 if (!skipComments) {
110 bool updateLocs =
111 lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
112 QList<SourceLocation> *cLocs =
113 (updateLocs ? &(fMap->info().postCommentLocations[region]) : nullptr);
114 state().pendingComments[region].writePost(lw&: *this, locations: cLocs);
115 }
116 state().pendingComments.remove(key: region);
117 }
118}
119
120/*!
121\internal
122Helper method for writeRegion(FileLocationRegion region) that allows to use
123\c{writeRegion(ColonTokenRegion);} instead of having to write out the more error-prone
124\c{writeRegion(ColonTokenRegion, ":");} for tokens and keywords.
125*/
126OutWriter &OutWriter::writeRegion(FileLocationRegion region)
127{
128 QString codeForRegion;
129 switch (region) {
130 case ComponentKeywordRegion:
131 codeForRegion = u"component"_s;
132 break;
133 case IdColonTokenRegion:
134 case ColonTokenRegion:
135 codeForRegion = u":"_s;
136 break;
137 case ImportTokenRegion:
138 codeForRegion = u"import"_s;
139 break;
140 case AsTokenRegion:
141 codeForRegion = u"as"_s;
142 break;
143 case OnTokenRegion:
144 codeForRegion = u"on"_s;
145 break;
146 case IdTokenRegion:
147 codeForRegion = u"id"_s;
148 break;
149 case LeftBraceRegion:
150 codeForRegion = u"{"_s;
151 break;
152 case RightBraceRegion:
153 codeForRegion = u"}"_s;
154 break;
155 case LeftBracketRegion:
156 codeForRegion = u"["_s;
157 break;
158 case RightBracketRegion:
159 codeForRegion = u"]"_s;
160 break;
161 case LeftParenthesisRegion:
162 codeForRegion = u"("_s;
163 break;
164 case RightParenthesisRegion:
165 codeForRegion = u")"_s;
166 break;
167 case EnumKeywordRegion:
168 codeForRegion = u"enum"_s;
169 break;
170 case DefaultKeywordRegion:
171 codeForRegion = u"default"_s;
172 break;
173 case RequiredKeywordRegion:
174 codeForRegion = u"required"_s;
175 break;
176 case ReadonlyKeywordRegion:
177 codeForRegion = u"readonly"_s;
178 break;
179 case PropertyKeywordRegion:
180 codeForRegion = u"property"_s;
181 break;
182 case FunctionKeywordRegion:
183 codeForRegion = u"function"_s;
184 break;
185 case SignalKeywordRegion:
186 codeForRegion = u"signal"_s;
187 break;
188 case ReturnKeywordRegion:
189 codeForRegion = u"return"_s;
190 break;
191 case EllipsisTokenRegion:
192 codeForRegion = u"..."_s;
193 break;
194 case EqualTokenRegion:
195 codeForRegion = u"="_s;
196 break;
197 case PragmaKeywordRegion:
198 codeForRegion = u"pragma"_s;
199 break;
200 case CommaTokenRegion:
201 codeForRegion = u","_s;
202 break;
203 case ForKeywordRegion:
204 codeForRegion = u"for"_s;
205 break;
206 case ElseKeywordRegion:
207 codeForRegion = u"else"_s;
208 break;
209 case DoKeywordRegion:
210 codeForRegion = u"do"_s;
211 break;
212 case WhileKeywordRegion:
213 codeForRegion = u"while"_s;
214 break;
215 case TryKeywordRegion:
216 codeForRegion = u"try"_s;
217 break;
218 case CatchKeywordRegion:
219 codeForRegion = u"catch"_s;
220 break;
221 case FinallyKeywordRegion:
222 codeForRegion = u"finally"_s;
223 break;
224 case CaseKeywordRegion:
225 codeForRegion = u"case"_s;
226 break;
227 case ThrowKeywordRegion:
228 codeForRegion = u"throw"_s;
229 break;
230 case ContinueKeywordRegion:
231 codeForRegion = u"continue"_s;
232 break;
233 case BreakKeywordRegion:
234 codeForRegion = u"break"_s;
235 break;
236 case QuestionMarkTokenRegion:
237 codeForRegion = u"?"_s;
238 break;
239 case SemicolonTokenRegion:
240 codeForRegion = u";"_s;
241 break;
242 case IfKeywordRegion:
243 codeForRegion = u"if"_s;
244 break;
245 case SwitchKeywordRegion:
246 codeForRegion = u"switch"_s;
247 break;
248 case YieldKeywordRegion:
249 codeForRegion = u"yield"_s;
250 break;
251 case StarTokenRegion:
252 codeForRegion = u"*"_s;
253 break;
254 case NewKeywordRegion:
255 codeForRegion = u"new"_s;
256 break;
257 case ThisKeywordRegion:
258 codeForRegion = u"this"_s;
259 break;
260 case SuperKeywordRegion:
261 codeForRegion = u"super"_s;
262 break;
263 case DollarLeftBraceTokenRegion:
264 codeForRegion = u"${"_s;
265 break;
266 case LeftBacktickTokenRegion:
267 case RightBacktickTokenRegion:
268 codeForRegion = u"`"_s;
269 break;
270 // not keywords:
271 case ImportUriRegion:
272 case IdNameRegion:
273 case IdentifierRegion:
274 case PragmaValuesRegion:
275 case MainRegion:
276 case OnTargetRegion:
277 case TypeIdentifierRegion:
278 case TypeModifierRegion:
279 case FirstSemicolonTokenRegion:
280 case SecondSemicolonRegion:
281 case InOfTokenRegion:
282 case OperatorTokenRegion:
283 case VersionRegion:
284 case EnumValueRegion:
285 Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!");
286 return *this;
287 }
288
289 return writeRegion(region, toWrite: codeForRegion);
290}
291
292OutWriter &OutWriter::writeRegion(FileLocationRegion region, QStringView toWrite)
293{
294 regionStart(region);
295 lineWriter.write(v: toWrite);
296 regionEnd(region);
297 return *this;
298}
299/*!
300 \internal
301 Restores written out FileItem using intermediate information saved during DOM traversal.
302 It enables verifying DOM consistency of the written item later.
303
304 At the moment of writing, intermediate information consisting only of UpdatedScriptExpression,
305 however this is subject for change. The process of restoration is the following:
306 1. Creating copy of the initial fileItem
307 2. Updating relevant data/subitems modified during the WriteOut
308 3. Returning an item containing updates.
309 */
310DomItem OutWriter::restoreWrittenFileItem(const DomItem &fileItem)
311{
312 switch (fileItem.internalKind()) {
313 case DomType::QmlFile:
314 return writtenQmlFileItem(fileItem, filePath: fileItem.canonicalPath());
315 case DomType::JsFile:
316 return writtenJsFileItem(fileItem, filePath: fileItem.canonicalPath());
317 default:
318 qCWarning(writeOutLog) << fileItem.internalKind() << " is not supported";
319 return DomItem{};
320 }
321}
322
323DomItem OutWriter::writtenQmlFileItem(const DomItem &fileItem, const Path &filePath)
324{
325 Q_ASSERT(fileItem.internalKind() == DomType::QmlFile);
326 auto mutableFile = fileItem.makeCopy(option: DomItem::CopyOption::EnvDisconnected);
327 // QmlFile specific visitor for reformattedScriptExpressions tree
328 // lambda function responsible for the update of the initial expression by the formatted one
329 auto exprUpdater = [&mutableFile, filePath](
330 const Path &p, const UpdatedScriptExpression::Tree &t) {
331 if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
332 Q_ASSERT(p.mid(0, filePath.length()) == filePath);
333 MutableDomItem originalExprItem = mutableFile.path(p: p.mid(offset: filePath.length()));
334 if (!originalExprItem)
335 qCWarning(writeOutLog) << "failed to get" << p.mid(offset: filePath.length()) << "from"
336 << mutableFile.canonicalPath();
337 // Verifying originalExprItem.as<ScriptExpression>() == false is handy
338 // because we can't call setScript on the ScriptExpression itself and it needs to
339 // be called on the container / parent item. See setScript for details
340 else if (formattedExpr->ast()
341 || (!originalExprItem.as<ScriptExpression>()
342 || !originalExprItem.as<ScriptExpression>()->ast()))
343 originalExprItem.setScript(formattedExpr);
344 else {
345 logScriptExprUpdateSkipped(exprItem: originalExprItem.item(),
346 exprPath: originalExprItem.canonicalPath(), formattedExpr);
347 }
348 }
349 return true;
350 };
351 // update relevant formatted expressions
352 UpdatedScriptExpression::visitTree(base: reformattedScriptExpressions, visitor: exprUpdater);
353 return mutableFile.item();
354}
355
356DomItem OutWriter::writtenJsFileItem(const DomItem &fileItem, const Path &filePath)
357{
358 Q_ASSERT(fileItem.internalKind() == DomType::JsFile);
359 auto mutableFile = fileItem.makeCopy(option: DomItem::CopyOption::EnvDisconnected);
360 UpdatedScriptExpression::visitTree(
361 base: reformattedScriptExpressions,
362 visitor: [&mutableFile, filePath](const Path &p, const UpdatedScriptExpression::Tree &t) {
363 if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
364 Q_ASSERT(p.mid(0, filePath.length()) == filePath);
365 mutableFile.mutableAs<JsFile>()->setExpression(formattedExpr);
366 }
367 return true;
368 });
369 return mutableFile.item();
370}
371
372void OutWriter::logScriptExprUpdateSkipped(
373 const DomItem &exprItem, const Path &exprPath,
374 const std::shared_ptr<ScriptExpression> &formattedExpr)
375{
376 qCWarning(writeOutLog).noquote() << "Skipped update of reformatted ScriptExpression with "
377 "code:\n---------------\n"
378 << formattedExpr->code() << "\n---------------\n preCode:" <<
379 [&formattedExpr](Sink s) { sinkEscaped(sink: s, s: formattedExpr->preCode()); }
380 << "\n postCode: " <<
381 [&formattedExpr](Sink s) { sinkEscaped(sink: s, s: formattedExpr->postCode()); }
382 << "\n as it failed standalone reparse with errors:" <<
383 [&exprItem, &exprPath, &formattedExpr](Sink s) {
384 exprItem.copy(owner: formattedExpr, ownerPath: exprPath)
385 .iterateErrors(
386 visitor: [s](const DomItem &, const ErrorMessage &msg) {
387 s(u"\n ");
388 msg.dump(s);
389 return true;
390 },
391 iterate: true);
392 } << "\n";
393}
394} // namespace Dom
395} // namespace QQmlJS
396QT_END_NAMESPACE
397

source code of qtdeclarative/src/qmldom/qqmldomoutwriter.cpp