Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ #include "qqmldom_fwd_p.h"
++ #include "qqmldompath_p.h"
++ #include "qqmldomfilelocations_p.h"
++
++ QT_BEGIN_NAMESPACE
++ namespace QQmlJS {
++ namespace Dom {
++
++ using namespace Qt::StringLiterals;
++
++ /*!
++ \internal
++ \namespace QQmlJS::Dom::FileLocations
++ \brief Provides entities to maintain mappings between elements and their location in a file
++
++ The location information is associated with the element it refers to via ::Node
++ There are free functions to simplify the handling of the FileLocations tree.
++ */
++ namespace FileLocations {
++
++ /*!
++ \internal
++ \namespace QQmlJS::Dom::FileLocations::Info
++ \brief Contains region information about the item
++
++ Attributes:
++ \list
++ \li fullRegion: A location guaranteed to include this element and all its sub elements
++ \li regions: a map with locations of regions of this element, the empty string is the default region
++ of this element
++ \endlist
++ */
++ bool Info::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ bool cont = true;
++ cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::fullRegion, [this]() {
++ return sourceLocationToQCborValue(fullRegion);
++ });
++ cont = cont && visitor(PathEls::Field(Fields::regions), [this, &self]() -> DomItem {
++ const Path pathFromOwner = self.pathFromOwner().withField(Fields::regions);
++ auto map = Map::fromFileRegionMap(pathFromOwner, regions);
++ return self.subMapItem(map);
++ });
++ return cont;
++ }
++
++ Tree createTree(const Path &basePath)
++ {
++ return Node::instantiate(nullptr, basePath);
++ }
++
++ Tree ensure(const Tree &base, const Path &basePath)
++ {
++ Path relative = basePath;
++ Tree res = base;
++ for (const auto &p : std::as_const(relative)) {
++ res = res->insertOrReturnChildAt(p);
++ }
++ return res;
++ }
++
++ Tree find(const Tree &self, const Path &p)
++ {
++ Path rest = p;
++ Tree res = self;
++ while (rest) {
++ if (!res)
++ break;
++ res = res->subItems().value(rest.head());
++ rest = rest.dropFront();
++ }
++ return res;
++ }
++
++ bool visitTree(const Tree &base, function_ref<bool(const Path &, const Tree &)> visitor,
++ const Path &basePath)
++ {
++ if (base) {
++ Path pNow = basePath.withPath(base->path());
++ if (!visitor(pNow, base)) {
++ return false;
++ }
++ for (const auto &childNode : base->subItems()) {
++ if (!visitTree(childNode, visitor, pNow))
++ return false;
++ }
++ }
++ return true;
++ }
++
++ QString canonicalPathForTesting(const Tree &base)
++ {
++ QString result;
++ for (auto *it = base.get(); it; it = it->parent().get()) {
++ result.prepend(it->path().toString());
++ }
++ return result;
++ }
++
++ /*!
++ \internal
++ Returns the tree corresponding to a DomItem.
++ */
++ Tree treeOf(const DomItem &item)
++ {
++ Path p;
++ DomItem fLoc = item.field(Fields::fileLocationsTree);
++ if (!fLoc) {
++ // owner or container.owner should be a file, so this works, but we could simply use the
++ // canonical path, and PathType::Canonical instead...
++ DomItem o = item.owner();
++ p = item.pathFromOwner();
++ fLoc = o.field(Fields::fileLocationsTree);
++ while (!fLoc && o) {
++ DomItem c = o.container();
++ p = c.pathFromOwner().withPath(o.canonicalPath().last()).withPath(p);
++ o = c.owner();
++ fLoc = o.field(Fields::fileLocationsTree);
++ }
++ }
++ if (Node::Ptr fLocPtr = fLoc.ownerAs<Node>())
++ return find(fLocPtr, p);
++ return {};
++ }
++
++ static void updateFullLocation(const Tree &fLoc, SourceLocation loc)
++ {
++ Q_ASSERT(fLoc);
++ if (loc != SourceLocation()) {
++ Tree p = fLoc;
++ while (p) {
++ SourceLocation &l = p->info().fullRegion;
++ if (loc.begin() < l.begin() || loc.end() > l.end()) {
++ l = combine(l, loc);
++ p->info().regions[MainRegion] = l;
++ } else {
++ break;
++ }
++ p = p->parent();
++ }
++ }
++ }
++
++ // Adding a new region to file location regions might break down qmlformat because
++ // comments might be linked to new region undesirably. We might need to add an
++ // exception to AstRangesVisitor::shouldSkipRegion when confronted those cases.
++ // beware of shrinking and reassigning regions (QTBUG-131288)
++ void addRegion(const Tree &fLoc, FileLocationRegion region, SourceLocation loc)
++ {
++ Q_ASSERT(fLoc);
++ fLoc->info().regions[region] = loc;
++ updateFullLocation(fLoc, loc);
++ }
++
++ SourceLocation region(const Tree &fLoc, FileLocationRegion region)
++ {
++ Q_ASSERT(fLoc);
++ const auto &regions = fLoc->info().regions;
++ if (auto it = regions.constFind(region); it != regions.constEnd() && it->isValid()) {
++ return *it;
++ }
++
++ if (region == MainRegion)
++ return fLoc->info().fullRegion;
++
++ return SourceLocation{};
++ }
++
++ /*!
++ \internal
++ \class QQmlJS::Dom::FileLocations::Node
++ \brief Represents a Node of FileLocations tree
++
++ Attributes:
++ \list
++ \li parent: parent Node in tree (might be empty)
++ \li subItems: subItems of the tree (path -> Node)
++ \li infoItem: actual FileLocations::Info with regions
++ \endlist
++
++ \sa QQmlJs::Dom::Node
++ */
++
++ Node::Ptr Node::instantiate(const Ptr &parent, const Path &p)
++ {
++ return std::shared_ptr<Node>(new Node(parent, p));
++ }
++
++ bool Node::iterateDirectSubpaths(const DomItem &self, DirectVisitor visitor) const
++ {
++ bool cont = true;
++ if (const Ptr p = parent()) {
++ cont = cont && visitor(PathEls::Field(Fields::parent), [&self, &p]() {
++ return self.copy(p, self.m_ownerPath.dropTail(2), p.get());
++ });
++ }
++ cont = cont && self.invokeVisitorOnLazyField(visitor, Fields::path, [this]() {
++ return path().toString();
++ });
++ cont = cont && visitor(PathEls::Field(Fields::subItems), [this, &self]() {
++ return self.subMapItem(Map(
++ Path::fromField(Fields::subItems),
++ [this](const DomItem &map, const QString &key) {
++ Path p = Path::fromString(key);
++ return map.copy(m_subItems.value(p), map.canonicalPath().withKey(key));
++ },
++ [this](const DomItem &) {
++ QSet<QString> res;
++ for (const auto &p : m_subItems.keys())
++ res.insert(p.toString());
++ return res;
++ },
++ QLatin1String("Node")));
++ });
++ cont = cont && visitor(PathEls::Field(Fields::infoItem), [&self, this]() {
++ return self.wrap(PathEls::Field(Fields::infoItem), m_info);
++ });
++ return cont;
++ }
++
++ Node::Ptr Node::insertOrReturnChildAt(const Path &path)
++ {
++ if (Node::Ptr subEl = m_subItems.value(path)) {
++ return subEl;
++ }
++ return m_subItems.insert(path, Node::instantiate(shared_from_this(), path)).value();
++ }
++
++ std::shared_ptr<OwningItem> Node::doCopy(const DomItem &) const
++ {
++ return std::shared_ptr<Node>(new Node(*this));
++ }
++
++ } // namespace FileLocations
++ } // namespace Dom
++ } // namespace QQmlJS
++ QT_END_NAMESPACE