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 | |
14 | QT_BEGIN_NAMESPACE |
15 | namespace QQmlJS { |
16 | namespace Dom { |
17 | |
18 | OutWriterState::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 | |
28 | void 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 | |
48 | OutWriterState &OutWriter::state(int i) |
49 | { |
50 | return states[states.size() - 1 - i]; |
51 | } |
52 | |
53 | void 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 | |
80 | void 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 | |
89 | void 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 | |
103 | void 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 | |
121 | OutWriter &OutWriter::writeRegion(QString rName, QStringView toWrite) |
122 | { |
123 | regionStart(rName); |
124 | lineWriter.write(v: toWrite); |
125 | regionEnd(rName); |
126 | return *this; |
127 | } |
128 | |
129 | DomItem 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: [©, 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 |
208 | QT_END_NAMESPACE |
209 | |