| 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 "qqmldompath_p.h" |
| 5 | #include "qqmldomfilelocations_p.h" |
| 6 | |
| 7 | QT_BEGIN_NAMESPACE |
| 8 | namespace QQmlJS { |
| 9 | namespace Dom { |
| 10 | |
| 11 | using namespace Qt::StringLiterals; |
| 12 | |
| 13 | /*! |
| 14 | \internal |
| 15 | \namespace QQmlJS::Dom::FileLocations |
| 16 | \brief Provides entities to maintain mappings between elements and their location in a file |
| 17 | |
| 18 | The location information is associated with the element it refers to via ::Node |
| 19 | There are free functions to simplify the handling of the FileLocations tree. |
| 20 | */ |
| 21 | namespace FileLocations { |
| 22 | |
| 23 | /*! |
| 24 | \internal |
| 25 | \namespace QQmlJS::Dom::FileLocations::Info |
| 26 | \brief Contains region information about the item |
| 27 | |
| 28 | Attributes: |
| 29 | \list |
| 30 | \li fullRegion: A location guaranteed to include this element and all its sub elements |
| 31 | \li regions: a map with locations of regions of this element, the empty string is the default region |
| 32 | of this element |
| 33 | \endlist |
| 34 | */ |
| 35 | bool Info::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 36 | { |
| 37 | bool cont = true; |
| 38 | cont = cont && self.dvValueLazyField(visitor, f: Fields::fullRegion, valueF: [this]() { |
| 39 | return sourceLocationToQCborValue(loc: fullRegion); |
| 40 | }); |
| 41 | cont = cont |
| 42 | && self.dvItemField(visitor: std::move(visitor), f: Fields::regions, it: [this, &self]() -> DomItem { |
| 43 | const Path pathFromOwner = self.pathFromOwner().withField(name: Fields::regions); |
| 44 | auto map = Map::fromFileRegionMap(pathFromOwner, map: regions); |
| 45 | return self.subMapItem(map); |
| 46 | }); |
| 47 | return cont; |
| 48 | } |
| 49 | |
| 50 | Tree createTree(const Path &basePath) |
| 51 | { |
| 52 | return Node::instantiate(parent: nullptr, p: basePath); |
| 53 | } |
| 54 | |
| 55 | Tree ensure(const Tree &base, const Path &basePath) |
| 56 | { |
| 57 | Path relative = basePath; |
| 58 | Tree res = base; |
| 59 | for (const auto &p : std::as_const(t&: relative)) { |
| 60 | res = res->insertOrReturnChildAt(path: p); |
| 61 | } |
| 62 | return res; |
| 63 | } |
| 64 | |
| 65 | Tree find(const Tree &self, const Path &p) |
| 66 | { |
| 67 | Path rest = p; |
| 68 | Tree res = self; |
| 69 | while (rest) { |
| 70 | if (!res) |
| 71 | break; |
| 72 | res = res->subItems().value(key: rest.head()); |
| 73 | rest = rest.dropFront(); |
| 74 | } |
| 75 | return res; |
| 76 | } |
| 77 | |
| 78 | bool visitTree(const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor, |
| 79 | const Path &basePath) |
| 80 | { |
| 81 | if (base) { |
| 82 | Path pNow = basePath.withPath(toAdd: base->path()); |
| 83 | if (!visitor(pNow, base)) { |
| 84 | return false; |
| 85 | } |
| 86 | for (const auto &childNode : base->subItems()) { |
| 87 | if (!visitTree(base: childNode, visitor, basePath: pNow)) |
| 88 | return false; |
| 89 | } |
| 90 | } |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | QString canonicalPathForTesting(const Tree &base) |
| 95 | { |
| 96 | QString result; |
| 97 | for (auto *it = base.get(); it; it = it->parent().get()) { |
| 98 | result.prepend(s: it->path().toString()); |
| 99 | } |
| 100 | return result; |
| 101 | } |
| 102 | |
| 103 | /*! |
| 104 | \internal |
| 105 | Returns the tree corresponding to a DomItem. |
| 106 | */ |
| 107 | Tree treeOf(const DomItem &item) |
| 108 | { |
| 109 | Path p; |
| 110 | DomItem fLoc = item.field(name: Fields::fileLocationsTree); |
| 111 | if (!fLoc) { |
| 112 | // owner or container.owner should be a file, so this works, but we could simply use the |
| 113 | // canonical path, and PathType::Canonical instead... |
| 114 | DomItem o = item.owner(); |
| 115 | p = item.pathFromOwner(); |
| 116 | fLoc = o.field(name: Fields::fileLocationsTree); |
| 117 | while (!fLoc && o) { |
| 118 | DomItem c = o.container(); |
| 119 | p = c.pathFromOwner().withPath(toAdd: o.canonicalPath().last()).withPath(toAdd: p); |
| 120 | o = c.owner(); |
| 121 | fLoc = o.field(name: Fields::fileLocationsTree); |
| 122 | } |
| 123 | } |
| 124 | if (Node::Ptr fLocPtr = fLoc.ownerAs<Node>()) |
| 125 | return find(self: fLocPtr, p); |
| 126 | return {}; |
| 127 | } |
| 128 | |
| 129 | static void updateFullLocation(const Tree &fLoc, SourceLocation loc) |
| 130 | { |
| 131 | Q_ASSERT(fLoc); |
| 132 | if (loc != SourceLocation()) { |
| 133 | Tree p = fLoc; |
| 134 | while (p) { |
| 135 | SourceLocation &l = p->info().fullRegion; |
| 136 | if (loc.begin() < l.begin() || loc.end() > l.end()) { |
| 137 | l = combine(l1: l, l2: loc); |
| 138 | p->info().regions[MainRegion] = l; |
| 139 | } else { |
| 140 | break; |
| 141 | } |
| 142 | p = p->parent(); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | // Adding a new region to file location regions might break down qmlformat because |
| 148 | // comments might be linked to new region undesirably. We might need to add an |
| 149 | // exception to AstRangesVisitor::shouldSkipRegion when confronted those cases. |
| 150 | // beware of shrinking and reassigning regions (QTBUG-131288) |
| 151 | void addRegion(const Tree &fLoc, FileLocationRegion region, SourceLocation loc) |
| 152 | { |
| 153 | Q_ASSERT(fLoc); |
| 154 | fLoc->info().regions[region] = loc; |
| 155 | updateFullLocation(fLoc, loc); |
| 156 | } |
| 157 | |
| 158 | SourceLocation region(const Tree &fLoc, FileLocationRegion region) |
| 159 | { |
| 160 | Q_ASSERT(fLoc); |
| 161 | const auto ®ions = fLoc->info().regions; |
| 162 | if (auto it = regions.constFind(key: region); it != regions.constEnd() && it->isValid()) { |
| 163 | return *it; |
| 164 | } |
| 165 | |
| 166 | if (region == MainRegion) |
| 167 | return fLoc->info().fullRegion; |
| 168 | |
| 169 | return SourceLocation{}; |
| 170 | } |
| 171 | |
| 172 | /*! |
| 173 | \internal |
| 174 | \class QQmlJS::Dom::FileLocations::Node |
| 175 | \brief Represents a Node of FileLocations tree |
| 176 | |
| 177 | Attributes: |
| 178 | \list |
| 179 | \li parent: parent Node in tree (might be empty) |
| 180 | \li subItems: subItems of the tree (path -> Node) |
| 181 | \li infoItem: actual FileLocations::Info with regions |
| 182 | \endlist |
| 183 | |
| 184 | \sa QQmlJs::Dom::Node |
| 185 | */ |
| 186 | |
| 187 | Node::Ptr Node::instantiate(const Ptr &parent, const Path &p) |
| 188 | { |
| 189 | return std::shared_ptr<Node>(new Node(parent, p)); |
| 190 | } |
| 191 | |
| 192 | bool Node::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const |
| 193 | { |
| 194 | bool cont = true; |
| 195 | if (const Ptr p = parent()) { |
| 196 | cont = cont && self.dvItemField(visitor, f: Fields::parent, it: [&self, &p]() { |
| 197 | return self.copy(owner: p, ownerPath: self.m_ownerPath.dropTail(n: 2), base: p.get()); |
| 198 | }); |
| 199 | } |
| 200 | cont = cont |
| 201 | && self.dvValueLazyField(visitor, f: Fields::path, valueF: [this]() { return path().toString(); }); |
| 202 | cont = cont && self.dvItemField(visitor, f: Fields::subItems, it: [this, &self]() { |
| 203 | return self.subMapItem(map: Map( |
| 204 | Path::fromField(s: Fields::subItems), |
| 205 | [this](const DomItem &map, const QString &key) { |
| 206 | Path p = Path::fromString(s: key); |
| 207 | return map.copy(owner: m_subItems.value(key: p), ownerPath: map.canonicalPath().withKey(name: key)); |
| 208 | }, |
| 209 | [this](const DomItem &) { |
| 210 | QSet<QString> res; |
| 211 | for (const auto &p : m_subItems.keys()) |
| 212 | res.insert(value: p.toString()); |
| 213 | return res; |
| 214 | }, |
| 215 | QLatin1String("Node" ))); |
| 216 | }); |
| 217 | cont = cont && self.dvItemField(visitor: std::move(visitor), f: Fields::infoItem, it: [&self, this]() { |
| 218 | return self.wrapField(f: Fields::infoItem, obj: m_info); |
| 219 | }); |
| 220 | return cont; |
| 221 | } |
| 222 | |
| 223 | Node::Ptr Node::insertOrReturnChildAt(const Path &path) |
| 224 | { |
| 225 | if (Node::Ptr subEl = m_subItems.value(key: path)) { |
| 226 | return subEl; |
| 227 | } |
| 228 | return m_subItems.insert(key: path, value: Node::instantiate(parent: shared_from_this(), p: path)).value(); |
| 229 | } |
| 230 | |
| 231 | std::shared_ptr<OwningItem> Node::doCopy(const DomItem &) const |
| 232 | { |
| 233 | return std::shared_ptr<Node>(new Node(*this)); |
| 234 | } |
| 235 | |
| 236 | } // namespace FileLocations |
| 237 | } // namespace Dom |
| 238 | } // namespace QQmlJS |
| 239 | QT_END_NAMESPACE |
| 240 | |