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 // not keywords:
264 case ImportUriRegion:
265 case IdNameRegion:
266 case IdentifierRegion:
267 case PragmaValuesRegion:
268 case MainRegion:
269 case OnTargetRegion:
270 case TypeIdentifierRegion:
271 case TypeModifierRegion:
272 case FirstSemicolonTokenRegion:
273 case SecondSemicolonRegion:
274 case InOfTokenRegion:
275 case OperatorTokenRegion:
276 case VersionRegion:
277 case EnumValueRegion:
278 Q_ASSERT_X(false, "regionToString", "Using regionToString on a value or an identifier!");
279 return *this;
280 }
281
282 return writeRegion(region, toWrite: codeForRegion);
283}
284
285OutWriter &OutWriter::writeRegion(FileLocationRegion region, QStringView toWrite)
286{
287 regionStart(region);
288 lineWriter.write(v: toWrite);
289 regionEnd(region);
290 return *this;
291}
292/*!
293 \internal
294 Restores written out FileItem using intermediate information saved during DOM traversal.
295 It enables verifying DOM consistency of the written item later.
296
297 At the moment of writing, intermediate information consisting only of UpdatedScriptExpression,
298 however this is subject for change. The process of restoration is the following:
299 1. Creating copy of the initial fileItem
300 2. Updating relevant data/subitems modified during the WriteOut
301 3. Returning an item containing updates.
302 */
303DomItem OutWriter::restoreWrittenFileItem(const DomItem &fileItem)
304{
305 switch (fileItem.internalKind()) {
306 case DomType::QmlFile:
307 return writtenQmlFileItem(fileItem, filePath: fileItem.canonicalPath());
308 case DomType::JsFile:
309 return writtenJsFileItem(fileItem, filePath: fileItem.canonicalPath());
310 default:
311 qCWarning(writeOutLog) << fileItem.internalKind() << " is not supported";
312 return DomItem{};
313 }
314}
315
316DomItem OutWriter::writtenQmlFileItem(const DomItem &fileItem, const Path &filePath)
317{
318 Q_ASSERT(fileItem.internalKind() == DomType::QmlFile);
319 auto mutableFile = fileItem.makeCopy(option: DomItem::CopyOption::EnvDisconnected);
320 // QmlFile specific visitor for reformattedScriptExpressions tree
321 // lambda function responsible for the update of the initial expression by the formatted one
322 auto exprUpdater = [&mutableFile, filePath](
323 const Path &p, const UpdatedScriptExpression::Tree &t) {
324 if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
325 Q_ASSERT(p.mid(0, filePath.length()) == filePath);
326 MutableDomItem originalExprItem = mutableFile.path(p: p.mid(offset: filePath.length()));
327 if (!originalExprItem)
328 qCWarning(writeOutLog) << "failed to get" << p.mid(offset: filePath.length()) << "from"
329 << mutableFile.canonicalPath();
330 // Verifying originalExprItem.as<ScriptExpression>() == false is handy
331 // because we can't call setScript on the ScriptExpression itself and it needs to
332 // be called on the container / parent item. See setScript for details
333 else if (formattedExpr->ast()
334 || (!originalExprItem.as<ScriptExpression>()
335 || !originalExprItem.as<ScriptExpression>()->ast()))
336 originalExprItem.setScript(formattedExpr);
337 else {
338 logScriptExprUpdateSkipped(exprItem: originalExprItem.item(),
339 exprPath: originalExprItem.canonicalPath(), formattedExpr);
340 }
341 }
342 return true;
343 };
344 // update relevant formatted expressions
345 UpdatedScriptExpression::visitTree(base: reformattedScriptExpressions, visitor: exprUpdater);
346 return mutableFile.item();
347}
348
349DomItem OutWriter::writtenJsFileItem(const DomItem &fileItem, const Path &filePath)
350{
351 Q_ASSERT(fileItem.internalKind() == DomType::JsFile);
352 auto mutableFile = fileItem.makeCopy(option: DomItem::CopyOption::EnvDisconnected);
353 UpdatedScriptExpression::visitTree(
354 base: reformattedScriptExpressions,
355 visitor: [&mutableFile, filePath](const Path &p, const UpdatedScriptExpression::Tree &t) {
356 if (std::shared_ptr<ScriptExpression> formattedExpr = t->info().expr) {
357 Q_ASSERT(p.mid(0, filePath.length()) == filePath);
358 mutableFile.mutableAs<JsFile>()->setExpression(formattedExpr);
359 }
360 return true;
361 });
362 return mutableFile.item();
363}
364
365void OutWriter::logScriptExprUpdateSkipped(
366 const DomItem &exprItem, const Path &exprPath,
367 const std::shared_ptr<ScriptExpression> &formattedExpr)
368{
369 qCWarning(writeOutLog).noquote() << "Skipped update of reformatted ScriptExpression with "
370 "code:\n---------------\n"
371 << formattedExpr->code() << "\n---------------\n preCode:" <<
372 [&formattedExpr](Sink s) { sinkEscaped(sink: s, s: formattedExpr->preCode()); }
373 << "\n postCode: " <<
374 [&formattedExpr](Sink s) { sinkEscaped(sink: s, s: formattedExpr->postCode()); }
375 << "\n as it failed standalone reparse with errors:" <<
376 [&exprItem, &exprPath, &formattedExpr](Sink s) {
377 exprItem.copy(owner: formattedExpr, ownerPath: exprPath)
378 .iterateErrors(
379 visitor: [s](const DomItem &, const ErrorMessage &msg) {
380 s(u"\n ");
381 msg.dump(s);
382 return true;
383 },
384 iterate: true);
385 } << "\n";
386}
387} // namespace Dom
388} // namespace QQmlJS
389QT_END_NAMESPACE
390

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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