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