diff options
author | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-03-23 15:37:32 +0100 |
---|---|---|
committer | Fawzi Mohamed <fawzi.mohamed@qt.io> | 2021-06-05 00:07:48 +0200 |
commit | de3d65009adf3c7ce0be6da77ee74b0a2610c9c5 (patch) | |
tree | b9c03fc2b5e2c7845c4b4070c403eae66e5df3ef /src | |
parent | 6cf15ad4e359223bb19c997f0dd279f9aea842d7 (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.txt | 3 | ||||
-rw-r--r-- | src/qmldom/qqmldomattachedinfo.cpp | 46 | ||||
-rw-r--r-- | src/qmldom/qqmldomattachedinfo_p.h | 27 | ||||
-rw-r--r-- | src/qmldom/qqmldomcomments.cpp | 37 | ||||
-rw-r--r-- | src/qmldom/qqmldomconstants_p.h | 15 | ||||
-rw-r--r-- | src/qmldom/qqmldomelements.cpp | 512 | ||||
-rw-r--r-- | src/qmldom/qqmldomelements_p.h | 55 | ||||
-rw-r--r-- | src/qmldom/qqmldomexternalitems.cpp | 14 | ||||
-rw-r--r-- | src/qmldom/qqmldomexternalitems_p.h | 2 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem.cpp | 235 | ||||
-rw-r--r-- | src/qmldom/qqmldomitem_p.h | 61 | ||||
-rw-r--r-- | src/qmldom/qqmldomlinewriter.cpp | 482 | ||||
-rw-r--r-- | src/qmldom/qqmldomlinewriter_p.h | 251 | ||||
-rw-r--r-- | src/qmldom/qqmldomoutwriter.cpp | 242 | ||||
-rw-r--r-- | src/qmldom/qqmldomoutwriter_p.h | 204 | ||||
-rw-r--r-- | src/qmldom/qqmldomreformatter.cpp | 1145 | ||||
-rw-r--r-- | src/qmldom/qqmldomreformatter_p.h | 72 | ||||
-rw-r--r-- | src/qmldom/qqmldomstringdumper.cpp | 7 | ||||
-rw-r--r-- | src/qmldom/qqmldomstringdumper_p.h | 2 |
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, ©, &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 ¤tLine() 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, + [©, 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 |