1 | // Copyright (C) 2021 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 | #include "qqmldom_fwd_p.h" |
4 | #include "qqmldomlinewriter_p.h" |
5 | #include "qqmldomelements_p.h" |
6 | #include "qqmldompath_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | namespace QQmlJS { |
10 | namespace Dom { |
11 | |
12 | using namespace Qt::StringLiterals; |
13 | |
14 | |
15 | /*! |
16 | \internal |
17 | \class QQmlJS::Dom::FileLocations |
18 | \brief Represents and maintains a mapping between elements and their location in a file |
19 | |
20 | The location information is attached to the element it refers to via AttachedInfo |
21 | There are static methods to simplify the handling of the tree of AttachedInfo. |
22 | |
23 | Attributes: |
24 | \list |
25 | \li fullRegion: A location guaranteed to include this element, its comments, and all its sub elements |
26 | \li regions: a map with locations of regions of this element, the empty string is the default region |
27 | of this element |
28 | \li preCommentLocations: locations of the comments before this element |
29 | \li postCommentLocations: locations of the comments after this element |
30 | \endlist |
31 | |
32 | \sa QQmlJs::Dom::AttachedInfo |
33 | */ |
34 | bool FileLocations::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
35 | { |
36 | bool cont = true; |
37 | cont = cont && self.dvValueLazyField(visitor, f: Fields::fullRegion, valueF: [this]() { |
38 | return sourceLocationToQCborValue(loc: fullRegion); |
39 | }); |
40 | cont = cont && self.dvItemField(visitor, f: Fields::regions, it: [this, &self]() -> DomItem { |
41 | const Path pathFromOwner = self.pathFromOwner().field(name: Fields::regions); |
42 | auto map = Map::fromFileRegionMap(pathFromOwner, map: regions); |
43 | return self.subMapItem(map); |
44 | }); |
45 | cont = cont |
46 | && self.dvItemField(visitor, f: Fields::preCommentLocations, it: [this, &self]() -> DomItem { |
47 | const Path pathFromOwner = |
48 | self.pathFromOwner().field(name: Fields::preCommentLocations); |
49 | auto map = Map::fromFileRegionListMap(pathFromOwner, map: preCommentLocations); |
50 | return self.subMapItem(map); |
51 | }); |
52 | cont = cont |
53 | && self.dvItemField(visitor, f: Fields::postCommentLocations, it: [this, &self]() -> DomItem { |
54 | const Path pathFromOwner = |
55 | self.pathFromOwner().field(name: Fields::postCommentLocations); |
56 | auto map = Map::fromFileRegionListMap(pathFromOwner, map: postCommentLocations); |
57 | return self.subMapItem(map); |
58 | }); |
59 | return cont; |
60 | } |
61 | |
62 | FileLocations::Tree FileLocations::createTree(const Path &basePath){ |
63 | return AttachedInfoT<FileLocations>::createTree(p: basePath); |
64 | } |
65 | |
66 | FileLocations::Tree FileLocations::ensure( |
67 | const FileLocations::Tree &base, const Path &basePath, AttachedInfo::PathType pType) |
68 | { |
69 | return AttachedInfoT<FileLocations>::ensure(self: base, path: basePath, pType); |
70 | } |
71 | |
72 | /*! |
73 | \internal |
74 | Allows to query information about the FileLocations::Tree obtained from item, such as path of |
75 | the Tree root in the Dom, the path of this item's Tree in the Dom, and so on. |
76 | |
77 | \note You can use \c{qDebug() << item.path(FileLocations::findAttachedInfo(item).foundTreePath)} or |
78 | \c{item.path(FileLocations::findAttachedInfo(item).foundTreePath).toString()} to print out the Tree |
79 | of item, for example, as Tree's cannot be printed when outside the Dom. |
80 | */ |
81 | AttachedInfoLookupResult<FileLocations::Tree> |
82 | FileLocations::findAttachedInfo(const DomItem &item) |
83 | { |
84 | return AttachedInfoT<FileLocations>::findAttachedInfo(item, fieldName: Fields::fileLocationsTree); |
85 | } |
86 | |
87 | /*! |
88 | \internal |
89 | Returns the tree corresponding to a DomItem. |
90 | */ |
91 | FileLocations::Tree FileLocations::treeOf(const DomItem &item) |
92 | { |
93 | return findAttachedInfo(item).foundTree; |
94 | } |
95 | |
96 | /*! |
97 | \internal |
98 | Returns the filelocation Info corresponding to a DomItem. |
99 | */ |
100 | const FileLocations *FileLocations::fileLocationsOf(const DomItem &item) |
101 | { |
102 | if (const FileLocations::Tree &t = treeOf(item)) |
103 | return &(t->info()); |
104 | return nullptr; |
105 | } |
106 | |
107 | void FileLocations::updateFullLocation(const FileLocations::Tree &fLoc, SourceLocation loc) |
108 | { |
109 | Q_ASSERT(fLoc); |
110 | if (loc != SourceLocation()) { |
111 | FileLocations::Tree p = fLoc; |
112 | while (p) { |
113 | SourceLocation &l = p->info().fullRegion; |
114 | if (loc.begin() < l.begin() || loc.end() > l.end()) { |
115 | l = combine(l1: l, l2: loc); |
116 | p->info().regions[MainRegion] = l; |
117 | } else { |
118 | break; |
119 | } |
120 | p = p->parent(); |
121 | } |
122 | } |
123 | } |
124 | |
125 | // Adding a new region to file location regions might break down qmlformat because |
126 | // comments might be linked to new region undesirably. We might need to add an |
127 | // exception to AstRangesVisitor::shouldSkipRegion when confronted those cases. |
128 | void FileLocations::addRegion(const FileLocations::Tree &fLoc, FileLocationRegion region, |
129 | SourceLocation loc) |
130 | { |
131 | Q_ASSERT(fLoc); |
132 | fLoc->info().regions[region] = loc; |
133 | updateFullLocation(fLoc, loc); |
134 | } |
135 | |
136 | SourceLocation FileLocations::region(const FileLocations::Tree &fLoc, FileLocationRegion region) |
137 | { |
138 | Q_ASSERT(fLoc); |
139 | const auto ®ions = fLoc->info().regions; |
140 | if (auto it = regions.constFind(key: region); it != regions.constEnd() && it->isValid()) { |
141 | return *it; |
142 | } |
143 | |
144 | if (region == MainRegion) |
145 | return fLoc->info().fullRegion; |
146 | |
147 | return SourceLocation{}; |
148 | } |
149 | |
150 | /*! |
151 | \internal |
152 | \class QQmlJS::Dom::AttachedInfo |
153 | \brief Attached info creates a tree to attach extra info to DomItems |
154 | |
155 | Normally one uses the template AttachedInfoT<SpecificInfoToAttach> |
156 | |
157 | static methods |
158 | Attributes: |
159 | \list |
160 | \li parent: parent AttachedInfo in tree (might be empty) |
161 | \li subItems: subItems of the tree (path -> AttachedInfo) |
162 | \li infoItem: the attached information |
163 | \endlist |
164 | |
165 | \sa QQmlJs::Dom::AttachedInfo |
166 | */ |
167 | |
168 | bool AttachedInfo::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
169 | { |
170 | bool cont = true; |
171 | if (Ptr p = parent()) |
172 | cont = cont && self.dvItemField(visitor, f: Fields::parent, it: [&self, p]() { |
173 | return self.copy(owner: p, ownerPath: self.m_ownerPath.dropTail(n: 2), base: p.get()); |
174 | }); |
175 | cont = cont |
176 | && self.dvValueLazyField(visitor, f: Fields::path, valueF: [this]() { return path().toString(); }); |
177 | cont = cont && self.dvItemField(visitor, f: Fields::subItems, it: [this, &self]() { |
178 | return self.subMapItem(map: Map( |
179 | Path::Field(s: Fields::subItems), |
180 | [this](const DomItem &map, const QString &key) { |
181 | Path p = Path::fromString(s: key); |
182 | return map.copy(owner: m_subItems.value(key: p), ownerPath: map.canonicalPath().key(name: key)); |
183 | }, |
184 | [this](const DomItem &) { |
185 | QSet<QString> res; |
186 | for (const auto &p : m_subItems.keys()) |
187 | res.insert(value: p.toString()); |
188 | return res; |
189 | }, |
190 | QLatin1String("AttachedInfo" ))); |
191 | }); |
192 | cont = cont && self.dvItemField(visitor, f: Fields::infoItem, it: [&self, this]() { |
193 | return infoItem(self); |
194 | }); |
195 | return cont; |
196 | } |
197 | |
198 | AttachedInfo::AttachedInfo(const AttachedInfo &o): |
199 | OwningItem(o), |
200 | m_parent(o.m_parent) |
201 | { |
202 | } |
203 | |
204 | /*! |
205 | \brief |
206 | Returns that the AttachedInfo corresponding to the given path, creating it if it does not exists. |
207 | |
208 | The path might be either a relative path or a canonical path, as specified by the PathType |
209 | */ |
210 | AttachedInfo::Ptr AttachedInfo::ensure( |
211 | const AttachedInfo::Ptr &self, const Path &path, AttachedInfo::PathType pType){ |
212 | Path relative; |
213 | switch (pType) { |
214 | case PathType::Canonical: { |
215 | if (!path) |
216 | return nullptr; |
217 | Q_ASSERT(self); |
218 | Path removed = path.mid(offset: 0, length: self->path().length()); |
219 | Q_ASSERT(removed == self->path()); |
220 | relative = path.mid(offset: self->path().length()); |
221 | } break; |
222 | case PathType::Relative: |
223 | Q_ASSERT(self); |
224 | relative = path; |
225 | break; |
226 | } |
227 | Ptr res = self; |
228 | for (const auto &p : std::as_const(t&: relative)) { |
229 | if (AttachedInfo::Ptr subEl = res->m_subItems.value(key: p)) { |
230 | res = subEl; |
231 | } else { |
232 | AttachedInfo::Ptr newEl = res->instantiate(parent: res, p); |
233 | res->m_subItems.insert(key: p, value: newEl); |
234 | res = newEl; |
235 | } |
236 | } |
237 | return res; |
238 | } |
239 | |
240 | AttachedInfo::Ptr AttachedInfo::find( |
241 | const AttachedInfo::Ptr &self, const Path &p, AttachedInfo::PathType pType) |
242 | { |
243 | Path rest; |
244 | if (pType == PathType::Canonical) { |
245 | if (!self) return nullptr; |
246 | Path removed = p.mid(offset: 0, length: self->path().length()); |
247 | if (removed != self->path()) |
248 | return nullptr; |
249 | rest = p.dropFront(n: self->path().length()); |
250 | } else { |
251 | rest = p; |
252 | } |
253 | |
254 | AttachedInfo::Ptr res = self; |
255 | while (rest) { |
256 | if (!res) |
257 | break; |
258 | res = res->m_subItems.value(key: rest.head()); |
259 | rest = rest.dropFront(); |
260 | } |
261 | return res; |
262 | } |
263 | |
264 | AttachedInfoLookupResult<AttachedInfo::Ptr> |
265 | AttachedInfo::findAttachedInfo(const DomItem &item, QStringView fieldName) |
266 | { |
267 | Path p; |
268 | DomItem fLoc = item.field(name: fieldName); |
269 | if (!fLoc) { |
270 | // owner or container.owner should be a file, so this works, but we could simply use the |
271 | // canonical path, and PathType::Canonical instead... |
272 | DomItem o = item.owner(); |
273 | p = item.pathFromOwner(); |
274 | fLoc = o.field(name: fieldName); |
275 | while (!fLoc && o) { |
276 | DomItem c = o.container(); |
277 | p = c.pathFromOwner().path(toAdd: o.canonicalPath().last()).path(toAdd: p); |
278 | o = c.owner(); |
279 | fLoc = o.field(name: fieldName); |
280 | } |
281 | } |
282 | AttachedInfoLookupResult<AttachedInfo::Ptr> res; |
283 | res.lookupPath = p; |
284 | if (AttachedInfo::Ptr fLocPtr = fLoc.ownerAs<AttachedInfo>()) |
285 | if (AttachedInfo::Ptr foundTree = |
286 | AttachedInfo::find(self: fLocPtr, p, pType: AttachedInfo::PathType::Relative)) |
287 | res.foundTree = foundTree; |
288 | res.rootTreePath = fLoc.canonicalPath(); |
289 | |
290 | res.foundTreePath = res.rootTreePath; |
291 | for (const Path &pEl : res.lookupPath) |
292 | res.foundTreePath = res.foundTreePath.field(name: Fields::subItems).key(name: pEl.toString()); |
293 | return res; |
294 | } |
295 | |
296 | bool UpdatedScriptExpression::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
297 | { |
298 | bool cont = true; |
299 | cont = cont && self.dvWrapField(visitor, f: Fields::expr, obj: expr); |
300 | return cont; |
301 | } |
302 | |
303 | UpdatedScriptExpression::Tree UpdatedScriptExpression::createTree(const Path &basePath) |
304 | { |
305 | return AttachedInfoT<UpdatedScriptExpression>::createTree(p: basePath); |
306 | } |
307 | |
308 | UpdatedScriptExpression::Tree UpdatedScriptExpression::ensure( |
309 | const UpdatedScriptExpression::Tree &base, const Path &basePath, |
310 | AttachedInfo::PathType pType) |
311 | { |
312 | return AttachedInfoT<UpdatedScriptExpression>::ensure(self: base, path: basePath, pType); |
313 | } |
314 | |
315 | AttachedInfoLookupResult<UpdatedScriptExpression::Tree> |
316 | UpdatedScriptExpression::findAttachedInfo(const DomItem &item) |
317 | { |
318 | return AttachedInfoT<UpdatedScriptExpression>::findAttachedInfo( |
319 | item, fieldName: Fields::updatedScriptExpressions); |
320 | } |
321 | |
322 | UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(const DomItem &item) |
323 | { |
324 | return AttachedInfoT<UpdatedScriptExpression>::treePtr(item, fieldName: Fields::updatedScriptExpressions); |
325 | } |
326 | |
327 | const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(const DomItem &item) |
328 | { |
329 | if (UpdatedScriptExpression::Tree t = treePtr(item)) |
330 | return &(t->info()); |
331 | return nullptr; |
332 | } |
333 | |
334 | bool UpdatedScriptExpression::visitTree( |
335 | const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, |
336 | const Path &basePath) |
337 | { |
338 | return AttachedInfoT<UpdatedScriptExpression>::visitTree(base, visitor, basePath); |
339 | } |
340 | |
341 | } // namespace Dom |
342 | } // namespace QQmlJS |
343 | QT_END_NAMESPACE |
344 | |
345 | #include "moc_qqmldomattachedinfo_p.cpp" |
346 | |