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(Path itCanonicalPath, DomItem &it, FileLocations::Tree fLoc)
19 : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc)
20{
21 DomItem cRegions = it.field(name: Fields::comments);
22 if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) {
23 pendingComments = cRegionsPtr->regionComments;
24 fLoc->info().ensureCommentLocations(keys: pendingComments.keys());
25 }
26}
27
28void OutWriterState::closeState(OutWriter &w)
29{
30 if (w.lineWriter.options().updateOptions & LineWriterOptions::Update::Locations)
31 w.lineWriter.endSourceLocation(fullRegionId);
32 if (!pendingRegions.isEmpty()) {
33 qCWarning(writeOutLog) << "PendingRegions non empty when closing item"
34 << pendingRegions.keys();
35 auto iend = pendingRegions.end();
36 auto it = pendingRegions.begin();
37 while (it == iend) {
38 w.lineWriter.endSourceLocation(it.value());
39 ++it;
40 }
41 }
42 if (!w.skipComments && !pendingComments.isEmpty())
43 qCWarning(writeOutLog) << "PendingComments when closing item "
44 << item.canonicalPath().toString() << "for regions"
45 << pendingComments.keys();
46}
47
48OutWriterState &OutWriter::state(int i)
49{
50 return states[states.size() - 1 - i];
51}
52
53void OutWriter::itemStart(DomItem &it)
54{
55 if (!topLocation->path())
56 topLocation->setPath(it.canonicalPath());
57 bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
58 FileLocations::Tree newFLoc = topLocation;
59 Path itP = it.canonicalPath();
60 if (updateLocs) {
61 if (!states.isEmpty()
62 && states.last().itemCanonicalPath
63 == itP.mid(offset: 0, length: states.last().itemCanonicalPath.length())) {
64 int oldL = states.last().itemCanonicalPath.length();
65 newFLoc = FileLocations::ensure(base: states.last().currentMap,
66 basePath: itP.mid(offset: oldL, length: itP.length() - oldL),
67 pType: AttachedInfo::PathType::Relative);
68
69 } else {
70 newFLoc = FileLocations::ensure(base: topLocation, basePath: itP, pType: AttachedInfo::PathType::Canonical);
71 }
72 }
73 states.append(t: OutWriterState(itP, it, newFLoc));
74 if (updateLocs)
75 state().fullRegionId = lineWriter.startSourceLocation(
76 [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(fLoc: newFLoc, loc: l); });
77 regionStart(rName: QString());
78}
79
80void OutWriter::itemEnd(DomItem &it)
81{
82 Q_ASSERT(states.size() > 0);
83 Q_ASSERT(state().item == it);
84 regionEnd(rName: QString());
85 state().closeState(w&: *this);
86 states.removeLast();
87}
88
89void OutWriter::regionStart(QString rName)
90{
91 Q_ASSERT(!state().pendingRegions.contains(rName));
92 FileLocations::Tree fMap = state().currentMap;
93 if (!skipComments && state().pendingComments.contains(key: rName)) {
94 bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
95 QList<SourceLocation> *cLocs =
96 (updateLocs ? &(fMap->info().preCommentLocations[rName]) : nullptr);
97 state().pendingComments[rName].writePre(lw&: *this, locations: cLocs);
98 }
99 state().pendingRegions[rName] = lineWriter.startSourceLocation(
100 [rName, fMap](SourceLocation l) { FileLocations::addRegion(fLoc: fMap, locName: rName, loc: l); });
101}
102
103void OutWriter::regionEnd(QString rName)
104{
105 Q_ASSERT(state().pendingRegions.contains(rName));
106 FileLocations::Tree fMap = state().currentMap;
107 lineWriter.endSourceLocation(state().pendingRegions.value(key: rName));
108 state().pendingRegions.remove(key: rName);
109 if (state().pendingComments.contains(key: rName)) {
110 if (!skipComments) {
111 bool updateLocs =
112 lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
113 QList<SourceLocation> *cLocs =
114 (updateLocs ? &(fMap->info().postCommentLocations[rName]) : nullptr);
115 state().pendingComments[rName].writePost(lw&: *this, locations: cLocs);
116 }
117 state().pendingComments.remove(key: rName);
118 }
119}
120
121OutWriter &OutWriter::writeRegion(QString rName, QStringView toWrite)
122{
123 regionStart(rName);
124 lineWriter.write(v: toWrite);
125 regionEnd(rName);
126 return *this;
127}
128
129DomItem OutWriter::updatedFile(DomItem &qmlFile)
130{
131 Q_ASSERT(qmlFile.internalKind() == DomType::QmlFile);
132 if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
133 std::shared_ptr<QmlFile> copyPtr = qmlFilePtr->makeCopy(self&: qmlFile);
134 DomItem env = qmlFile.environment();
135 std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
136 Q_ASSERT(envPtr);
137 auto newEnvPtr = std::make_shared<DomEnvironment>(
138 args&: envPtr, args: envPtr->loadPaths(), args: envPtr->options());
139 newEnvPtr->addQmlFile(file: copyPtr);
140 MutableDomItem copy = MutableDomItem(DomItem(newEnvPtr).copy(base: copyPtr));
141 FileLocations::Tree newLoc = topLocation;
142 Path qmlFilePath = qmlFile.canonicalPath();
143 if (newLoc->path() != qmlFilePath) {
144 if (newLoc->path()) {
145 if (newLoc->path().length() > qmlFilePath.length()
146 && newLoc->path().mid(offset: 0, length: qmlFilePath.length()) == qmlFilePath) {
147 newLoc = FileLocations::createTree(basePath: qmlFilePath);
148 FileLocations::Tree loc =
149 FileLocations::ensure(base: newLoc, basePath: newLoc->path().mid(offset: qmlFilePath.length()),
150 pType: AttachedInfo::PathType::Relative);
151 loc->setSubItems(topLocation->subItems());
152 } else {
153 qCWarning(writeOutLog)
154 << "failed to base fileLocations in OutWriter (" << newLoc->path()
155 << ") to current file (" << qmlFilePath << ")";
156 }
157 } else {
158 newLoc = FileLocations::createTree(basePath: qmlFilePath);
159 Q_ASSERT(newLoc->subItems().isEmpty() && newLoc->info().regions.isEmpty());
160 }
161 }
162 copyPtr->setFileLocationsTree(newLoc);
163 UpdatedScriptExpression::visitTree(
164 base: reformattedScriptExpressions,
165 visitor: [&copy, qmlFilePath](Path p, UpdatedScriptExpression::Tree t) {
166 if (std::shared_ptr<ScriptExpression> exprPtr = t->info().expr) {
167 Q_ASSERT(p.mid(0, qmlFilePath.length()) == qmlFilePath);
168 MutableDomItem targetExpr = copy.path(p: p.mid(offset: qmlFilePath.length()));
169 if (!targetExpr)
170 qCWarning(writeOutLog) << "failed to get" << p.mid(offset: qmlFilePath.length())
171 << "from" << copy.canonicalPath();
172 else if (exprPtr->ast()
173 || (!targetExpr.as<ScriptExpression>()
174 || !targetExpr.as<ScriptExpression>()->ast()))
175 targetExpr.setScript(exprPtr);
176 else {
177 qCWarning(writeOutLog).noquote()
178 << "Skipped update of reformatted ScriptExpression with "
179 "code:\n---------------\n"
180 << exprPtr->code() << "\n---------------\n preCode:" <<
181 [exprPtr](Sink s) { sinkEscaped(sink: s, s: exprPtr->preCode()); }
182 << "\n postCode: " <<
183 [exprPtr](Sink s) { sinkEscaped(sink: s, s: exprPtr->postCode()); }
184 << "\n as it failed standalone reparse with errors:" <<
185 [&targetExpr, exprPtr](Sink s) {
186 targetExpr.item()
187 .copy(owner: exprPtr, ownerPath: targetExpr.canonicalPath())
188 .iterateErrors(
189 visitor: [s](DomItem, ErrorMessage msg) {
190 s(u"\n ");
191 msg.dump(s);
192 return true;
193 },
194 iterate: true);
195 }
196 << "\n";
197 }
198 }
199 return true;
200 });
201 return copy.item();
202 }
203 return DomItem();
204}
205
206} // namespace Dom
207} // namespace QQmlJS
208QT_END_NAMESPACE
209

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