aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFawzi Mohamed <fawzi.mohamed@qt.io>2021-03-23 15:37:32 +0100
committerFawzi Mohamed <fawzi.mohamed@qt.io>2021-06-05 00:07:48 +0200
commitde3d65009adf3c7ce0be6da77ee74b0a2610c9c5 (patch)
treeb9c03fc2b5e2c7845c4b4070c403eae66e5df3ef /src
parent6cf15ad4e359223bb19c997f0dd279f9aea842d7 (diff)
qmldom: writeOut, write reformatted Qml
Adding writeOut: support for reformatted Qml - linewriter: write line by line with caching, callbacks, SourceLoaction updating - outwriter: write to line writer, and keep track of updated file locations and reformatted ScriptExpressions - reformatter: reformat javascript Change-Id: I4bdc393fb2d9b5a3db944a850719c24ef8726d15 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/qmldom/CMakeLists.txt3
-rw-r--r--src/qmldom/qqmldomattachedinfo.cpp46
-rw-r--r--src/qmldom/qqmldomattachedinfo_p.h27
-rw-r--r--src/qmldom/qqmldomcomments.cpp37
-rw-r--r--src/qmldom/qqmldomconstants_p.h15
-rw-r--r--src/qmldom/qqmldomelements.cpp512
-rw-r--r--src/qmldom/qqmldomelements_p.h55
-rw-r--r--src/qmldom/qqmldomexternalitems.cpp14
-rw-r--r--src/qmldom/qqmldomexternalitems_p.h2
-rw-r--r--src/qmldom/qqmldomitem.cpp235
-rw-r--r--src/qmldom/qqmldomitem_p.h61
-rw-r--r--src/qmldom/qqmldomlinewriter.cpp482
-rw-r--r--src/qmldom/qqmldomlinewriter_p.h251
-rw-r--r--src/qmldom/qqmldomoutwriter.cpp242
-rw-r--r--src/qmldom/qqmldomoutwriter_p.h204
-rw-r--r--src/qmldom/qqmldomreformatter.cpp1145
-rw-r--r--src/qmldom/qqmldomreformatter_p.h72
-rw-r--r--src/qmldom/qqmldomstringdumper.cpp7
-rw-r--r--src/qmldom/qqmldomstringdumper_p.h2
19 files changed, 3372 insertions, 40 deletions
diff --git a/src/qmldom/CMakeLists.txt b/src/qmldom/CMakeLists.txt
index 1ec6f55172..d8f244c7a7 100644
--- a/src/qmldom/CMakeLists.txt
+++ b/src/qmldom/CMakeLists.txt
@@ -25,9 +25,12 @@ qt_internal_add_module(QmlDom
qqmldomfunctionref_p.h
qqmldomitem.cpp qqmldomitem_p.h
qqmldommock.cpp qqmldommock_p.h
+ qqmldomlinewriter.cpp qqmldomlinewriter_p.h
qqmldommoduleindex.cpp qqmldommoduleindex_p.h
+ qqmldomoutwriter.cpp qqmldomoutwriter_p.h
qqmldompath.cpp qqmldompath_p.h
qqmldomstringdumper.cpp qqmldomstringdumper_p.h
+ qqmldomreformatter.cpp qqmldomreformatter_p.h
qqmldomtop.cpp qqmldomtop_p.h
DEFINES
QMLDOM_LIBRARY
diff --git a/src/qmldom/qqmldomattachedinfo.cpp b/src/qmldom/qqmldomattachedinfo.cpp
index c86279cff6..707aae4283 100644
--- a/src/qmldom/qqmldomattachedinfo.cpp
+++ b/src/qmldom/qqmldomattachedinfo.cpp
@@ -36,6 +36,8 @@
** $QT_END_LICENSE$
**/
#include "qqmldomattachedinfo_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomelements_p.h"
QT_BEGIN_NAMESPACE
namespace QQmlJS {
@@ -320,6 +322,50 @@ AttachedInfo::findAttachedInfo(DomItem &item, QStringView fieldName,
return res;
}
+bool UpdatedScriptExpression::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
+{
+ bool cont = true;
+ cont = cont && self.dvWrapField(visitor, Fields::expr, expr);
+ return cont;
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::createTree(Path basePath)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::createTree(basePath);
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::ensure(UpdatedScriptExpression::Tree base,
+ Path basePath,
+ AttachedInfo::PathType pType)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::ensure(base, basePath, pType);
+}
+
+AttachedInfoLookupResult<UpdatedScriptExpression::Tree>
+UpdatedScriptExpression::findAttachedInfo(DomItem &item, AttachedInfo::FindOptions options)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::findAttachedInfo(
+ item, Fields::updatedScriptExpressions, options);
+}
+
+UpdatedScriptExpression::Tree UpdatedScriptExpression::treePtr(DomItem &item)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::treePtr(item, Fields::updatedScriptExpressions);
+}
+
+const UpdatedScriptExpression *UpdatedScriptExpression::exprPtr(DomItem &item)
+{
+ if (UpdatedScriptExpression::Tree t = treePtr(item))
+ return &(t->info());
+ return nullptr;
+}
+
+bool UpdatedScriptExpression::visitTree(Tree base, function_ref<bool(Path, Tree)> visitor,
+ Path basePath)
+{
+ return AttachedInfoT<UpdatedScriptExpression>::visitTree(base, visitor, basePath);
+}
+
} // namespace Dom
} // namespace QQmlJS
QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomattachedinfo_p.h b/src/qmldom/qqmldomattachedinfo_p.h
index ea080f3fbc..668df88f8b 100644
--- a/src/qmldom/qqmldomattachedinfo_p.h
+++ b/src/qmldom/qqmldomattachedinfo_p.h
@@ -304,6 +304,33 @@ public:
QMap<QString, QList<SourceLocation>> postCommentLocations;
};
+class QMLDOM_EXPORT UpdatedScriptExpression
+{
+ Q_GADGET
+public:
+ using Tree = std::shared_ptr<AttachedInfoT<UpdatedScriptExpression>>;
+ constexpr static DomType kindValue = DomType::UpdatedScriptExpression;
+ DomType kind() const { return kindValue; }
+ bool iterateDirectSubpaths(DomItem &self, DirectVisitor);
+
+ static Tree createTree(Path basePath);
+ static Tree ensure(Tree base, Path basePath, AttachedInfo::PathType pType);
+
+ // returns the path looked up and the found tree when looking for the info attached to item
+ static AttachedInfoLookupResult<Tree>
+ findAttachedInfo(DomItem &item,
+ AttachedInfo::FindOptions options = AttachedInfo::FindOption::Default);
+ // convenience: find FileLocations::Tree attached to the given item
+ static Tree treePtr(DomItem &);
+ // convenience: find FileLocations* attached to the given item (if there is one)
+ static const UpdatedScriptExpression *exprPtr(DomItem &);
+
+ static bool visitTree(Tree base, function_ref<bool(Path, Tree)> visitor,
+ Path basePath = Path());
+
+ std::shared_ptr<ScriptExpression> expr;
+};
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomcomments.cpp b/src/qmldom/qqmldomcomments.cpp
index d1f386c6be..af58b6d821 100644
--- a/src/qmldom/qqmldomcomments.cpp
+++ b/src/qmldom/qqmldomcomments.cpp
@@ -27,6 +27,8 @@
****************************************************************************/
#include "qqmldomcomments_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomlinewriter_p.h"
#include "qqmldomelements_p.h"
#include "qqmldomexternalitems_p.h"
#include "qqmldomastdumper_p.h"
@@ -222,6 +224,23 @@ bool Comment::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
return cont;
}
+void Comment::write(OutWriter &lw, SourceLocation *commentLocation) const
+{
+ if (newlinesBefore())
+ lw.ensureNewline(newlinesBefore());
+ CommentInfo cInfo = info();
+ lw.ensureSpace(cInfo.preWhitespace());
+ QStringView cBody = cInfo.comment();
+ PendingSourceLocationId cLoc = lw.lineWriter.startSourceLocation(commentLocation);
+ lw.write(cBody.mid(0, 1));
+ bool indentOn = lw.indentNextlines;
+ lw.indentNextlines = false;
+ lw.write(cBody.mid(1));
+ lw.indentNextlines = indentOn;
+ lw.lineWriter.endSourceLocation(cLoc);
+ lw.write(cInfo.postWhitespace());
+}
+
/*!
\class QQmlJS::Dom::CommentedElement
\brief Keeps the comment associated with an element
@@ -250,6 +269,24 @@ bool CommentedElement::iterateDirectSubpaths(DomItem &self, DirectVisitor visito
return cont;
}
+void CommentedElement::writePre(OutWriter &lw, QList<SourceLocation> *locs) const
+{
+ if (locs)
+ locs->resize(preComments.size());
+ int i = 0;
+ for (const Comment &c : preComments)
+ c.write(lw, (locs ? &((*locs)[i++]) : nullptr));
+}
+
+void CommentedElement::writePost(OutWriter &lw, QList<SourceLocation> *locs) const
+{
+ if (locs)
+ locs->resize(postComments.size());
+ int i = 0;
+ for (const Comment &c : postComments)
+ c.write(lw, (locs ? &((*locs)[i++]) : nullptr));
+}
+
/*!
\brief Given the SourceLocation of the current element returns the comments associated with the
start and end of item
diff --git a/src/qmldom/qqmldomconstants_p.h b/src/qmldom/qqmldomconstants_p.h
index e9a2b8c753..0156509399 100644
--- a/src/qmldom/qqmldomconstants_p.h
+++ b/src/qmldom/qqmldomconstants_p.h
@@ -279,6 +279,21 @@ Q_ENUM_NS(AddOption)
enum class FilterUpOptions { ReturnOuter, ReturnOuterNoSelf, ReturnInner };
Q_ENUM_NS(FilterUpOptions)
+enum class WriteOutCheck {
+ None = 0x0,
+ UpdatedDomCompare = 0x1,
+ UpdatedDomStable = 0x2,
+ Reparse = 0x4,
+ ReparseCompare = 0x8,
+ ReparseStable = 0x10,
+ DumpOnFailure = 0x20,
+ All = 0x3F,
+ Default = Reparse | ReparseCompare | ReparseStable
+};
+Q_ENUM_NS(WriteOutCheck)
+Q_DECLARE_FLAGS(WriteOutChecks, WriteOutCheck)
+Q_DECLARE_OPERATORS_FOR_FLAGS(WriteOutChecks)
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp
index 2600035512..7688c845d7 100644
--- a/src/qmldom/qqmldomelements.cpp
+++ b/src/qmldom/qqmldomelements.cpp
@@ -39,6 +39,9 @@
#include "qqmldomcomments_p.h"
#include "qqmldomastdumper_p.h"
#include "qqmldommock_p.h"
+#include "qqmldomreformatter_p.h"
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomlinewriter_p.h"
#include "qqmldomtop_p.h"
#include "qqmldomexternalitems_p.h"
@@ -199,6 +202,20 @@ void QmlComponent::updatePathFromOwner(Path newPath)
updatePathFromOwnerMultiMap(m_ids, newPath.field(Fields::annotations));
}
+void QmlComponent::writeOut(DomItem &self, OutWriter &lw) const
+{
+ if (name().contains(QLatin1Char('.'))) {
+ // inline component
+ lw.ensureNewline()
+ .writeRegion(u"component")
+ .space()
+ .writeRegion(u"componentName", name().split(QLatin1Char('.')).last())
+ .writeRegion(u"colon", u":")
+ .space();
+ }
+ self.field(Fields::objects).index(0).writeOut(lw);
+}
+
QList<QString> QmlComponent::subComponentsNames(DomItem &self) const
{
DomItem components = self.owner().field(Fields::components);
@@ -360,6 +377,30 @@ bool Import::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
return cont;
}
+void Import::writeOut(DomItem &self, OutWriter &ow) const
+{
+ if (implicit)
+ return;
+ ow.ensureNewline();
+ ow.writeRegion(u"import").space();
+ if (uri.startsWith(u"http://") || uri.startsWith(u"https://") || uri.startsWith(u"file://")) {
+ if (uri.startsWith(u"file://")) {
+ QFileInfo myPath(self.canonicalFilePath());
+ QString relPath = myPath.dir().relativeFilePath(uri.mid(7));
+ ow.writeRegion(u"uri", dumperToString([relPath](Sink s) { sinkEscaped(s, relPath); }));
+ } else {
+ ow.writeRegion(u"uri", dumperToString([this](Sink s) { sinkEscaped(s, this->uri); }));
+ }
+ } else {
+ ow.writeRegion(u"uri", uri);
+ QString vString = version.stringValue();
+ if (!vString.isEmpty())
+ ow.space().write(vString);
+ }
+ if (!importId.isEmpty())
+ ow.space().writeRegion(u"as").space().writeRegion(u"id", importId);
+}
+
Id::Id(QString idName, Path referredObject) : name(idName), referredObjectPath(referredObject) { }
bool Id::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
@@ -610,6 +651,250 @@ MutableDomItem QmlObject::addMethod(MutableDomItem &self, MethodInfo functionDef
return self.owner().path(p);
}
+void QmlObject::writeOut(DomItem &self, OutWriter &ow, QString onTarget) const
+{
+ bool isRootObject = pathFromOwner().length() == 5
+ && pathFromOwner()[0] == Path::Field(Fields::components)
+ && pathFromOwner()[3] == Path::Field(Fields::objects);
+ ow.writeRegion(u"name", name());
+ if (!onTarget.isEmpty())
+ ow.space().writeRegion(u"on", u"on").space().writeRegion(u"onTarget", onTarget).space();
+ ow.writeRegion(u"leftBrace", u" {").newline();
+ int baseIndent = ow.increaseIndent();
+ int spacerId = 0;
+ if (!idStr().isEmpty()) { // *always* put id first
+ DomItem myId = self.component().field(Fields::ids).key(idStr()).index(0);
+ if (myId)
+ myId.writeOutPre(ow);
+ ow.ensureNewline()
+ .writeRegion(u"idToken", u"id")
+ .writeRegion(u"idColon", u":")
+ .space()
+ .writeRegion(u"id", idStr());
+ if (myId)
+ myId.writeOutPost(ow);
+ }
+ quint32 counter = ow.counter();
+ DomItem component;
+ if (isRootObject)
+ component = self.containingObject();
+ auto startLoc = [](FileLocations::Tree l) {
+ if (l)
+ return l->info().fullRegion;
+ return SourceLocation(~quint32(0), 0, 0, 0);
+ };
+ if (ow.lineWriter.options().attributesSequence
+ == LineWriterOptions::AttributesSequence::Preserve) {
+ QList<QPair<SourceLocation, DomItem>> attribs;
+ AttachedInfoLookupResult<FileLocations::Tree> objLoc =
+ FileLocations::findAttachedInfo(self);
+ FileLocations::Tree componentLoc;
+ if (isRootObject && objLoc.foundTree)
+ componentLoc = objLoc.foundTree->parent()->parent();
+ auto addMMap = [&attribs, &startLoc](DomItem &base, FileLocations::Tree baseLoc) {
+ if (!base)
+ return;
+ for (auto els : base.values()) {
+ FileLocations::Tree elsLoc =
+ FileLocations::find(baseLoc, els.pathFromOwner().last());
+ for (auto el : els.values()) {
+ FileLocations::Tree elLoc =
+ FileLocations::find(elsLoc, el.pathFromOwner().last());
+ attribs.append(std::make_pair(startLoc(elLoc), el));
+ }
+ }
+ };
+ auto addMyMMap = [this, &objLoc, &self, &addMMap](QStringView fieldName) {
+ DomItem base = this->field(self, fieldName);
+ addMMap(base, FileLocations::find(objLoc.foundTree, base.pathFromOwner().last()));
+ };
+ auto addSingleLevel = [&attribs, &startLoc](DomItem &base, FileLocations::Tree baseLoc) {
+ if (!base)
+ return;
+ for (auto el : base.values()) {
+ FileLocations::Tree elLoc = FileLocations::find(baseLoc, el.pathFromOwner().last());
+ attribs.append(std::make_pair(startLoc(elLoc), el));
+ }
+ };
+ if (isRootObject) {
+ DomItem enums = component.field(Fields::enumerations);
+ addMMap(enums, FileLocations::find(componentLoc, enums.pathFromOwner().last()));
+ }
+ addMyMMap(Fields::propertyDefs);
+ addMyMMap(Fields::bindings);
+ addMyMMap(Fields::methods);
+ DomItem children = field(self, Fields::children);
+ addSingleLevel(children,
+ FileLocations::find(objLoc.foundTree, children.pathFromOwner().last()));
+ if (isRootObject) {
+ DomItem subCs = component.field(Fields::subComponents);
+ for (DomItem &c : subCs.values()) {
+ AttachedInfoLookupResult<FileLocations::Tree> subLoc =
+ FileLocations::findAttachedInfo(c);
+ Q_ASSERT(subLoc.foundTree);
+ attribs.append(std::make_pair(startLoc(subLoc.foundTree), c));
+ }
+ }
+ std::stable_sort(attribs.begin(), attribs.end(),
+ [](const std::pair<SourceLocation, DomItem> &el1,
+ const std::pair<SourceLocation, DomItem> &el2) {
+ if (el1.first.offset < el2.first.offset)
+ return true;
+ if (el1.first.offset > el2.first.offset)
+ return false;
+ int i = int(el1.second.internalKind())
+ - int(el2.second.internalKind());
+ return i < 0;
+ });
+ qsizetype iAttr = 0;
+ while (iAttr != attribs.size()) {
+ auto &el = attribs[iAttr++];
+ if (iAttr > 1 && el.second.internalKind() != attribs[iAttr - 2].second.internalKind())
+ ow.ensureNewline(2);
+ else
+ ow.ensureNewline();
+ if (el.second.internalKind() == DomType::PropertyDefinition && iAttr != attribs.size()
+ && el.first.offset != ~quint32(0)) {
+ DomItem b;
+ auto &bPair = attribs[iAttr];
+ if (bPair.second.internalKind() == DomType::Binding
+ && bPair.first.begin() < el.first.end()
+ && bPair.second.name() == el.second.name()) {
+ b = bPair.second;
+ ++iAttr;
+ b.writeOutPre(ow);
+ }
+ el.second.writeOut(ow);
+ if (b) {
+ ow.write(u": ");
+ if (const Binding *bPtr = b.as<Binding>())
+ bPtr->writeOutValue(b, ow);
+ else {
+ qWarning() << "Internal error casting binding to Binding in"
+ << b.canonicalPath();
+ ow.writeRegion(u"leftBrace", u"{").writeRegion(u"rightBrace", u"}");
+ }
+ b.writeOutPost(ow);
+ }
+ } else {
+ el.second.writeOut(ow);
+ }
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.ensureNewline().write(u"}");
+
+ return;
+ }
+ DomItem bindings = field(self, Fields::bindings);
+ DomItem propertyDefs = field(self, Fields::propertyDefs);
+
+ if (isRootObject) {
+ for (auto enumDescs : component.field(Fields::enumerations).values()) {
+ for (auto enumDesc : enumDescs.values()) {
+ ow.ensureNewline(1);
+ enumDesc.writeOut(ow);
+ ow.ensureNewline(1);
+ }
+ }
+ }
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto pDefs : propertyDefs.values()) {
+ for (auto pDef : pDefs.values()) {
+ DomItem b;
+ bindings.key(pDef.name()).visitIndexes([&b](DomItem &el) {
+ const Binding *elPtr = el.as<Binding>();
+ if (elPtr && elPtr->bindingType() == BindingType::Normal) {
+ b = el;
+ return false;
+ }
+ return true;
+ });
+ if (b)
+ b.writeOutPre(ow);
+ pDef.writeOut(ow);
+ if (b) {
+ ow.write(u": ");
+ if (const Binding *bPtr = b.as<Binding>())
+ bPtr->writeOutValue(b, ow);
+ else {
+ qWarning() << "Internal error casting binding to Binding in"
+ << b.canonicalPath();
+ ow.writeRegion(u"leftBrace", u"{").writeRegion(u"rightBrace", u"}");
+ }
+ b.writeOutPost(ow);
+ }
+ }
+ }
+ ow.removeTextAddCallback(spacerId);
+ // check more than the name?
+ QList<DomItem> normalBindings, signalHandlers, delayedBindings;
+ for (auto bName : bindings.sortedKeys()) {
+ bool skipFirstNormal = m_propertyDefs.contains(bName);
+ for (auto b : bindings.key(bName).values()) {
+ const Binding *bPtr = b.as<Binding>();
+ if (skipFirstNormal) {
+ if (bPtr && bPtr->bindingType() == BindingType::Normal) {
+ skipFirstNormal = false;
+ continue;
+ }
+ }
+ if (bPtr->valueKind() == BindingValueKind::Array
+ || bPtr->valueKind() == BindingValueKind::Object)
+ delayedBindings.append(b);
+ else if (b.field(Fields::isSignalHandler).value().toBool(false))
+ signalHandlers.append(b);
+ else
+ normalBindings.append(b);
+ }
+ }
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto b : normalBindings)
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto ms : field(self, Fields::methods).values()) {
+ for (auto m : ms.values()) {
+ ow.ensureNewline();
+ m.writeOut(ow);
+ ow.ensureNewline();
+ }
+ }
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto b : signalHandlers)
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto c : field(self, Fields::children).values()) {
+ ow.ensureNewline();
+ c.writeOut(ow);
+ }
+ ow.removeTextAddCallback(spacerId);
+ if (isRootObject) {
+ // we are a root object, possibly add components
+ DomItem subComps = component.field(Fields::subComponents);
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto subC : subComps.values()) {
+ ow.ensureNewline();
+ subC.writeOut(ow);
+ }
+ ow.removeTextAddCallback(spacerId);
+ }
+ if (counter != ow.counter())
+ spacerId = ow.addNewlinesAutospacerCallback(2);
+ for (auto b : delayedBindings)
+ b.writeOut(ow);
+ ow.removeTextAddCallback(spacerId);
+ ow.decreaseIndent(1, baseIndent);
+ ow.ensureNewline().write(u"}");
+}
+
Binding::Binding(QString name, std::unique_ptr<BindingValue> value, BindingType bindingType)
: m_bindingType(bindingType), m_name(name), m_value(std::move(value))
{
@@ -761,6 +1046,48 @@ void Binding::updatePathFromOwner(Path newPath)
updatePathFromOwnerQList(m_annotations, newPath.field(Fields::annotations));
}
+void Binding::writeOut(DomItem &self, OutWriter &lw) const
+{
+ lw.ensureNewline();
+ if (m_bindingType == BindingType::Normal) {
+ lw.writeRegion(u"name", name());
+ lw.writeRegion(u"colon", u":").space();
+ writeOutValue(self, lw);
+ } else {
+ DomItem v = valueItem(self);
+ if (const QmlObject *vPtr = v.as<QmlObject>()) {
+ v.writeOutPre(lw);
+ vPtr->writeOut(v, lw, name());
+ v.writeOutPost(lw);
+ } else {
+ qCWarning(writeOutLog()) << "On Binding requires an QmlObject Value, not "
+ << v.internalKindStr() << " at " << self.canonicalPath();
+ }
+ }
+}
+
+void Binding::writeOutValue(DomItem &self, OutWriter &lw) const
+{
+ DomItem v = valueItem(self);
+ switch (valueKind()) {
+ case BindingValueKind::Empty:
+ qCWarning(writeOutLog()) << "Writing of empty binding " << name();
+ lw.write(u"{}");
+ break;
+ case BindingValueKind::Array:
+ if (const List *vPtr = v.as<List>()) {
+ v.writeOutPre(lw);
+ vPtr->writeOut(v, lw, false);
+ v.writeOutPost(lw);
+ }
+ break;
+ case BindingValueKind::Object:
+ case BindingValueKind::ScriptExpression:
+ v.writeOut(lw);
+ break;
+ }
+}
+
bool QmltypesComponent::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
{
bool cont = Component::iterateDirectSubpaths(self, visitor);
@@ -852,6 +1179,22 @@ Path EnumDecl::addAnnotation(const QmlObject &annotation, QmlObject **aPtr)
annotation, aPtr);
}
+void EnumDecl::writeOut(DomItem &self, OutWriter &ow) const
+{
+ ow.writeRegion(u"enum", u"enum")
+ .space()
+ .writeRegion(u"name", name())
+ .space()
+ .writeRegion(u"lbrace", u"{");
+ int iLevel = ow.increaseIndent(1);
+ for (auto value : self.field(Fields::values).values()) {
+ ow.ensureNewline();
+ value.writeOut(ow);
+ }
+ ow.decreaseIndent(1, iLevel);
+ ow.ensureNewline().writeRegion(u"rbrace", u"}");
+}
+
QList<Path> ImportScope::allSources(DomItem &self) const
{
DomItem env = self.environment();
@@ -1195,6 +1538,32 @@ QString ScriptExpression::astRelocatableDump() const
});
}
+void ScriptExpression::writeOut(DomItem &self, OutWriter &lw) const
+{
+ OutWriter *ow = &lw;
+
+ std::optional<PendingSourceLocationId> codeLoc;
+ if (lw.lineWriter.options().updateOptions & LineWriterOptions::Update::Expressions)
+ codeLoc = lw.lineWriter.startSourceLocation([this, self, ow](SourceLocation myLoc) mutable {
+ QStringView reformattedCode =
+ QStringView(ow->writtenStr).mid(myLoc.offset, myLoc.length);
+ if (reformattedCode != code()) {
+ std::shared_ptr<ScriptExpression> copy =
+ copyWithUpdatedCode(self, reformattedCode.toString());
+ ow->addReformattedScriptExpression(self.canonicalPath(), copy);
+ }
+ });
+ reformatAst(
+ lw, m_astComments,
+ [this](SourceLocation astL) {
+ SourceLocation l = this->locationToLocal(astL); // use engine->code() instead?
+ return this->code().mid(l.offset, l.length);
+ },
+ ast());
+ if (codeLoc)
+ lw.lineWriter.endSourceLocation(*codeLoc);
+}
+
SourceLocation ScriptExpression::globalLocation(DomItem &self) const
{
if (const FileLocations *fLocPtr = FileLocations::fileLocationsPtr(self)) {
@@ -1203,6 +1572,22 @@ SourceLocation ScriptExpression::globalLocation(DomItem &self) const
return SourceLocation();
}
+void PropertyDefinition::writeOut(DomItem &, OutWriter &lw) const
+{
+ lw.ensureNewline();
+ if (isDefaultMember)
+ lw.writeRegion(u"default").space();
+ if (isRequired)
+ lw.writeRegion(u"required").space();
+ if (isReadonly)
+ lw.writeRegion(u"readonly").space();
+ if (!typeName.isEmpty()) {
+ lw.writeRegion(u"property").space();
+ lw.writeRegion(u"type", typeName).space();
+ }
+ lw.writeRegion(u"name", name);
+}
+
bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
{
bool cont = AttributeInfo::iterateDirectSubpaths(self, visitor);
@@ -1219,9 +1604,20 @@ bool MethodInfo::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
return cont;
}
-QString MethodInfo::preCode(DomItem &) const
+QString MethodInfo::preCode(DomItem &self) const
{
- return QLatin1String("function(){");
+ QString res;
+ LineWriter lw([&res](QStringView s) { res.append(s); }, QLatin1String("*preCode*"));
+ OutWriter ow(lw);
+ ow.indentNextlines = true;
+ ow.skipComments = true;
+ MockObject standinObj(self.pathFromOwner());
+ DomItem standin = self.copy(&standinObj);
+ ow.itemStart(standin);
+ writePre(self, ow);
+ ow.itemEnd(standin);
+ ow.eof();
+ return res;
}
QString MethodInfo::postCode(DomItem &) const
@@ -1229,6 +1625,65 @@ QString MethodInfo::postCode(DomItem &) const
return QLatin1String("\n}\n");
}
+void MethodInfo::writePre(DomItem &self, OutWriter &ow) const
+{
+ ow.writeRegion(u"function").space().writeRegion(u"name", name);
+ bool first = true;
+ ow.writeRegion(u"leftParen", u"(");
+ index_type idx = 0;
+ for (const MethodParameter &mp : parameters) {
+ DomItem arg = self.copy(SimpleObjectWrap::fromObjectRef<MethodParameter &>(
+ self.pathFromOwner().field(Fields::parameters).index(idx++),
+ *const_cast<MethodParameter *>(&mp)));
+ if (first)
+ first = false;
+ else
+ ow.write(u", ");
+ arg.writeOut(ow);
+ }
+ ow.writeRegion(u"leftParen", u")");
+ ow.ensureSpace().writeRegion(u"leftBrace", u"{");
+}
+
+void MethodInfo::writeOut(DomItem &self, OutWriter &ow) const
+{
+ switch (methodType) {
+ case MethodType::Signal: {
+ if (body)
+ qCWarning(domLog) << "signal should not have a body in" << self.canonicalPath();
+ ow.writeRegion(u"signal").space().writeRegion(u"name", name);
+ if (parameters.isEmpty())
+ return;
+ bool first = true;
+ ow.writeRegion(u"leftParen", u"(");
+ int baseIndent = ow.increaseIndent();
+ for (DomItem arg : self.field(Fields::parameters).values()) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ");
+ if (const MethodParameter *argPtr = arg.as<MethodParameter>())
+ argPtr->writeOutSignal(arg, ow);
+ else
+ qCWarning(domLog) << "failed to cast to MethodParameter";
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(u"leftParen", u")");
+ return;
+ } break;
+ case MethodType::Method: {
+ writePre(self, ow);
+ int baseIndent = ow.increaseIndent();
+ if (DomItem b = self.field(Fields::body)) {
+ ow.ensureNewline();
+ b.writeOut(ow);
+ }
+ ow.decreaseIndent(1, baseIndent);
+ ow.ensureNewline().writeRegion(u"rightBrace", u"}");
+ } break;
+ }
+}
+
bool MethodParameter::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
{
bool cont = true;
@@ -1248,6 +1703,33 @@ bool MethodParameter::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor
return cont;
}
+void MethodParameter::writeOut(DomItem &self, OutWriter &ow) const
+{
+ ow.writeRegion(u"name", name);
+ if (!typeName.isEmpty())
+ ow.writeRegion(u"colon", u":").space().writeRegion(u"type", typeName);
+ if (defaultValue) {
+ ow.space().writeRegion(u"equal", u"=").space();
+ self.subOwnerItem(PathEls::Field(Fields::defaultValue), defaultValue).writeOut(ow);
+ }
+}
+
+void MethodParameter::writeOutSignal(DomItem &self, OutWriter &ow) const
+{
+ self.writeOutPre(ow);
+ if (!typeName.isEmpty())
+ ow.writeRegion(u"type", typeName).space();
+ ow.writeRegion(u"name", name);
+ self.writeOutPost(ow);
+}
+
+void Pragma::writeOut(DomItem &, OutWriter &ow) const
+{
+ ow.ensureNewline();
+ ow.writeRegion(u"pragma").space().writeRegion(u"name", name);
+ ow.ensureNewline();
+}
+
bool EnumItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
{
bool cont = true;
@@ -1257,6 +1739,32 @@ bool EnumItem::iterateDirectSubpaths(DomItem &self, DirectVisitor visitor)
return cont;
}
+void EnumItem::writeOut(DomItem &self, OutWriter &ow) const
+{
+ ow.ensureNewline();
+ ow.writeRegion(u"name", name());
+ bool hasDefaultValue = false;
+ index_type myIndex = self.pathFromOwner().last().headIndex();
+ if (myIndex == 0)
+ hasDefaultValue = value() == 0;
+ else if (myIndex > 0)
+ hasDefaultValue = value()
+ == self.container()
+ .index(myIndex - 1)
+ .field(Fields::value)
+ .value()
+ .toDouble(value())
+ + 1;
+ if (!hasDefaultValue) {
+ QString v = QString::number(value(), 'f', 0);
+ if (abs(value() - v.toDouble()) > 1.e-10)
+ v = QString::number(value());
+ ow.space().writeRegion(u"equal", u"=").space().writeRegion(u"value", v);
+ }
+ if (myIndex >= 0 && self.container().indexes() != myIndex + 1)
+ ow.writeRegion(u"comma", u",");
+}
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h
index 66b568d871..1cb3737b80 100644
--- a/src/qmldom/qqmldomelements_p.h
+++ b/src/qmldom/qqmldomelements_p.h
@@ -52,6 +52,7 @@
#include "qqmldomitem_p.h"
#include "qqmldomconstants_p.h"
#include "qqmldomcomments_p.h"
+#include "qqmldomlinewriter_p.h"
#include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljsengine_p.h>
@@ -166,41 +167,6 @@ inline Path loadInfoPath(Path el)
}
} // end namespace Paths
-class IndentInfo
-{
-public:
- QStringView string;
- QStringView trailingString;
- int nNewlines = 0;
- int column = 0;
-
- IndentInfo(QStringView line, int tabSize, int initialColumn = 0)
- {
- string = line;
- int fixup = 0;
- if (initialColumn < 0) // we do not want % of negative numbers
- fixup = (-initialColumn + tabSize - 1) / tabSize * tabSize;
- column = initialColumn + fixup;
- const QChar tab = QLatin1Char('\t');
- int iStart = 0;
- int len = line.length();
- for (int i = 0; i < len; i++) {
- if (line[i] == tab)
- column = ((column / tabSize) + 1) * tabSize;
- else if (line[i] == QLatin1Char('\n')
- || (line[i] == QLatin1Char('\r')
- && (i + 1 == len || line[i + 1] != QLatin1Char('\n')))) {
- iStart = i + 1;
- ++nNewlines;
- column = 0;
- } else if (!line[i].isLowSurrogate())
- column++;
- }
- column -= fixup;
- trailingString = line.mid(iStart);
- }
-};
-
class QMLDOM_EXPORT CommentableDomElement : public DomElement
{
public:
@@ -338,6 +304,8 @@ public:
}
friend bool operator!=(const Import &i1, const Import &i2) { return !(i1 == i2); }
+ void writeOut(DomItem &self, OutWriter &ow) const;
+
static QRegularExpression importRe();
QString uri;
@@ -387,6 +355,8 @@ public:
return cont;
}
+ void writeOut(DomItem &self, OutWriter &ow) const;
+
QString name;
RegionComments comments;
};
@@ -494,6 +464,7 @@ public:
return m_engine;
}
std::shared_ptr<AstComments> astComments() const { return m_astComments; }
+ void writeOut(DomItem &self, OutWriter &lw) const override;
SourceLocation globalLocation(DomItem &self) const;
SourceLocation localOffset() const { return m_localOffset; }
QStringView preCode() const { return m_preCode; }
@@ -583,6 +554,8 @@ public:
const RegionComments &comments() const { return m_comments; }
RegionComments &comments() { return m_comments; }
void updatePathFromOwner(Path newPath);
+ void writeOut(DomItem &self, OutWriter &lw) const;
+ void writeOutValue(DomItem &self, OutWriter &lw) const;
bool isSignalHandler() const
{
QString baseName = m_name.split(QLatin1Char('.')).last();
@@ -649,6 +622,8 @@ public:
return res;
}
+ void writeOut(DomItem &self, OutWriter &lw) const;
+
bool isPointer = false;
bool isAlias = false;
bool isDefaultMember = false;
@@ -673,6 +648,9 @@ public:
bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor);
+ void writeOut(DomItem &self, OutWriter &ow) const;
+ void writeOutSignal(DomItem &self, OutWriter &ow) const;
+
QString name;
QString typeName;
bool isPointer = false;
@@ -700,6 +678,8 @@ public:
bool iterateDirectSubpaths(DomItem &self, DirectVisitor visitor);
QString preCode(DomItem &) const;
QString postCode(DomItem &) const;
+ void writePre(DomItem &self, OutWriter &ow) const;
+ void writeOut(DomItem &self, OutWriter &ow) const;
void setCode(QString code)
{
body = std::shared_ptr<ScriptExpression>(
@@ -727,6 +707,7 @@ public:
double value() const { return m_value; }
RegionComments &comments() { return m_comments; }
const RegionComments &comments() const { return m_comments; }
+ void writeOut(DomItem &self, OutWriter &lw) const;
private:
QString m_name;
@@ -767,6 +748,7 @@ public:
QList<QmlObject> annotations() const;
void setAnnotations(QList<QmlObject> annotations);
Path addAnnotation(const QmlObject &child, QmlObject **cPtr = nullptr);
+ void writeOut(DomItem &self, OutWriter &lw) const override;
private:
QString m_name;
@@ -878,6 +860,8 @@ public:
return appendUpdatableElementInQList(pathFromOwner().field(Fields::annotations),
m_annotations, annotation, aPtr);
}
+ void writeOut(DomItem &self, OutWriter &ow, QString onTarget) const;
+ void writeOut(DomItem &self, OutWriter &lw) const override { writeOut(self, lw, QString()); }
private:
friend class QmlDomAstCreator;
@@ -1047,6 +1031,7 @@ public:
return insertUpdatableElementInMultiMap(pathFromOwner().field(Fields::ids), m_ids, id.name,
id, option, idPtr);
}
+ void writeOut(DomItem &self, OutWriter &) const override;
QList<QString> subComponentsNames(DomItem &self) const;
QList<DomItem> subComponents(DomItem &self) const;
diff --git a/src/qmldom/qqmldomexternalitems.cpp b/src/qmldom/qqmldomexternalitems.cpp
index ea884fb5c0..f7d08a266d 100644
--- a/src/qmldom/qqmldomexternalitems.cpp
+++ b/src/qmldom/qqmldomexternalitems.cpp
@@ -38,6 +38,7 @@
#include "qqmldomexternalitems_p.h"
#include "qqmldomtop_p.h"
+#include "qqmldomoutwriter_p.h"
#include "qqmldomcomments_p.h"
#include "qqmldommock_p.h"
#include "qqmldomelements_p.h"
@@ -359,6 +360,19 @@ void QmlFile::addError(DomItem &self, ErrorMessage msg)
self.containingObject().addError(msg);
}
+void QmlFile::writeOut(DomItem &self, OutWriter &ow) const
+{
+ for (DomItem &p : self.field(Fields::pragmas).values()) {
+ p.writeOut(ow);
+ }
+ for (auto i : self.field(Fields::imports).values()) {
+ i.writeOut(ow);
+ }
+ ow.ensureNewline(2);
+ DomItem mainC = self.field(Fields::components).key(QString()).index(0);
+ mainC.writeOut(ow);
+}
+
std::shared_ptr<OwningItem> GlobalScope::doCopy(DomItem &self) const
{
std::shared_ptr<GlobalScope> res(
diff --git a/src/qmldom/qqmldomexternalitems_p.h b/src/qmldom/qqmldomexternalitems_p.h
index e9de497c3c..8e7b185039 100644
--- a/src/qmldom/qqmldomexternalitems_p.h
+++ b/src/qmldom/qqmldomexternalitems_p.h
@@ -306,6 +306,8 @@ public:
component, option, cPtr);
}
+ void writeOut(DomItem &self, OutWriter &lw) const override;
+
AST::UiProgram *ast() const
{
return m_ast; // avoid making it public? would make moving away from it easier
diff --git a/src/qmldom/qqmldomitem.cpp b/src/qmldom/qqmldomitem.cpp
index a2bffdb06d..bc099d75ca 100644
--- a/src/qmldom/qqmldomitem.cpp
+++ b/src/qmldom/qqmldomitem.cpp
@@ -41,9 +41,12 @@
#include "qqmldomexternalitems_p.h"
#include "qqmldommock_p.h"
#include "qqmldomastdumper_p.h"
+#include "qqmldomoutwriter_p.h"
#include "qqmldomfilewriter_p.h"
#include "qqmldomfieldfilter_p.h"
#include "qqmldomcompare_p.h"
+#include "qqmldomastdumper_p.h"
+#include "qqmldomlinewriter_p.h"
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@@ -52,6 +55,7 @@
#include <QtQml/private/qqmljsast_p.h>
#include <QtCore/QDebug>
+#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QPair>
@@ -70,6 +74,7 @@ QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
+Q_LOGGING_CATEGORY(writeOutLog, "qt.qmldom.writeOut", QtWarningMsg);
static Q_LOGGING_CATEGORY(refLog, "qt.qmldom.ref", QtWarningMsg);
using std::shared_ptr;
@@ -222,6 +227,12 @@ QString DomBase::canonicalFilePath(DomItem &self) const
return QString();
}
+void DomBase::writeOut(DomItem &self, OutWriter &) const
+{
+ qCWarning(writeOutLog) << "Ignoring unsupported writeOut for " << domTypeToString(kind()) << ":"
+ << self.canonicalPath();
+}
+
ConstantData::ConstantData(Path pathFromOwner, QCborValue value, Options options)
: DomElement(pathFromOwner), m_value(value), m_options(options)
{}
@@ -291,7 +302,6 @@ DomKind ConstantData::domKind() const
return DomKind::List;
return DomKind::Value;
}
-
/*!
\internal
\class QQmlJS::Dom::DomItem
@@ -1054,12 +1064,187 @@ QList<DomItem> DomItem::values()
return res;
}
+void DomItem::writeOutPre(OutWriter &ow)
+{
+ if (hasAnnotations()) {
+ DomItem anns = field(Fields::annotations);
+ for (auto ann : anns.values()) {
+ if (ann.annotations().indexes() == 0) {
+ ow.ensureNewline();
+ ann.writeOut(ow);
+ ow.ensureNewline();
+ } else {
+ DomItem annAnn = ann.annotations();
+ Q_ASSERT_X(annAnn.indexes() == 1 && annAnn.index(0).name() == u"duplicate",
+ "DomItem::writeOutPre", "Unexpected annotation of annotation");
+ }
+ }
+ }
+ ow.itemStart(*this);
+}
+
+void DomItem::writeOut(OutWriter &ow)
+{
+ writeOutPre(ow);
+ visitEl([this, &ow](auto &&el) { el->writeOut(*this, ow); });
+ writeOutPost(ow);
+}
+
+void DomItem::writeOutPost(OutWriter &ow)
+{
+ ow.itemEnd(*this);
+}
+
+DomItem DomItem::writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks)
+{
+ ow.indentNextlines = true;
+ writeOut(ow);
+ ow.eof();
+ DomItem fObj = fileObject();
+ DomItem copy = ow.updatedFile(fObj);
+ if (extraChecks & WriteOutCheck::All) {
+ QStringList dumped;
+ auto maybeDump = [&ow, extraChecks, &dumped](DomItem &obj, QStringView objName) {
+ QString objDumpPath;
+ if (extraChecks & WriteOutCheck::DumpOnFailure) {
+ objDumpPath = QDir(QDir::tempPath())
+ .filePath(objName.toString()
+ + QFileInfo(ow.lineWriter.fileName()).baseName()
+ + QLatin1String(".dump.json"));
+ obj.dump(objDumpPath);
+ dumped.append(objDumpPath);
+ }
+ return objDumpPath;
+ };
+ auto dumpedDumper = [&dumped](Sink s) {
+ if (dumped.isEmpty())
+ return;
+ s(u"\ndump: ");
+ for (auto dumpPath : dumped) {
+ s(u" ");
+ sinkEscaped(s, dumpPath);
+ }
+ };
+ auto compare = [&maybeDump, &dumpedDumper, this](DomItem &obj1, QStringView obj1Name,
+ DomItem &obj2, QStringView obj2Name,
+ const FieldFilter &filter) {
+ if (!domCompareStrList(obj1, obj2, filter).isEmpty()) {
+ maybeDump(obj1, obj1Name);
+ maybeDump(obj2, obj2Name);
+ qCWarning(writeOutLog).noquote().nospace()
+ << obj2Name << " writeOut of " << this->canonicalFilePath()
+ << " has changes:\n"
+ << domCompareStrList(obj1, obj2, filter, DomCompareStrList::AllDiffs)
+ .join(QString())
+ << dumpedDumper;
+ return false;
+ }
+ return true;
+ };
+ auto checkStability = [&maybeDump, &dumpedDumper, &dumped, &ow,
+ this](QString expected, DomItem &obj, QStringView objName) {
+ LineWriter lw2([](QStringView) {}, ow.lineWriter.fileName(), ow.lineWriter.options());
+ OutWriter ow2(lw2);
+ ow2.indentNextlines = true;
+ obj.writeOut(ow2);
+ ow2.eof();
+ if (ow2.writtenStr != expected) {
+ DomItem fObj = this->fileObject();
+ maybeDump(fObj, u"initial");
+ maybeDump(obj, objName);
+ qCWarning(writeOutLog).noquote().nospace()
+ << objName << " non stable writeOut of " << this->canonicalFilePath() << ":"
+ << lineDiff(ow2.writtenStr, expected, 2) << dumpedDumper;
+ dumped.clear();
+ return false;
+ }
+ return true;
+ };
+ if ((extraChecks & WriteOutCheck::UpdatedDomCompare)
+ && !compare(fObj, u"initial", copy, u"reformatted", FieldFilter::noLocationFilter()))
+ return DomItem();
+ if (extraChecks & WriteOutCheck::UpdatedDomStable)
+ checkStability(ow.writtenStr, copy, u"reformatted");
+ if (extraChecks
+ & (WriteOutCheck::Reparse | WriteOutCheck::ReparseCompare
+ | WriteOutCheck::ReparseStable)) {
+ DomItem newEnv = environment().makeCopy().item();
+ if (std::shared_ptr<DomEnvironment> newEnvPtr = newEnv.ownerAs<DomEnvironment>()) {
+ std::shared_ptr<QmlFile> newFilePtr(
+ new QmlFile(canonicalFilePath(), ow.writtenStr));
+ newEnvPtr->addQmlFile(newFilePtr, AddOption::Overwrite);
+ DomItem newFile = newEnv.copy(newFilePtr, Path());
+ if (newFilePtr->isValid()) {
+ if (extraChecks
+ & (WriteOutCheck::ReparseCompare | WriteOutCheck::ReparseStable)) {
+ MutableDomItem newFileMutable(newFile);
+ createDom(newFileMutable);
+ if ((extraChecks & WriteOutCheck::ReparseCompare)
+ && !compare(copy, u"reformatted", newFile, u"reparsed",
+ FieldFilter::compareNoCommentsFilter()))
+ return DomItem();
+ if ((extraChecks & WriteOutCheck::ReparseStable))
+ checkStability(ow.writtenStr, newFile, u"reparsed");
+ }
+ } else {
+ qCWarning(writeOutLog).noquote().nospace()
+ << "writeOut of " << canonicalFilePath()
+ << " created invalid code:\n----------\n"
+ << ow.writtenStr << "\n----------" << [&newFile](Sink s) {
+ newFile.iterateErrors(
+ [s](DomItem, ErrorMessage msg) {
+ s(u"\n ");
+ msg.dump(s);
+ return true;
+ },
+ true);
+ s(u"\n"); // extra empty line at the end...
+ };
+ return DomItem();
+ }
+ }
+ }
+ }
+ return copy;
+}
+
+DomItem DomItem::writeOut(QString path, int nBackups, const LineWriterOptions &options,
+ FileWriter *fw, WriteOutChecks extraChecks)
+{
+ DomItem res = *this;
+ DomItem copy;
+ FileWriter localFw;
+ if (!fw)
+ fw = &localFw;
+ switch (fw->write(
+ path,
+ [this, path, &copy, &options, extraChecks](QTextStream &ts) {
+ LineWriter lw([&ts](QStringView s) { ts << s; }, path, options);
+ OutWriter ow(lw);
+ copy = writeOutForFile(ow, extraChecks);
+ return bool(copy);
+ },
+ nBackups)) {
+ case FileWriter::Status::ShouldWrite:
+ case FileWriter::Status::SkippedDueToFailure:
+ qCWarning(writeOutLog) << "failure reformatting " << path;
+ break;
+ case FileWriter::Status::DidWrite:
+ case FileWriter::Status::SkippedEqual:
+ res = copy;
+ break;
+ }
+ return res;
+}
+
bool DomItem::isCanonicalChild(DomItem &item)
{
if (item.isOwningItem()) {
return canonicalPath() == item.canonicalPath().dropTail();
} else {
- return item.owner() == owner() && item.pathFromOwner().dropTail() == pathFromOwner();
+ DomItem itemOw = item.owner();
+ DomItem selfOw = owner();
+ return itemOw == selfOw && item.pathFromOwner().dropTail() == pathFromOwner();
}
}
@@ -2410,6 +2595,30 @@ DomItem List::index(DomItem &self, index_type index) const
return m_lookup(self, index);
}
+void List::writeOut(DomItem &self, OutWriter &ow, bool compact) const
+{
+ ow.writeRegion(u"leftSquareBrace", u"[");
+ int baseIndent = ow.increaseIndent(1);
+ bool first = true;
+ const_cast<List *>(this)->iterateDirectSubpaths(
+ self,
+ [&ow, &first, compact](const PathEls::PathComponent &, function_ref<DomItem()> elF) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ", LineWriter::TextAddType::Extra);
+ if (!compact)
+ ow.ensureNewline(1);
+ DomItem el = elF();
+ el.writeOut(ow);
+ return true;
+ });
+ if (!compact && !first)
+ ow.newline();
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(u"rightSquareBrace", u"]");
+}
+
DomElement::DomElement(Path pathFromOwner) : m_pathFromOwner(pathFromOwner) { }
Path DomElement::pathFromOwner(DomItem &) const
@@ -3133,6 +3342,28 @@ bool ListPBase::iterateDirectSubpaths(DomItem &self, DirectVisitor v)
return true;
}
+void ListPBase::writeOut(DomItem &self, OutWriter &ow, bool compact) const
+{
+ ow.writeRegion(u"leftSquareBrace", u"[");
+ int baseIndent = ow.increaseIndent(1);
+ bool first = true;
+ index_type len = index_type(m_pList.size());
+ for (index_type i = 0; i < len; ++i) {
+ if (first)
+ first = false;
+ else
+ ow.write(u", ", LineWriter::TextAddType::Extra);
+ if (!compact)
+ ow.ensureNewline(1);
+ DomItem el = index(self, i);
+ el.writeOut(ow);
+ }
+ if (!compact && !first)
+ ow.newline();
+ ow.decreaseIndent(1, baseIndent);
+ ow.writeRegion(u"rightSquareBrace", u"]");
+}
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomitem_p.h b/src/qmldom/qqmldomitem_p.h
index 5dc9ac8c43..469001709c 100644
--- a/src/qmldom/qqmldomitem_p.h
+++ b/src/qmldom/qqmldomitem_p.h
@@ -57,6 +57,7 @@
#include "qqmldomerrormessage_p.h"
#include "qqmldomfunctionref_p.h"
#include "qqmldomfilewriter_p.h"
+#include "qqmldomlinewriter_p.h"
#include <QtCore/QMap>
#include <QtCore/QMultiMap>
@@ -85,6 +86,8 @@ namespace Dom {
class Path;
+Q_DECLARE_LOGGING_CATEGORY(writeOutLog);
+
constexpr bool domTypeIsObjWrap(DomType k);
constexpr bool domTypeIsValueWrap(DomType k);
constexpr bool domTypeIsDomElement(DomType);
@@ -257,6 +260,8 @@ public:
virtual QString canonicalFilePath(DomItem &self) const;
+ virtual void writeOut(DomItem &self, OutWriter &lw) const;
+
virtual QCborValue value() const {
return QCborValue();
}
@@ -385,6 +390,8 @@ public:
fromQListRef(Path pathFromOwner, QList<T> &list,
std::function<DomItem(DomItem &, const PathEls::PathComponent &, T &)> elWrapper,
ListOptions options = ListOptions::Normal);
+ void writeOut(DomItem &self, OutWriter &ow, bool compact) const;
+ void writeOut(DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); }
private:
LookupFunction m_lookup;
@@ -408,6 +415,8 @@ public:
virtual void moveTo(ListPBase *) const { Q_ASSERT(false); };
quintptr id() const override { return quintptr(0); }
index_type indexes(DomItem &) const override { return index_type(m_pList.size()); }
+ void writeOut(DomItem &self, OutWriter &ow, bool compact) const;
+ void writeOut(DomItem &self, OutWriter &ow) const override { writeOut(self, ow, true); }
protected:
QList<void *> m_pList;
@@ -527,7 +536,6 @@ public:
return m_value.value<T *>();
}
}
-
SimpleObjectWrapBase() = delete;
virtual void copyTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); }
virtual void moveTo(SimpleObjectWrapBase *) const { Q_ASSERT(false); }
@@ -569,6 +577,8 @@ public:
return mutableAsT()->iterateDirectSubpaths(self, visitor);
}
+ void writeOut(DomItem &self, OutWriter &lw) const override;
+
T const *asT() const
{
if constexpr (domTypeIsValueWrap(T::kindValue)) {
@@ -840,6 +850,13 @@ public:
bool visitKeys(function_ref<bool(QString, DomItem &)> visitor);
QList<DomItem> values();
+ void writeOutPre(OutWriter &lw);
+ void writeOut(OutWriter &lw);
+ void writeOutPost(OutWriter &lw);
+ DomItem writeOutForFile(OutWriter &ow, WriteOutChecks extraChecks);
+ DomItem writeOut(QString path, int nBackups = 2,
+ const LineWriterOptions &opt = LineWriterOptions(), FileWriter *fw = nullptr,
+ WriteOutChecks extraChecks = WriteOutCheck::Default);
bool visitTree(Path basePath, ChildrenVisitor visitor,
VisitOptions options = VisitOption::Default,
@@ -1356,6 +1373,41 @@ std::shared_ptr<T> DomItem::ownerAs()
return std::shared_ptr<T> {};
}
+template<int I>
+struct rank : rank<I - 1>
+{
+ static_assert(I > 0, "");
+};
+template<>
+struct rank<0>
+{
+};
+
+template<typename T>
+auto writeOutWrap(const T &t, DomItem &self, OutWriter &lw, rank<1>)
+ -> decltype(t.writeOut(self, lw))
+{
+ t.writeOut(self, lw);
+}
+
+template<typename T>
+auto writeOutWrap(const T &, DomItem &, OutWriter &, rank<0>) -> void
+{
+ qCWarning(writeOutLog) << "Ignoring writeout to wrapped object not supporting it ("
+ << typeid(T).name();
+}
+template<typename T>
+auto writeOutWrap(const T &t, DomItem &self, OutWriter &lw) -> void
+{
+ writeOutWrap(t, self, lw, rank<1>());
+}
+
+template<typename T>
+void SimpleObjectWrapT<T>::writeOut(DomItem &self, OutWriter &lw) const
+{
+ writeOutWrap<T>(*asT(), self, lw);
+}
+
QDebug operator<<(QDebug debug, const DomItem &c);
class MutableDomItem {
@@ -1454,6 +1506,13 @@ public:
{
return item().dump(path, filter, nBackups, indent, fw);
}
+ void writeOut(OutWriter &lw) { return item().writeOut(lw); }
+ MutableDomItem writeOut(QString path, int nBackups = 2,
+ const LineWriterOptions &opt = LineWriterOptions(),
+ FileWriter *fw = nullptr)
+ {
+ return MutableDomItem(item().writeOut(path, nBackups, opt, fw));
+ }
MutableDomItem fileLocations() { return MutableDomItem(item().fileLocations()); }
MutableDomItem makeCopy(CopyOption option = CopyOption::EnvConnected)
diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp
new file mode 100644
index 0000000000..a2371c4ccd
--- /dev/null
+++ b/src/qmldom/qqmldomlinewriter.cpp
@@ -0,0 +1,482 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#include "qqmldomlinewriter_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QRegularExpression>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+quint32 PendingSourceLocation::utf16Start() const
+{
+ return value.offset;
+}
+
+quint32 PendingSourceLocation::utf16End() const
+{
+ return value.offset + value.length;
+}
+
+void PendingSourceLocation::changeAtOffset(quint32 offset, qint32 change, qint32 colChange,
+ qint32 lineChange)
+{
+ if (offset < utf16Start()) {
+ if (change < 0 && offset - change >= utf16Start()) {
+ int c1 = offset - utf16Start();
+ int c2 = offset - change - utf16Start();
+ change = c1;
+ if (value.length < quint32(c2))
+ value.length = 0;
+ else
+ value.length -= c2;
+ }
+ value.offset += change;
+ value.startColumn += colChange;
+ value.startLine += lineChange;
+ } else if (offset < utf16End()) {
+ if (change < 0 && offset - change > utf16End())
+ change = offset - utf16End();
+ value.length += change;
+ }
+}
+
+void PendingSourceLocation::commit()
+{
+ if (toUpdate)
+ *toUpdate = value;
+ if (updater)
+ updater(value);
+}
+
+LineWriter::LineWriter(SinkF innerSink, QString fileName, const LineWriterOptions &options,
+ int lineNr, int columnNr, int utf16Offset, QString currentLine)
+ : m_innerSinks({ innerSink }),
+ m_fileName(fileName),
+ m_lineNr(lineNr),
+ m_columnNr(columnNr),
+ m_currentColumnNr(columnNr),
+ m_utf16Offset(utf16Offset),
+ m_currentLine(currentLine),
+ m_options(options)
+{
+}
+
+LineWriter &LineWriter::ensureNewline(int nNewline, TextAddType t)
+{
+ int nToAdd = nNewline;
+ if (nToAdd <= 0)
+ return *this;
+ if (m_currentLine.trimmed().isEmpty()) {
+ --nToAdd;
+ if (m_committedEmptyLines >= unsigned(nToAdd))
+ return *this;
+ nToAdd -= m_committedEmptyLines;
+ }
+ for (int i = 0; i < nToAdd; ++i)
+ write(u"\n", t);
+ return *this;
+}
+
+LineWriter &LineWriter::ensureSpace(TextAddType t)
+{
+ if (!m_currentLine.isEmpty() && !m_currentLine.at(m_currentLine.size() - 1).isSpace())
+ write(u" ", t);
+ return *this;
+}
+
+LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t)
+{
+ int tabSize = m_options.formatOptions.tabSize;
+ IndentInfo ind(space, tabSize);
+ auto cc = counter();
+ if (ind.nNewlines > 0)
+ ensureNewline(ind.nNewlines, t);
+ if (cc != counter() || m_currentLine.isEmpty()
+ || !m_currentLine.at(m_currentLine.length() - 1).isSpace())
+ write(ind.trailingString, t);
+ else {
+ int len = m_currentLine.length();
+ int i = len;
+ while (i != 0 && m_currentLine.at(i - 1).isSpace())
+ --i;
+ QStringView trailingSpace = QStringView(m_currentLine).mid(i, len - i);
+ int trailingSpaceStartColumn =
+ IndentInfo(QStringView(m_currentLine).mid(0, i), tabSize, m_columnNr).column;
+ IndentInfo indExisting(trailingSpace, tabSize, trailingSpaceStartColumn);
+ if (trailingSpaceStartColumn != 0)
+ ind = IndentInfo(space, tabSize, trailingSpaceStartColumn);
+ if (i == 0) {
+ if (indExisting.column < ind.column) {
+ qint32 utf16Change = ind.trailingString.length() - trailingSpace.length();
+ m_currentColumnNr += ind.trailingString.length() - trailingSpace.length();
+ m_currentLine.replace(
+ i, len - i, ind.trailingString.toString()); // invalidates most QStringViews
+ changeAtOffset(i, utf16Change, utf16Change, 0);
+ lineChanged();
+ }
+ } else if (indExisting.column < ind.column) { // use just spaces if not at start of a line
+ write(QStringLiteral(u" ").repeated(ind.column - indExisting.column), t);
+ }
+ }
+ return *this;
+}
+
+QString LineWriter::eolToWrite() const
+{
+ switch (m_options.lineEndings) {
+ case LineWriterOptions::LineEndings::Unix:
+ return QStringLiteral(u"\n");
+ case LineWriterOptions::LineEndings::Windows:
+ return QStringLiteral(u"\r\n");
+ case LineWriterOptions::LineEndings::OldMacOs:
+ return QStringLiteral(u"\r");
+ }
+ Q_ASSERT(false);
+ return QStringLiteral(u"\n");
+}
+
+LineWriter &LineWriter::write(QStringView v, TextAddType tAdd)
+{
+ QString eol;
+ // split multiple lines
+ static QRegularExpression eolRe(QLatin1String(
+ "(\r?\n|\r)")); // does not support split of \r and \n for windows style line endings
+ QRegularExpressionMatch m = eolRe.match(v);
+ if (m.hasMatch()) {
+ // add line by line
+ auto i = m.capturedStart(1);
+ auto iEnd = m.capturedEnd(1);
+ eol = eolToWrite();
+ // offset change (eol used vs input) cannot affect things,
+ // because we cannot have already opened or closed a PendingSourceLocation
+ if (iEnd < v.size()) {
+ write(v.mid(0, iEnd));
+ m = eolRe.match(v, iEnd);
+ while (m.hasMatch()) {
+ write(v.mid(iEnd, m.capturedEnd(1) - iEnd));
+ iEnd = m.capturedEnd(1);
+ m = eolRe.match(v, iEnd);
+ }
+ if (iEnd < v.size())
+ write(v.mid(iEnd, v.size() - iEnd));
+ return *this;
+ }
+ QStringView toAdd = v.mid(0, i);
+ if (!toAdd.trimmed().isEmpty())
+ textAddCallback(tAdd);
+ m_counter += i;
+ m_currentLine.append(toAdd);
+ m_currentColumnNr +=
+ IndentInfo(toAdd, m_options.formatOptions.tabSize, m_currentColumnNr).column;
+ lineChanged();
+ } else {
+ if (!v.trimmed().isEmpty())
+ textAddCallback(tAdd);
+ m_counter += v.size();
+ m_currentLine.append(v);
+ m_currentColumnNr +=
+ IndentInfo(v, m_options.formatOptions.tabSize, m_currentColumnNr).column;
+ lineChanged();
+ }
+ if (!eol.isEmpty()
+ || (m_options.maxLineLength > 0 && m_currentColumnNr > m_options.maxLineLength)) {
+ reindentAndSplit(eol);
+ }
+ return *this;
+}
+
+void LineWriter::flush()
+{
+ if (m_currentLine.size() > 0)
+ commitLine(QString());
+}
+
+void LineWriter::eof(bool shouldEnsureNewline)
+{
+ if (shouldEnsureNewline)
+ ensureNewline();
+ reindentAndSplit(QString(), true);
+}
+
+SourceLocation LineWriter::committedLocation() const
+{
+ return SourceLocation(m_utf16Offset, 0, m_lineNr, m_lineUtf16Offset);
+}
+
+PendingSourceLocationId LineWriter::startSourceLocation(SourceLocation *toUpdate)
+{
+ PendingSourceLocation res;
+ res.id = ++m_lastSourceLocationId;
+ res.value = currentSourceLocation();
+ res.toUpdate = toUpdate;
+ m_pendingSourceLocations.insert(res.id, res);
+ return res.id;
+}
+
+PendingSourceLocationId LineWriter::startSourceLocation(std::function<void(SourceLocation)> updater)
+{
+ PendingSourceLocation res;
+ res.id = ++m_lastSourceLocationId;
+ res.value = currentSourceLocation();
+ res.updater = updater;
+ m_pendingSourceLocations.insert(res.id, res);
+ return res.id;
+}
+
+void LineWriter::endSourceLocation(PendingSourceLocationId slId)
+{
+ if (m_pendingSourceLocations.contains(slId)) {
+ auto &pLoc = m_pendingSourceLocations[slId];
+ if (!pLoc.open) {
+ qWarning() << "Trying to close already closed PendingSourceLocation" << int(slId);
+ }
+ pLoc.open = false;
+ pLoc.value.length = m_utf16Offset + m_currentLine.size() - pLoc.value.offset;
+ } else {
+ qWarning() << "Trying to close non existing PendingSourceLocation" << int(slId);
+ }
+}
+
+int LineWriter::addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback)
+{
+ int nextId = ++m_lastCallbackId;
+ Q_ASSERT(nextId != 0);
+ if (callback)
+ m_textAddCallbacks.insert(nextId, callback);
+ return nextId;
+}
+
+int LineWriter::addNewlinesAutospacerCallback(int nLines)
+{
+ return addTextAddCallback([nLines](LineWriter &self, TextAddType t) {
+ if (t == TextAddType::Normal) {
+ quint32 c = self.counter();
+ QString spacesToPreserve;
+ bool spaceOnly = QStringView(self.m_currentLine).trimmed().isEmpty();
+ if (spaceOnly && !self.m_currentLine.isEmpty())
+ spacesToPreserve = self.m_currentLine;
+ self.ensureNewline(nLines, LineWriter::TextAddType::Extra);
+ if (self.counter() != c && !spacesToPreserve.isEmpty())
+ self.write(spacesToPreserve, TextAddType::Extra);
+ return false;
+ } else {
+ return true;
+ }
+ });
+}
+
+void LineWriter::setLineIndent(int indentAmount)
+{
+ int startNonSpace = 0;
+ while (startNonSpace < m_currentLine.size() && m_currentLine.at(startNonSpace).isSpace())
+ ++startNonSpace;
+ int oldColumn = column(startNonSpace);
+ if (indentAmount >= 0) {
+ QString indent;
+ if (m_options.formatOptions.useTabs) {
+ indent = QStringLiteral(u"\t").repeated(indentAmount / m_options.formatOptions.tabSize)
+ + QStringLiteral(u" ").repeated(indentAmount % m_options.formatOptions.tabSize);
+ } else {
+ indent = QStringLiteral(u" ").repeated(indentAmount);
+ }
+ if (indent != m_currentLine.mid(0, startNonSpace)) {
+ quint32 colChange = indentAmount - oldColumn;
+ m_currentColumnNr += colChange;
+ qint32 oChange = indent.size() - startNonSpace;
+ m_currentLine = indent + m_currentLine.mid(startNonSpace);
+ m_currentColumnNr = column(m_currentLine.size());
+ lineChanged();
+ changeAtOffset(m_utf16Offset, oChange, oChange, 0);
+ }
+ }
+}
+
+void LineWriter::handleTrailingSpace(LineWriterOptions::TrailingSpace trailingSpace)
+{
+ switch (trailingSpace) {
+ case LineWriterOptions::TrailingSpace::Preserve:
+ break;
+ case LineWriterOptions::TrailingSpace::Remove: {
+ int lastNonSpace = m_currentLine.size();
+ while (lastNonSpace > 0 && m_currentLine.at(lastNonSpace - 1).isSpace())
+ --lastNonSpace;
+ if (lastNonSpace != m_currentLine.size()) {
+ qint32 oChange = lastNonSpace - m_currentLine.size();
+ m_currentLine = m_currentLine.mid(0, lastNonSpace);
+ changeAtOffset(m_utf16Offset + lastNonSpace, oChange, oChange, 0);
+ m_currentColumnNr =
+ column(m_currentLine.size()); // to be extra accurate in the potential split
+ lineChanged();
+ }
+ } break;
+ }
+}
+
+void LineWriter::reindentAndSplit(QString eol, bool eof)
+{
+ // maybe write out
+ if (!eol.isEmpty() || eof) {
+ handleTrailingSpace(m_options.codeTrailingSpace);
+ commitLine(eol);
+ }
+}
+
+SourceLocation LineWriter::currentSourceLocation() const
+{
+ return SourceLocation(m_utf16Offset + m_currentLine.size(), 0, m_lineNr,
+ m_lineUtf16Offset + m_currentLine.size());
+}
+
+void LineWriter::changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange)
+{
+ auto iEnd = m_pendingSourceLocations.end();
+ auto i = m_pendingSourceLocations.begin();
+ while (i != iEnd) {
+ i.value().changeAtOffset(offset, change, colChange, lineChange);
+ ++i;
+ }
+}
+
+int LineWriter::column(int index)
+{
+ if (index > m_currentLine.length())
+ index = m_currentLine.length();
+ IndentInfo iInfo(QStringView(m_currentLine).mid(0, index), m_options.formatOptions.tabSize,
+ m_columnNr);
+ return iInfo.column;
+}
+
+void LineWriter::textAddCallback(LineWriter::TextAddType t)
+{
+ if (m_textAddCallbacks.isEmpty())
+ return;
+ int iNow = (--m_textAddCallbacks.end()).key() + 1;
+ while (true) {
+ auto it = m_textAddCallbacks.lowerBound(iNow);
+ if (it == m_textAddCallbacks.begin())
+ break;
+ --it;
+ iNow = it.key();
+ if (!it.value()(*this, t))
+ m_textAddCallbacks.erase(it);
+ }
+}
+
+void LineWriter::commitLine(QString eol, TextAddType tType, int untilChar)
+{
+ if (untilChar == -1)
+ untilChar = m_currentLine.size();
+ bool isSpaceOnly = QStringView(m_currentLine).mid(0, untilChar).trimmed().isEmpty();
+ bool isEmptyNewline = !eol.isEmpty() && isSpaceOnly;
+ quint32 endCommit = m_utf16Offset + untilChar;
+ // update positon, lineNr,...
+ // write out
+ for (SinkF &sink : m_innerSinks)
+ sink(m_currentLine.mid(0, untilChar));
+ m_utf16Offset += untilChar;
+ if (!eol.isEmpty()) {
+ m_utf16Offset += eol.size();
+ for (SinkF &sink : m_innerSinks)
+ sink(eol);
+ ++m_lineNr;
+ int oldCol = column(untilChar);
+ m_columnNr = 0;
+ m_lineUtf16Offset = 0;
+ changeAtOffset(m_utf16Offset, 0, -oldCol, 1);
+ } else {
+ m_columnNr = column(untilChar);
+ m_lineUtf16Offset += untilChar;
+ }
+ if (untilChar == m_currentLine.size()) {
+ willCommit();
+ m_currentLine.clear();
+ } else {
+ QString nextLine = m_currentLine.mid(untilChar);
+ m_currentLine = m_currentLine.mid(0, untilChar);
+ lineChanged();
+ willCommit();
+ m_currentLine = nextLine;
+ }
+ lineChanged();
+ m_currentColumnNr = column(m_currentLine.size());
+ TextAddType notifyType = tType;
+ switch (tType) {
+ case TextAddType::Normal:
+ if (eol.isEmpty())
+ notifyType = TextAddType::PartialCommit;
+ else
+ notifyType = TextAddType::Newline;
+ break;
+ case TextAddType::Extra:
+ if (eol.isEmpty())
+ notifyType = TextAddType::NewlineExtra;
+ else
+ notifyType = TextAddType::PartialCommit;
+ break;
+ case TextAddType::Newline:
+ case TextAddType::NewlineSplit:
+ case TextAddType::NewlineExtra:
+ case TextAddType::PartialCommit:
+ case TextAddType::Eof:
+ break;
+ }
+ if (isEmptyNewline)
+ ++m_committedEmptyLines;
+ else if (!isSpaceOnly)
+ m_committedEmptyLines = 0;
+ // commit finished pending
+ auto iEnd = m_pendingSourceLocations.end();
+ auto i = m_pendingSourceLocations.begin();
+ while (i != iEnd) {
+ auto &pLoc = i.value();
+ if (!pLoc.open && pLoc.utf16End() <= endCommit) {
+ pLoc.commit();
+ i = m_pendingSourceLocations.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ // notify
+ textAddCallback(notifyType);
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomlinewriter_p.h b/src/qmldom/qqmldomlinewriter_p.h
new file mode 100644
index 0000000000..91aff5ccfb
--- /dev/null
+++ b/src/qmldom/qqmldomlinewriter_p.h
@@ -0,0 +1,251 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QQMLDOMLINEWRITER_P
+#define QQMLDOMLINEWRITER_P
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+#include "qqmldomstringdumper_p.h"
+
+#include <QtQml/private/qqmljssourcelocation_p.h>
+#include <QtCore/QObject>
+#include <QtCore/QAtomicInt>
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+class IndentInfo
+{
+public:
+ QStringView string;
+ QStringView trailingString;
+ int nNewlines = 0;
+ int column = 0;
+
+ IndentInfo(QStringView line, int tabSize, int initialColumn = 0)
+ {
+ string = line;
+ int fixup = 0;
+ if (initialColumn < 0) // we do not want % of negative numbers
+ fixup = (-initialColumn + tabSize - 1) / tabSize * tabSize;
+ column = initialColumn + fixup;
+ const QChar tab = QLatin1Char('\t');
+ int iStart = 0;
+ int len = line.length();
+ for (int i = 0; i < len; i++) {
+ if (line[i] == tab)
+ column = ((column / tabSize) + 1) * tabSize;
+ else if (line[i] == QLatin1Char('\n')
+ || (line[i] == QLatin1Char('\r')
+ && (i + 1 == len || line[i + 1] != QLatin1Char('\n')))) {
+ iStart = i + 1;
+ ++nNewlines;
+ column = 0;
+ } else if (!line[i].isLowSurrogate())
+ column++;
+ }
+ column -= fixup;
+ trailingString = line.mid(iStart);
+ }
+};
+
+class QMLDOM_EXPORT FormatOptions
+{
+public:
+ int tabSize = 4;
+ int indentSize = 4;
+ bool useTabs = false;
+};
+
+QMLDOM_EXPORT class LineWriterOptions
+{
+ Q_GADGET
+public:
+ enum class LineEndings { Unix, Windows, OldMacOs };
+ Q_ENUM(LineEndings)
+ enum class TrailingSpace { Preserve, Remove };
+ Q_ENUM(TrailingSpace)
+ enum class Update { None = 0, Expressions = 0x1, Locations = 0x2, All = 0x3, Default = All };
+ Q_ENUM(Update)
+ Q_DECLARE_FLAGS(Updates, Update)
+ enum class AttributesSequence { Normalize, Preserve };
+ Q_ENUM(AttributesSequence)
+
+ int maxLineLength = -1;
+ int strongMaxLineExtra = 20;
+ int minContentLength = 10;
+ LineEndings lineEndings = LineEndings::Unix;
+ TrailingSpace codeTrailingSpace = TrailingSpace::Remove;
+ TrailingSpace commentTrailingSpace = TrailingSpace::Remove;
+ TrailingSpace stringTrailingSpace = TrailingSpace::Preserve;
+ FormatOptions formatOptions;
+ Updates updateOptions = Update::Default;
+ AttributesSequence attributesSequence = AttributesSequence::Normalize;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(LineWriterOptions::Updates)
+
+using PendingSourceLocationId = QAtomicInt;
+class LineWriter;
+
+QMLDOM_EXPORT class PendingSourceLocation
+{
+ Q_GADGET
+public:
+ quint32 utf16Start() const;
+ quint32 utf16End() const;
+ void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange);
+ void commit();
+ PendingSourceLocationId id;
+ SourceLocation value;
+ SourceLocation *toUpdate = nullptr;
+ std::function<void(SourceLocation)> updater = nullptr;
+ bool open = true;
+};
+
+QMLDOM_EXPORT class LineWriter
+{
+ Q_GADGET
+public:
+ enum class TextAddType {
+ Normal,
+ Extra,
+ Newline,
+ NewlineSplit,
+ NewlineExtra,
+ PartialCommit,
+ Eof
+ };
+
+ LineWriter(SinkF innerSink, QString fileName,
+ const LineWriterOptions &options = LineWriterOptions(), int lineNr = 0,
+ int columnNr = 0, int utf16Offset = 0, QString currentLine = QString());
+ std::function<void(QStringView)> sink()
+ {
+ return [this](QStringView s) { this->write(s); };
+ }
+
+ virtual ~LineWriter() { }
+
+ QList<SinkF> innerSinks() { return m_innerSinks; }
+ void addInnerSink(SinkF s) { m_innerSinks.append(s); }
+ LineWriter &ensureNewline(int nNewlines = 1, TextAddType t = TextAddType::Extra);
+ LineWriter &ensureSpace(TextAddType t = TextAddType::Extra);
+ LineWriter &ensureSpace(QStringView space, TextAddType t = TextAddType::Extra);
+
+ LineWriter &newline()
+ {
+ write(u"\n");
+ return *this;
+ }
+ LineWriter &space()
+ {
+ write(u" ");
+ return *this;
+ }
+ LineWriter &write(QStringView v, TextAddType tType = TextAddType::Normal);
+ LineWriter &write(QStringView v, SourceLocation *toUpdate)
+ {
+ auto pLoc = startSourceLocation(toUpdate);
+ write(v);
+ endSourceLocation(pLoc);
+ return *this;
+ }
+ void commitLine(QString eol, TextAddType t = TextAddType::Normal, int untilChar = -1);
+ void flush();
+ void eof(bool ensureNewline = true);
+ SourceLocation committedLocation() const;
+ PendingSourceLocationId startSourceLocation(SourceLocation *);
+ PendingSourceLocationId startSourceLocation(std::function<void(SourceLocation)>);
+ void endSourceLocation(PendingSourceLocationId);
+ quint32 counter() const { return m_counter; }
+ int addTextAddCallback(std::function<bool(LineWriter &, TextAddType)> callback);
+ bool removeTextAddCallback(int i) { return m_textAddCallbacks.remove(i); }
+ int addNewlinesAutospacerCallback(int nLines);
+ void handleTrailingSpace(LineWriterOptions::TrailingSpace s);
+ void setLineIndent(int indentAmount);
+ QString fileName() const { return m_fileName; }
+ const QString &currentLine() const { return m_currentLine; }
+ const LineWriterOptions &options() const { return m_options; }
+ virtual void lineChanged() { }
+ virtual void reindentAndSplit(QString eol, bool eof = false);
+ virtual void willCommit() { }
+
+private:
+ Q_DISABLE_COPY_MOVE(LineWriter)
+protected:
+ void changeAtOffset(quint32 offset, qint32 change, qint32 colChange, qint32 lineChange);
+ QString eolToWrite() const;
+ SourceLocation currentSourceLocation() const;
+ int column(int localIndex);
+ void textAddCallback(TextAddType t);
+
+ QList<SinkF> m_innerSinks;
+ QString m_fileName;
+ int m_lineNr;
+ int m_columnNr; // columnNr (starts at 0) of committed data
+ int m_lineUtf16Offset; // utf16 offset since last newline (what is typically stores as
+ // SourceLocation::startColumn
+ int m_currentColumnNr; // current columnNr (starts at 0)
+ int m_utf16Offset; // utf16 offset since start for committed data
+ QString m_currentLine;
+ LineWriterOptions m_options;
+ PendingSourceLocationId m_lastSourceLocationId;
+ QMap<PendingSourceLocationId, PendingSourceLocation> m_pendingSourceLocations;
+ QAtomicInt m_lastCallbackId;
+ QMap<int, std::function<bool(LineWriter &, TextAddType)>> m_textAddCallbacks;
+ quint32 m_counter = 0;
+ quint32 m_committedEmptyLines = 0x7FFFFFFF;
+ bool m_reindent = true;
+};
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+#endif
diff --git a/src/qmldom/qqmldomoutwriter.cpp b/src/qmldom/qqmldomoutwriter.cpp
new file mode 100644
index 0000000000..90c9589b7d
--- /dev/null
+++ b/src/qmldom/qqmldomoutwriter.cpp
@@ -0,0 +1,242 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#include "qqmldomoutwriter_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomlinewriter_p.h"
+#include "qqmldomitem_p.h"
+#include "qqmldomcomments_p.h"
+#include "qqmldomexternalitems_p.h"
+#include "qqmldomtop_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+OutWriterState::OutWriterState(Path itCanonicalPath, DomItem &it, FileLocations::Tree fLoc)
+ : itemCanonicalPath(itCanonicalPath), item(it), currentMap(fLoc)
+{
+ DomItem cRegions = it.field(Fields::comments);
+ if (const RegionComments *cRegionsPtr = cRegions.as<RegionComments>()) {
+ pendingComments = cRegionsPtr->regionComments;
+ fLoc->info().ensureCommentLocations(pendingComments.keys());
+ }
+}
+
+void OutWriterState::closeState(OutWriter &w)
+{
+ if (w.lineWriter.options().updateOptions & LineWriterOptions::Update::Locations)
+ w.lineWriter.endSourceLocation(fullRegionId);
+ if (!pendingRegions.isEmpty()) {
+ qCWarning(writeOutLog) << "PendingRegions non empty when closing item"
+ << pendingRegions.keys();
+ auto iend = pendingRegions.end();
+ auto it = pendingRegions.begin();
+ while (it == iend) {
+ w.lineWriter.endSourceLocation(it.value());
+ ++it;
+ }
+ }
+ if (!w.skipComments && !pendingComments.isEmpty())
+ qCWarning(writeOutLog) << "PendingComments when closing item "
+ << item.canonicalPath().toString() << "for regions"
+ << pendingComments.keys();
+}
+
+OutWriterState &OutWriter::state(int i)
+{
+ return states[states.size() - 1 - i];
+}
+
+void OutWriter::itemStart(DomItem &it)
+{
+ if (!topLocation->path())
+ topLocation->setPath(it.canonicalPath());
+ bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ FileLocations::Tree newFLoc = topLocation;
+ Path itP = it.canonicalPath();
+ if (updateLocs) {
+ if (!states.isEmpty()
+ && states.last().itemCanonicalPath
+ == itP.mid(0, states.last().itemCanonicalPath.length())) {
+ int oldL = states.last().itemCanonicalPath.length();
+ newFLoc = FileLocations::ensure(states.last().currentMap,
+ itP.mid(oldL, itP.length() - oldL),
+ AttachedInfo::PathType::Relative);
+
+ } else {
+ newFLoc = FileLocations::ensure(topLocation, itP, AttachedInfo::PathType::Canonical);
+ }
+ }
+ states.append(OutWriterState(itP, it, newFLoc));
+ if (updateLocs)
+ state().fullRegionId = lineWriter.startSourceLocation(
+ [newFLoc](SourceLocation l) { FileLocations::updateFullLocation(newFLoc, l); });
+ regionStart(QString());
+}
+
+void OutWriter::itemEnd(DomItem &it)
+{
+ Q_ASSERT(states.size() > 0);
+ Q_ASSERT(state().item == it);
+ regionEnd(QString());
+ state().closeState(*this);
+ states.removeLast();
+}
+
+void OutWriter::regionStart(QString rName)
+{
+ Q_ASSERT(!state().pendingRegions.contains(rName));
+ FileLocations::Tree fMap = state().currentMap;
+ if (!skipComments && state().pendingComments.contains(rName)) {
+ bool updateLocs = lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ QList<SourceLocation> *cLocs =
+ (updateLocs ? &(fMap->info().preCommentLocations[rName]) : nullptr);
+ state().pendingComments[rName].writePre(*this, cLocs);
+ }
+ state().pendingRegions[rName] = lineWriter.startSourceLocation(
+ [rName, fMap](SourceLocation l) { FileLocations::addRegion(fMap, rName, l); });
+}
+
+void OutWriter::regionEnd(QString rName)
+{
+ Q_ASSERT(state().pendingRegions.contains(rName));
+ FileLocations::Tree fMap = state().currentMap;
+ lineWriter.endSourceLocation(state().pendingRegions.value(rName));
+ state().pendingRegions.remove(rName);
+ if (state().pendingComments.contains(rName)) {
+ if (!skipComments) {
+ bool updateLocs =
+ lineWriter.options().updateOptions & LineWriterOptions::Update::Locations;
+ QList<SourceLocation> *cLocs =
+ (updateLocs ? &(fMap->info().postCommentLocations[rName]) : nullptr);
+ state().pendingComments[rName].writePost(*this, cLocs);
+ }
+ state().pendingComments.remove(rName);
+ }
+}
+
+OutWriter &OutWriter::writeRegion(QString rName, QStringView toWrite)
+{
+ regionStart(rName);
+ lineWriter.write(toWrite);
+ regionEnd(rName);
+ return *this;
+}
+
+DomItem OutWriter::updatedFile(DomItem &qmlFile)
+{
+ Q_ASSERT(qmlFile.internalKind() == DomType::QmlFile);
+ if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) {
+ std::shared_ptr<QmlFile> copyPtr = qmlFilePtr->makeCopy(qmlFile);
+ DomItem env = qmlFile.environment();
+ std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>();
+ Q_ASSERT(envPtr);
+ std::shared_ptr<DomEnvironment> newEnvPtr(
+ new DomEnvironment(envPtr, envPtr->loadPaths(), envPtr->options()));
+ newEnvPtr->addQmlFile(copyPtr);
+ MutableDomItem copy = MutableDomItem(DomItem(newEnvPtr).copy(copyPtr));
+ FileLocations::Tree newLoc = topLocation;
+ Path qmlFilePath = qmlFile.canonicalPath();
+ if (newLoc->path() != qmlFilePath) {
+ if (newLoc->path()) {
+ if (newLoc->path().length() > qmlFilePath.length()
+ && newLoc->path().mid(0, qmlFilePath.length()) == qmlFilePath) {
+ newLoc = FileLocations::createTree(qmlFilePath);
+ FileLocations::Tree loc =
+ FileLocations::ensure(newLoc, newLoc->path().mid(qmlFilePath.length()),
+ AttachedInfo::PathType::Relative);
+ loc->setSubItems(topLocation->subItems());
+ } else {
+ qCWarning(writeOutLog)
+ << "failed to base fileLocations in OutWriter (" << newLoc->path()
+ << ") to current file (" << qmlFilePath << ")";
+ }
+ } else {
+ newLoc = FileLocations::createTree(qmlFilePath);
+ Q_ASSERT(newLoc->subItems().isEmpty() && newLoc->info().regions.isEmpty());
+ }
+ }
+ copyPtr->setFileLocationsTree(newLoc);
+ UpdatedScriptExpression::visitTree(
+ reformattedScriptExpressions,
+ [&copy, qmlFilePath](Path p, UpdatedScriptExpression::Tree t) {
+ if (std::shared_ptr<ScriptExpression> exprPtr = t->info().expr) {
+ Q_ASSERT(p.mid(0, qmlFilePath.length()) == qmlFilePath);
+ MutableDomItem targetExpr = copy.path(p.mid(qmlFilePath.length()));
+ if (!targetExpr)
+ qCWarning(writeOutLog) << "failed to get" << p.mid(qmlFilePath.length())
+ << "from" << copy.canonicalPath();
+ else if (exprPtr->ast()
+ || (!targetExpr.as<ScriptExpression>()
+ || !targetExpr.as<ScriptExpression>()->ast()))
+ targetExpr.setScript(exprPtr);
+ else {
+ qCWarning(writeOutLog).noquote()
+ << "Skipped update of reformatted ScriptExpression with "
+ "code:\n---------------\n"
+ << exprPtr->code() << "\n---------------\n preCode:" <<
+ [exprPtr](Sink s) { sinkEscaped(s, exprPtr->preCode()); }
+ << "\n postCode: " <<
+ [exprPtr](Sink s) { sinkEscaped(s, exprPtr->postCode()); }
+ << "\n as it failed standalone reparse with errors:" <<
+ [&targetExpr, exprPtr](Sink s) {
+ targetExpr.item()
+ .copy(exprPtr, targetExpr.canonicalPath())
+ .iterateErrors(
+ [s](DomItem, ErrorMessage msg) {
+ s(u"\n ");
+ msg.dump(s);
+ return true;
+ },
+ true);
+ }
+ << "\n";
+ }
+ }
+ return true;
+ });
+ return copy.item();
+ }
+ return DomItem();
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomoutwriter_p.h b/src/qmldom/qqmldomoutwriter_p.h
new file mode 100644
index 0000000000..76bf55a9c2
--- /dev/null
+++ b/src/qmldom/qqmldomoutwriter_p.h
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QMLDOMOUTWRITER_P_H
+#define QMLDOMOUTWRITER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+#include "qqmldom_fwd_p.h"
+#include "qqmldomattachedinfo_p.h"
+#include "qqmldomlinewriter_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QQmlJS {
+namespace Dom {
+
+#define QMLDOM_USTRING(s) u##s
+#define QMLDOM_REGION(name) constexpr const auto name = QMLDOM_USTRING(#name)
+// namespace, so it cam be reopened to add more entries
+namespace Regions {
+} // namespace Regions
+
+class QMLDOM_EXPORT OutWriterState
+{
+public:
+ OutWriterState(Path itPath, DomItem &it, FileLocations::Tree fLoc);
+
+ void closeState(OutWriter &);
+
+ Path itemCanonicalPath;
+ DomItem item;
+ PendingSourceLocationId fullRegionId;
+ FileLocations::Tree currentMap;
+ QMap<QString, PendingSourceLocationId> pendingRegions;
+ QMap<QString, CommentedElement> pendingComments;
+};
+
+class QMLDOM_EXPORT OutWriter
+{
+public:
+ int indent = 0;
+ int indenterId = -1;
+ bool indentNextlines = false;
+ bool skipComments = false;
+ LineWriter &lineWriter;
+ Path currentPath;
+ FileLocations::Tree topLocation;
+ QString writtenStr;
+ UpdatedScriptExpression::Tree reformattedScriptExpressions;
+ QList<OutWriterState> states;
+
+ explicit OutWriter(LineWriter &lw)
+ : lineWriter(lw),
+ topLocation(FileLocations::createTree(Path())),
+ reformattedScriptExpressions(UpdatedScriptExpression::createTree(Path()))
+ {
+ lineWriter.addInnerSink([this](QStringView s) { writtenStr.append(s); });
+ indenterId =
+ lineWriter.addTextAddCallback([this](LineWriter &, LineWriter::TextAddType tt) {
+ if (indentNextlines && tt == LineWriter::TextAddType::Normal
+ && QStringView(lineWriter.currentLine()).trimmed().isEmpty())
+ lineWriter.setLineIndent(indent);
+ return true;
+ });
+ }
+
+ OutWriterState &state(int i = 0);
+
+ int increaseIndent(int level = 1)
+ {
+ int oldIndent = indent;
+ indent += lineWriter.options().formatOptions.indentSize * level;
+ return oldIndent;
+ }
+ int decreaseIndent(int level = 1, int expectedIndent = -1)
+ {
+ indent -= lineWriter.options().formatOptions.indentSize * level;
+ Q_ASSERT(expectedIndent < 0 || expectedIndent == indent);
+ return indent;
+ }
+
+ void itemStart(DomItem &it);
+ void itemEnd(DomItem &it);
+ void regionStart(QString rName);
+ void regionStart(QStringView rName) { regionStart(rName.toString()); }
+ void regionEnd(QString rName);
+ void regionEnd(QStringView rName) { regionEnd(rName.toString()); }
+
+ quint32 counter() const { return lineWriter.counter(); }
+ OutWriter &writeRegion(QString rName, QStringView toWrite);
+ OutWriter &writeRegion(QStringView rName, QStringView toWrite)
+ {
+ return writeRegion(rName.toString(), toWrite);
+ }
+ OutWriter &writeRegion(QString t) { return writeRegion(t, t); }
+ OutWriter &writeRegion(QStringView t) { return writeRegion(t.toString(), t); }
+ OutWriter &ensureNewline(int nNewlines = 1)
+ {
+ lineWriter.ensureNewline(nNewlines);
+ return *this;
+ }
+ OutWriter &ensureSpace()
+ {
+ lineWriter.ensureSpace();
+ return *this;
+ }
+ OutWriter &ensureSpace(QStringView space)
+ {
+ lineWriter.ensureSpace(space);
+ return *this;
+ }
+ OutWriter &newline()
+ {
+ lineWriter.newline();
+ return *this;
+ }
+ OutWriter &space()
+ {
+ lineWriter.space();
+ return *this;
+ }
+ OutWriter &write(QStringView v, LineWriter::TextAddType t = LineWriter::TextAddType::Normal)
+ {
+ lineWriter.write(v, t);
+ return *this;
+ }
+ OutWriter &write(QStringView v, SourceLocation *toUpdate)
+ {
+ lineWriter.write(v, toUpdate);
+ return *this;
+ }
+ void flush() { lineWriter.flush(); }
+ void eof(bool ensureNewline = true) { lineWriter.eof(ensureNewline); }
+ int addNewlinesAutospacerCallback(int nLines)
+ {
+ return lineWriter.addNewlinesAutospacerCallback(nLines);
+ }
+ int addTextAddCallback(std::function<bool(LineWriter &, LineWriter::TextAddType)> callback)
+ {
+ return lineWriter.addTextAddCallback(callback);
+ }
+ bool removeTextAddCallback(int i) { return lineWriter.removeTextAddCallback(i); }
+ void addReformattedScriptExpression(Path p, std::shared_ptr<ScriptExpression> exp)
+ {
+ if (auto updExp = UpdatedScriptExpression::ensure(reformattedScriptExpressions, p,
+ AttachedInfo::PathType::Canonical)) {
+ updExp->info().expr = exp;
+ }
+ }
+ DomItem updatedFile(DomItem &qmlFile);
+};
+
+} // end namespace Dom
+} // end namespace QQmlJS
+
+QT_END_NAMESPACE
+#endif // QMLDOMOUTWRITER_P_H
diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp
new file mode 100644
index 0000000000..d627af12ef
--- /dev/null
+++ b/src/qmldom/qqmldomreformatter.cpp
@@ -0,0 +1,1145 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#include "qqmldomreformatter_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+#include <QtQml/private/qqmljsastvisitor_p.h>
+#include <QtQml/private/qqmljsengine_p.h>
+#include <QtQml/private/qqmljslexer_p.h>
+
+#include <QString>
+
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+using namespace AST;
+
+class Rewriter : protected BaseVisitor
+{
+ OutWriter &lw;
+ std::shared_ptr<AstComments> comments;
+ std::function<QStringView(SourceLocation)> loc2Str;
+ QHash<Node *, QList<std::function<void()>>> postOps;
+ int expressionDepth = 0;
+
+ bool addSemicolons() const { return expressionDepth > 0; }
+
+public:
+ Rewriter(OutWriter &lw, std::shared_ptr<AstComments> comments,
+ std::function<QStringView(SourceLocation)> loc2Str, Node *node)
+ : lw(lw), comments(comments), loc2Str(loc2Str)
+ {
+ accept(node);
+ }
+
+protected:
+ bool preVisit(Node *n) override
+ {
+ if (CommentedElement *c = comments->commentForNode(n)) {
+ c->writePre(lw);
+ postOps[n].append([c, this]() { c->writePost(lw); });
+ }
+ return true;
+ }
+ void postVisit(Node *n) override
+ {
+ for (auto &op : postOps[n]) {
+ op();
+ }
+ postOps.remove(n);
+ }
+
+ void accept(Node *node) { Node::accept(node, this); }
+
+ void lnAcceptIndented(Node *node)
+ {
+ int indent = lw.increaseIndent(1);
+ lw.ensureNewline();
+ accept(node);
+ lw.decreaseIndent(1, indent);
+ }
+
+ void out(const char *str) { lw.write(QString::fromLatin1(str)); }
+
+ void out(QStringView str) { lw.write(str); }
+
+ void out(const SourceLocation &loc)
+ {
+ if (loc.length != 0)
+ out(loc2Str(loc));
+ }
+
+ void newLine() { lw.ensureNewline(); }
+
+ bool acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline = false)
+ {
+ if (cast<Block *>(ast)) {
+ out(" ");
+ accept(ast);
+ if (finishWithSpaceOrNewline)
+ out(" ");
+ return true;
+ } else {
+ if (finishWithSpaceOrNewline)
+ postOps[ast].append([this]() { this->newLine(); });
+ lnAcceptIndented(ast);
+ return false;
+ }
+ }
+
+ // we are not supposed to handle the ui
+ bool visit(UiPragma *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiEnumDeclaration *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiEnumMemberList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiImport *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiObjectDefinition *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiObjectInitializer *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiParameterList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiPublicMember *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiObjectBinding *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiScriptBinding *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiArrayBinding *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiHeaderItemList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiObjectMemberList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiArrayMemberList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiQualifiedId *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiProgram *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiSourceElement *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiVersionSpecifier *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiInlineComponent *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiAnnotation *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiAnnotationList *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+ bool visit(UiRequired *) override
+ {
+ Q_ASSERT(false);
+ return false;
+ }
+
+ bool visit(ThisExpression *ast) override
+ {
+ out(ast->thisToken);
+ return true;
+ }
+ bool visit(NullExpression *ast) override
+ {
+ out(ast->nullToken);
+ return true;
+ }
+ bool visit(TrueLiteral *ast) override
+ {
+ out(ast->trueToken);
+ return true;
+ }
+ bool visit(FalseLiteral *ast) override
+ {
+ out(ast->falseToken);
+ return true;
+ }
+ bool visit(IdentifierExpression *ast) override
+ {
+ out(ast->identifierToken);
+ return true;
+ }
+ bool visit(StringLiteral *ast) override
+ {
+ // correctly handle multiline literals
+ if (ast->literalToken.length == 0)
+ return true;
+ QStringView str = loc2Str(ast->literalToken);
+ if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
+ out(str.mid(0, 1));
+ lw.indentNextlines = false;
+ out(str.mid(1));
+ lw.indentNextlines = true;
+ } else {
+ out(str);
+ }
+ return true;
+ }
+ bool visit(NumericLiteral *ast) override
+ {
+ out(ast->literalToken);
+ return true;
+ }
+ bool visit(RegExpLiteral *ast) override
+ {
+ out(ast->literalToken);
+ return true;
+ }
+
+ bool visit(ArrayPattern *ast) override
+ {
+ out(ast->lbracketToken);
+ int baseIndent = lw.increaseIndent(1);
+ if (ast->elements)
+ accept(ast->elements);
+ lw.decreaseIndent(1, baseIndent);
+ out(ast->rbracketToken);
+ return false;
+ }
+
+ bool visit(ObjectPattern *ast) override
+ {
+ out(ast->lbraceToken);
+ ++expressionDepth;
+ if (ast->properties) {
+ lnAcceptIndented(ast->properties);
+ newLine();
+ }
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ bool visit(PatternElementList *ast) override
+ {
+ for (PatternElementList *it = ast; it; it = it->next) {
+ if (it->elision)
+ accept(it->elision);
+ if (it->elision && it->element)
+ out(", ");
+ if (it->element)
+ accept(it->element);
+ if (it->next)
+ out(", ");
+ }
+ return false;
+ }
+
+ bool visit(PatternPropertyList *ast) override
+ {
+ for (PatternPropertyList *it = ast; it; it = it->next) {
+ PatternProperty *assignment = AST::cast<PatternProperty *>(it->property);
+ if (assignment) {
+ bool isStringLike = AST::cast<StringLiteralPropertyName *>(assignment->name)
+ || cast<IdentifierPropertyName *>(assignment->name);
+ if (isStringLike)
+ out("\"");
+ accept(assignment->name);
+ if (isStringLike)
+ out("\"");
+ out(": "); // assignment->colonToken
+ if (it->next)
+ postOps[assignment->initializer].append([this] {
+ out(","); // always invalid?
+ });
+ accept(assignment->initializer);
+ if (it->next)
+ newLine();
+ continue;
+ }
+ PatternPropertyList *getterSetter = AST::cast<PatternPropertyList *>(it->next);
+ if (getterSetter->property) {
+ switch (getterSetter->property->type) {
+ case PatternElement::Getter:
+ out("get");
+ break;
+ case PatternElement::Setter:
+ out("set");
+ break;
+ default:
+ break;
+ }
+
+ accept(getterSetter->property->name);
+ out("(");
+ // accept(getterSetter->formals); // TODO
+ out(")");
+ out(" {");
+ // accept(getterSetter->functionBody); // TODO
+ out(" }");
+ }
+ }
+ return false;
+ }
+
+ bool visit(NestedExpression *ast) override
+ {
+ out(ast->lparenToken);
+ int baseIndent = lw.increaseIndent(1);
+ accept(ast->expression);
+ lw.decreaseIndent(1, baseIndent);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ bool visit(IdentifierPropertyName *ast) override
+ {
+ out(ast->id.toString());
+ return true;
+ }
+ bool visit(StringLiteralPropertyName *ast) override
+ {
+ out(ast->id.toString());
+ return true;
+ }
+ bool visit(NumericLiteralPropertyName *ast) override
+ {
+ out(QString::number(ast->id));
+ return true;
+ }
+
+ bool visit(TemplateLiteral *ast) override
+ {
+ // correctly handle multiline literals
+ if (ast->literalToken.length != 0) {
+ QStringView str = loc2Str(ast->literalToken);
+ if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) {
+ out(str.mid(0, 1));
+ lw.indentNextlines = false;
+ out(str.mid(1));
+ lw.indentNextlines = true;
+ } else {
+ out(str);
+ }
+ }
+ accept(ast->expression);
+ return true;
+ }
+
+ bool visit(ArrayMemberExpression *ast) override
+ {
+ accept(ast->base);
+ out(ast->lbracketToken);
+ int indent = lw.increaseIndent(1);
+ accept(ast->expression);
+ lw.decreaseIndent(1, indent);
+ out(ast->rbracketToken);
+ return false;
+ }
+
+ bool visit(FieldMemberExpression *ast) override
+ {
+ accept(ast->base);
+ out(ast->dotToken);
+ out(ast->identifierToken);
+ return false;
+ }
+
+ bool visit(NewMemberExpression *ast) override
+ {
+ out("new "); // ast->newToken
+ accept(ast->base);
+ out(ast->lparenToken);
+ accept(ast->arguments);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ bool visit(NewExpression *ast) override
+ {
+ out("new "); // ast->newToken
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(CallExpression *ast) override
+ {
+ accept(ast->base);
+ out(ast->lparenToken);
+ int baseIndent = lw.increaseIndent(1);
+ accept(ast->arguments);
+ lw.decreaseIndent(1, baseIndent);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ bool visit(PostIncrementExpression *ast) override
+ {
+ accept(ast->base);
+ out(ast->incrementToken);
+ return false;
+ }
+
+ bool visit(PostDecrementExpression *ast) override
+ {
+ accept(ast->base);
+ out(ast->decrementToken);
+ return false;
+ }
+
+ bool visit(PreIncrementExpression *ast) override
+ {
+ out(ast->incrementToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(PreDecrementExpression *ast) override
+ {
+ out(ast->decrementToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(DeleteExpression *ast) override
+ {
+ out("delete "); // ast->deleteToken
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(VoidExpression *ast) override
+ {
+ out("void "); // ast->voidToken
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(TypeOfExpression *ast) override
+ {
+ out("typeof "); // ast->typeofToken
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(UnaryPlusExpression *ast) override
+ {
+ out(ast->plusToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(UnaryMinusExpression *ast) override
+ {
+ out(ast->minusToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(TildeExpression *ast) override
+ {
+ out(ast->tildeToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(NotExpression *ast) override
+ {
+ out(ast->notToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ bool visit(BinaryExpression *ast) override
+ {
+ accept(ast->left);
+ out(" ");
+ out(ast->operatorToken);
+ out(" ");
+ accept(ast->right);
+ return false;
+ }
+
+ bool visit(ConditionalExpression *ast) override
+ {
+ accept(ast->expression);
+ out(" ? "); // ast->questionToken
+ accept(ast->ok);
+ out(" : "); // ast->colonToken
+ accept(ast->ko);
+ return false;
+ }
+
+ bool visit(Block *ast) override
+ {
+ out(ast->lbraceToken);
+ ++expressionDepth;
+ lnAcceptIndented(ast->statements);
+ newLine();
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ bool visit(VariableStatement *ast) override
+ {
+ out(ast->declarationKindToken);
+ out(" ");
+ accept(ast->declarations);
+ if (addSemicolons())
+ out(";");
+ return false;
+ }
+
+ bool visit(PatternElement *ast) override
+ {
+ if (ast->isForDeclaration) {
+ if (ast->scope == VariableScope::Var) {
+ out("var ");
+ } else if (ast->scope == VariableScope::Let) {
+ out("let ");
+ } else if (ast->scope == VariableScope::Const) {
+ out("const ");
+ }
+ }
+ accept(ast->bindingTarget);
+ switch (ast->type) {
+ case PatternElement::Literal:
+ case PatternElement::Method:
+ case PatternElement::Binding:
+ break;
+ case PatternElement::Getter:
+ out("get ");
+ break;
+ case PatternElement::Setter:
+ out("set ");
+ break;
+ case PatternElement::SpreadElement:
+ out("...");
+ break;
+ }
+ out(ast->identifierToken);
+ if (ast->initializer) {
+ if (ast->isVariableDeclaration())
+ out(" = ");
+ accept(ast->initializer);
+ }
+ return false;
+ }
+
+ bool visit(EmptyStatement *ast) override
+ {
+ out(ast->semicolonToken);
+ return false;
+ }
+
+ bool visit(IfStatement *ast) override
+ {
+ out(ast->ifToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->ok, ast->ko);
+ if (ast->ko) {
+ out(ast->elseToken);
+ if (cast<Block *>(ast->ko) || cast<IfStatement *>(ast->ko)) {
+ out(" ");
+ accept(ast->ko);
+ } else {
+ lnAcceptIndented(ast->ko);
+ }
+ }
+ return false;
+ }
+
+ bool visit(DoWhileStatement *ast) override
+ {
+ out(ast->doToken);
+ acceptBlockOrIndented(ast->statement, true);
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ bool visit(WhileStatement *ast) override
+ {
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ bool visit(ForStatement *ast) override
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ if (ast->initialiser) {
+ accept(ast->initialiser);
+ } else if (ast->declarations) {
+ out("var ");
+ accept(ast->declarations);
+ }
+ out("; "); // ast->firstSemicolonToken
+ accept(ast->condition);
+ out("; "); // ast->secondSemicolonToken
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ bool visit(ForEachStatement *ast) override
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->lhs);
+ out(" ");
+ out(ast->inOfToken);
+ out(" ");
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ bool visit(ContinueStatement *ast) override
+ {
+ out(ast->continueToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+ }
+
+ bool visit(BreakStatement *ast) override
+ {
+ out(ast->breakToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+ }
+
+ bool visit(ReturnStatement *ast) override
+ {
+ out(ast->returnToken);
+ if (ast->expression) {
+ if (ast->returnToken.length != 0)
+ out(" ");
+ accept(ast->expression);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+ }
+
+ bool visit(ThrowStatement *ast) override
+ {
+ out(ast->throwToken);
+ if (ast->expression) {
+ out(" ");
+ accept(ast->expression);
+ }
+ if (addSemicolons())
+ out(";");
+ return false;
+ }
+
+ bool visit(WithStatement *ast) override
+ {
+ out(ast->withToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ bool visit(SwitchStatement *ast) override
+ {
+ out(ast->switchToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ out(" ");
+ accept(ast->block);
+ return false;
+ }
+
+ bool visit(CaseBlock *ast) override
+ {
+ out(ast->lbraceToken);
+ ++expressionDepth;
+ newLine();
+ accept(ast->clauses);
+ if (ast->clauses && ast->defaultClause)
+ newLine();
+ accept(ast->defaultClause);
+ if (ast->moreClauses)
+ newLine();
+ accept(ast->moreClauses);
+ newLine();
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ bool visit(CaseClause *ast) override
+ {
+ out("case "); // ast->caseToken
+ accept(ast->expression);
+ out(ast->colonToken);
+ if (ast->statements)
+ lnAcceptIndented(ast->statements);
+ return false;
+ }
+
+ bool visit(DefaultClause *ast) override
+ {
+ out(ast->defaultToken);
+ out(ast->colonToken);
+ lnAcceptIndented(ast->statements);
+ return false;
+ }
+
+ bool visit(LabelledStatement *ast) override
+ {
+ out(ast->identifierToken);
+ out(": "); // ast->colonToken
+ accept(ast->statement);
+ return false;
+ }
+
+ bool visit(TryStatement *ast) override
+ {
+ out("try "); // ast->tryToken
+ accept(ast->statement);
+ if (ast->catchExpression) {
+ out(" ");
+ accept(ast->catchExpression);
+ }
+ if (ast->finallyExpression) {
+ out(" ");
+ accept(ast->finallyExpression);
+ }
+ return false;
+ }
+
+ bool visit(Catch *ast) override
+ {
+ out(ast->catchToken);
+ out(" ");
+ out(ast->lparenToken);
+ out(ast->identifierToken);
+ out(") "); // ast->rparenToken
+ accept(ast->statement);
+ return false;
+ }
+
+ bool visit(Finally *ast) override
+ {
+ out("finally "); // ast->finallyToken
+ accept(ast->statement);
+ return false;
+ }
+
+ bool visit(FunctionDeclaration *ast) override
+ {
+ return visit(static_cast<FunctionExpression *>(ast));
+ }
+
+ bool visit(FunctionExpression *ast) override
+ {
+ if (!ast->isArrowFunction) {
+ out("function "); // ast->functionToken
+ if (!ast->name.isNull())
+ out(ast->identifierToken);
+ }
+ out(ast->lparenToken);
+ if (ast->isArrowFunction && ast->formals && ast->formals->next)
+ out("(");
+ int baseIndent = lw.increaseIndent(1);
+ accept(ast->formals);
+ lw.decreaseIndent(1, baseIndent);
+ if (ast->isArrowFunction && ast->formals && ast->formals->next)
+ out(")");
+ out(ast->rparenToken);
+ if (ast->isArrowFunction && !ast->formals)
+ out("()");
+ out(" ");
+ if (ast->isArrowFunction)
+ out("=> ");
+ out(ast->lbraceToken);
+ if (ast->lbraceToken.length != 0)
+ ++expressionDepth;
+ if (ast->body) {
+ if (ast->body->next || ast->lbraceToken.length != 0) {
+ lnAcceptIndented(ast->body);
+ newLine();
+ } else {
+ // print a single statement in one line. E.g. x => x * 2
+ baseIndent = lw.increaseIndent(1);
+ accept(ast->body);
+ lw.decreaseIndent(1, baseIndent);
+ }
+ }
+ if (ast->lbraceToken.length != 0)
+ --expressionDepth;
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ bool visit(Elision *ast) override
+ {
+ for (Elision *it = ast; it; it = it->next) {
+ if (it->next)
+ out(", "); // ast->commaToken
+ }
+ return false;
+ }
+
+ bool visit(ArgumentList *ast) override
+ {
+ for (ArgumentList *it = ast; it; it = it->next) {
+ if (it->isSpreadElement)
+ out("...");
+ accept(it->expression);
+ if (it->next) {
+ out(", "); // it->commaToken
+ }
+ }
+ return false;
+ }
+
+ bool visit(StatementList *ast) override
+ {
+ ++expressionDepth;
+ for (StatementList *it = ast; it; it = it->next) {
+ // ### work around parser bug: skip empty statements with wrong tokens
+ if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) {
+ if (loc2Str(emptyStatement->semicolonToken) != QLatin1String(";"))
+ continue;
+ }
+
+ accept(it->statement);
+ if (it->next)
+ newLine();
+ }
+ --expressionDepth;
+ return false;
+ }
+
+ bool visit(VariableDeclarationList *ast) override
+ {
+ for (VariableDeclarationList *it = ast; it; it = it->next) {
+ accept(it->declaration);
+ if (it->next)
+ out(", "); // it->commaToken
+ }
+ return false;
+ }
+
+ bool visit(CaseClauses *ast) override
+ {
+ for (CaseClauses *it = ast; it; it = it->next) {
+ accept(it->clause);
+ if (it->next)
+ newLine();
+ }
+ return false;
+ }
+
+ bool visit(FormalParameterList *ast) override
+ {
+ for (FormalParameterList *it = ast; it; it = it->next) {
+ out(it->element->bindingIdentifier.toString()); // TODO
+ if (it->next)
+ out(", ");
+ }
+ return false;
+ }
+
+ // to check
+ bool visit(TypeExpression *) override { return true; }
+ bool visit(SuperLiteral *) override { return true; }
+ bool visit(PatternProperty *) override { return true; }
+ bool visit(ComputedPropertyName *) override
+ {
+ out("[");
+ return true;
+ }
+ bool visit(TaggedTemplate *) override { return true; }
+ bool visit(Expression *) override { return true; }
+ bool visit(ExpressionStatement *el) override
+ {
+ if (addSemicolons())
+ postOps[el->expression].append([this]() { out(";"); });
+ return true;
+ }
+ bool visit(YieldExpression *) override { return true; }
+ bool visit(ClassExpression *) override { return true; }
+ bool visit(ClassDeclaration *) override { return true; }
+ bool visit(ClassElementList *) override { return true; }
+ bool visit(Program *) override { return true; }
+ bool visit(NameSpaceImport *) override { return true; }
+ bool visit(ImportSpecifier *) override { return true; }
+ bool visit(ImportsList *) override { return true; }
+ bool visit(NamedImports *) override { return true; }
+ bool visit(FromClause *) override { return true; }
+ bool visit(ImportClause *) override { return true; }
+ bool visit(ImportDeclaration *) override { return true; }
+ bool visit(ExportSpecifier *) override { return true; }
+ bool visit(ExportsList *) override { return true; }
+ bool visit(ExportClause *) override { return true; }
+ bool visit(ExportDeclaration *) override { return true; }
+ bool visit(ESModule *) override { return true; }
+ bool visit(DebuggerStatement *) override { return true; }
+ bool visit(Type *) override { return true; }
+ bool visit(TypeArgumentList *) override { return true; }
+ bool visit(TypeAnnotation *) override { return true; }
+
+ // overridden to use BasicVisitor (and ensure warnings about new added AST)
+ void endVisit(UiProgram *) override { }
+ void endVisit(UiImport *) override { }
+ void endVisit(UiHeaderItemList *) override { }
+ void endVisit(UiPragma *) override { }
+ void endVisit(UiPublicMember *) override { }
+ void endVisit(UiSourceElement *) override { }
+ void endVisit(UiObjectDefinition *) override { }
+ void endVisit(UiObjectInitializer *) override { }
+ void endVisit(UiObjectBinding *) override { }
+ void endVisit(UiScriptBinding *) override { }
+ void endVisit(UiArrayBinding *) override { }
+ void endVisit(UiParameterList *) override { }
+ void endVisit(UiObjectMemberList *) override { }
+ void endVisit(UiArrayMemberList *) override { }
+ void endVisit(UiQualifiedId *) override { }
+ void endVisit(UiEnumDeclaration *) override { }
+ void endVisit(UiEnumMemberList *) override { }
+ void endVisit(UiVersionSpecifier *) override { }
+ void endVisit(UiInlineComponent *) override { }
+ void endVisit(UiAnnotation *) override { }
+ void endVisit(UiAnnotationList *) override { }
+ void endVisit(UiRequired *) override { }
+ void endVisit(TypeExpression *) override { }
+ void endVisit(ThisExpression *) override { }
+ void endVisit(IdentifierExpression *) override { }
+ void endVisit(NullExpression *) override { }
+ void endVisit(TrueLiteral *) override { }
+ void endVisit(FalseLiteral *) override { }
+ void endVisit(SuperLiteral *) override { }
+ void endVisit(StringLiteral *) override { }
+ void endVisit(TemplateLiteral *) override { }
+ void endVisit(NumericLiteral *) override { }
+ void endVisit(RegExpLiteral *) override { }
+ void endVisit(ArrayPattern *) override { }
+ void endVisit(ObjectPattern *) override { }
+ void endVisit(PatternElementList *) override { }
+ void endVisit(PatternPropertyList *) override { }
+ void endVisit(PatternElement *) override { }
+ void endVisit(PatternProperty *) override { }
+ void endVisit(Elision *) override { }
+ void endVisit(NestedExpression *) override { }
+ void endVisit(IdentifierPropertyName *) override { }
+ void endVisit(StringLiteralPropertyName *) override { }
+ void endVisit(NumericLiteralPropertyName *) override { }
+ void endVisit(ComputedPropertyName *) override { out("]"); }
+ void endVisit(ArrayMemberExpression *) override { }
+ void endVisit(FieldMemberExpression *) override { }
+ void endVisit(TaggedTemplate *) override { }
+ void endVisit(NewMemberExpression *) override { }
+ void endVisit(NewExpression *) override { }
+ void endVisit(CallExpression *) override { }
+ void endVisit(ArgumentList *) override { }
+ void endVisit(PostIncrementExpression *) override { }
+ void endVisit(PostDecrementExpression *) override { }
+ void endVisit(DeleteExpression *) override { }
+ void endVisit(VoidExpression *) override { }
+ void endVisit(TypeOfExpression *) override { }
+ void endVisit(PreIncrementExpression *) override { }
+ void endVisit(PreDecrementExpression *) override { }
+ void endVisit(UnaryPlusExpression *) override { }
+ void endVisit(UnaryMinusExpression *) override { }
+ void endVisit(TildeExpression *) override { }
+ void endVisit(NotExpression *) override { }
+ void endVisit(BinaryExpression *) override { }
+ void endVisit(ConditionalExpression *) override { }
+ void endVisit(Expression *) override { }
+ void endVisit(Block *) override { }
+ void endVisit(StatementList *) override { }
+ void endVisit(VariableStatement *) override { }
+ void endVisit(VariableDeclarationList *) override { }
+ void endVisit(EmptyStatement *) override { }
+ void endVisit(ExpressionStatement *) override { }
+ void endVisit(IfStatement *) override { }
+ void endVisit(DoWhileStatement *) override { }
+ void endVisit(WhileStatement *) override { }
+ void endVisit(ForStatement *) override { }
+ void endVisit(ForEachStatement *) override { }
+ void endVisit(ContinueStatement *) override { }
+ void endVisit(BreakStatement *) override { }
+ void endVisit(ReturnStatement *) override { }
+ void endVisit(YieldExpression *) override { }
+ void endVisit(WithStatement *) override { }
+ void endVisit(SwitchStatement *) override { }
+ void endVisit(CaseBlock *) override { }
+ void endVisit(CaseClauses *) override { }
+ void endVisit(CaseClause *) override { }
+ void endVisit(DefaultClause *) override { }
+ void endVisit(LabelledStatement *) override { }
+ void endVisit(ThrowStatement *) override { }
+ void endVisit(TryStatement *) override { }
+ void endVisit(Catch *) override { }
+ void endVisit(Finally *) override { }
+ void endVisit(FunctionDeclaration *) override { }
+ void endVisit(FunctionExpression *) override { }
+ void endVisit(FormalParameterList *) override { }
+ void endVisit(ClassExpression *) override { }
+ void endVisit(ClassDeclaration *) override { }
+ void endVisit(ClassElementList *) override { }
+ void endVisit(Program *) override { }
+ void endVisit(NameSpaceImport *) override { }
+ void endVisit(ImportSpecifier *) override { }
+ void endVisit(ImportsList *) override { }
+ void endVisit(NamedImports *) override { }
+ void endVisit(FromClause *) override { }
+ void endVisit(ImportClause *) override { }
+ void endVisit(ImportDeclaration *) override { }
+ void endVisit(ExportSpecifier *) override { }
+ void endVisit(ExportsList *) override { }
+ void endVisit(ExportClause *) override { }
+ void endVisit(ExportDeclaration *) override { }
+ void endVisit(ESModule *) override { }
+ void endVisit(DebuggerStatement *) override { }
+ void endVisit(Type *) override { }
+ void endVisit(TypeArgumentList *) override { }
+ void endVisit(TypeAnnotation *) override { }
+
+ void throwRecursionDepthError() override
+ {
+ out("/* ERROR: Hit recursion limit visiting AST, rewrite failed */");
+ }
+};
+
+void reformatAst(OutWriter &lw, std::shared_ptr<AstComments> comments,
+ const std::function<QStringView(SourceLocation)> loc2Str, AST::Node *n)
+{
+ if (n) {
+ Rewriter rewriter(lw, comments, loc2Str, n);
+ }
+}
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
diff --git a/src/qmldom/qqmldomreformatter_p.h b/src/qmldom/qqmldomreformatter_p.h
new file mode 100644
index 0000000000..c6293095ba
--- /dev/null
+++ b/src/qmldom/qqmldomreformatter_p.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**/
+#ifndef QQMLDOMREFORMATTER_P
+#define QQMLDOMREFORMATTER_P
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmldom_global.h"
+
+#include "qqmldomoutwriter_p.h"
+#include "qqmldom_fwd_p.h"
+#include "qqmldomcomments_p.h"
+
+#include <QtQml/private/qqmljsast_p.h>
+
+QT_BEGIN_NAMESPACE
+namespace QQmlJS {
+namespace Dom {
+
+QMLDOM_EXPORT void reformatAst(OutWriter &lw, std::shared_ptr<AstComments> comments,
+ const std::function<QStringView(SourceLocation)> loc2Str,
+ AST::Node *n);
+
+} // namespace Dom
+} // namespace QQmlJS
+QT_END_NAMESPACE
+
+#endif // QQMLDOMREFORMATTER_P
diff --git a/src/qmldom/qqmldomstringdumper.cpp b/src/qmldom/qqmldomstringdumper.cpp
index f033ecbaff..1e9346483d 100644
--- a/src/qmldom/qqmldomstringdumper.cpp
+++ b/src/qmldom/qqmldomstringdumper.cpp
@@ -220,6 +220,13 @@ void sinkNewline(Sink s, int indent)
* \brief A sink that ignores whatever it receives
*/
+QDebug operator<<(QDebug d, Dumper dumper)
+{
+ QDebug dd = d.noquote().nospace();
+ dumper([&dd](QStringView s) { dd << s; });
+ return d;
+}
+
} // end namespace Dom
} // end namespace QQmlJS
diff --git a/src/qmldom/qqmldomstringdumper_p.h b/src/qmldom/qqmldomstringdumper_p.h
index 87e169e77e..639a053efe 100644
--- a/src/qmldom/qqmldomstringdumper_p.h
+++ b/src/qmldom/qqmldomstringdumper_p.h
@@ -147,6 +147,8 @@ QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, QDebug debug);
QMLDOM_EXPORT void dumperToQDebug(Dumper dumper, ErrorLevel level = ErrorLevel::Debug);
+QMLDOM_EXPORT QDebug operator<<(QDebug d, Dumper dumper);
+
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE