515 lines
28 KiB
C++
515 lines
28 KiB
C++
// Copyright (C) 2021 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
// common declarations
|
|
#include <QtQmlDom/private/qqmldomitem_p.h>
|
|
// comparisons of two DomItems
|
|
#include <QtQmlDom/private/qqmldomcompare_p.h>
|
|
// field filters to compare only selected fields (ignore for example location changes)
|
|
#include <QtQmlDom/private/qqmldomfieldfilter_p.h>
|
|
// needed to edit and cast to concrete type (PropertyDefinition, ScriptExpression,...)
|
|
#include <QtQmlDom/private/qqmldomelements_p.h>
|
|
// cast of the top level items (DomEnvironments,...)
|
|
#include <QtQmlDom/private/qqmldomtop_p.h>
|
|
|
|
#include <QtTest/QTest>
|
|
#include <QCborValue>
|
|
#include <QDebug>
|
|
#include <QLatin1String>
|
|
#include <QLatin1Char>
|
|
#include <QLibraryInfo>
|
|
#include <QDir>
|
|
|
|
#include <memory>
|
|
|
|
// everything is in the QQmlJS::Dom namespace
|
|
using namespace QQmlJS::Dom;
|
|
|
|
int main()
|
|
{
|
|
QString baseDir = QLatin1String(QT_QMLTEST_DATADIR) + QLatin1String("/reformatter");
|
|
QStringList qmltypeDirs =
|
|
QStringList({ baseDir, QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) });
|
|
|
|
qDebug() << "Creating an environment loading qml from the directories" << qmltypeDirs;
|
|
qDebug() << "single threaded, no dependencies";
|
|
auto envPtr =
|
|
DomEnvironment::create(qmltypeDirs,
|
|
QQmlJS::Dom::DomEnvironment::Option::SingleThreaded
|
|
| QQmlJS::Dom::DomEnvironment::Option::NoDependencies);
|
|
|
|
QString testFilePath = baseDir + QLatin1String("/file1.qml");
|
|
DomItem tFile; // place where to store the loaded file
|
|
|
|
qDebug() << "loading the file" << testFilePath;
|
|
envPtr->loadFile(
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
FileToLoad::fromFileSystem(envPtr, testFilePath),
|
|
#else
|
|
testFilePath, QString(),
|
|
#endif
|
|
[&tFile](Path, const DomItem &, const DomItem &newIt) {
|
|
tFile = newIt; // callback called when everything is loaded that receives the loaded
|
|
// external file pair (path, oldValue, newValue)
|
|
});
|
|
|
|
// trigger the load
|
|
envPtr->loadPendingDependencies();
|
|
DomItem env(envPtr);
|
|
|
|
// # Read only API: DomItem is a generic pointer for read only access to Dom Itmes :)
|
|
{
|
|
// ## declarative json like API
|
|
DomItem qmlFile = tFile.field(Fields::currentItem);
|
|
DomItem imports = qmlFile.field(Fields::imports);
|
|
DomItem qmlObj = qmlFile.field(Fields::components)
|
|
.key(QString())
|
|
.index(0)
|
|
.field(Fields::objects)
|
|
.index(0);
|
|
|
|
// ### Dump
|
|
// any DomItem can be dumped
|
|
qDebug() << "writing to QDebug dumps that element:" << imports;
|
|
// often the dump is too verbose, and one might want it to a separate file
|
|
QString dumpFilePath =
|
|
QDir(QDir::tempPath())
|
|
.filePath(QFileInfo(testFilePath).baseName() + QLatin1String(".dump.json"));
|
|
qmlFile.dump(dumpFilePath, FieldFilter::defaultFilter());
|
|
qDebug() << "dumped file to" << dumpFilePath;
|
|
|
|
// ### Paths
|
|
qDebug() << "To identify a DomItem a canonical path can be used:"
|
|
<< imports.canonicalPath();
|
|
// a path can be converted to/from strings
|
|
QString pString = imports.canonicalPath().toString();
|
|
Path importsPath = Path::fromString(pString);
|
|
// and loaded again using the .path(somePath) method
|
|
DomItem imports2 = env.path(importsPath);
|
|
Q_ASSERT(imports == imports2);
|
|
// the canonical path is absolute, but you can have relative paths
|
|
Path first = Path::Index(0);
|
|
DomItem firstImport = imports.path(first);
|
|
// an existing path can also be extended
|
|
Path firstImportPath = importsPath.index(0);
|
|
Q_ASSERT(firstImportPath == firstImport.canonicalPath());
|
|
// the normal elements of a path are index, key, field
|
|
// Uppercase static method creates one, lowercase appends to an existing path.
|
|
Path mainComponentPath = Path::Field(Fields::components).key("").index(0);
|
|
DomItem mainComponent = qmlFile.path(mainComponentPath);
|
|
// DomItems have the same methods to access their elements
|
|
DomItem mainComponent2 = qmlFile.field(Fields::components).key("").index(0);
|
|
// two other special ements are root (root element for absolute paths)
|
|
Path topPath = Path::Root(PathRoot::Top);
|
|
Q_ASSERT(topPath == importsPath[0]);
|
|
// the current element performs an operation (tipically a lookup or iteration) at the
|
|
// current path location (not handled here)
|
|
Path lookupPath = Path::Current(PathCurrent::Lookup);
|
|
|
|
// there are various visit methods to iterate/visit DomItems in particular visitTree
|
|
// which is quite flexible.
|
|
// They normally use callbacks that can return false to stop the iteration.
|
|
// Still often the DomKind specific for loop presentated later are clearer and more
|
|
// convenient
|
|
{
|
|
QDebug dbg = qDebug().noquote().nospace();
|
|
imports.visitTree(
|
|
Path(),
|
|
[&dbg](Path p, const DomItem &el, bool adopted) {
|
|
dbg << QStringLiteral(u" ").repeated(p.length()) << "*" << p.last() << " "
|
|
<< domKindToString(el.domKind()) << "(" << el.internalKindStr()
|
|
<< ")\n";
|
|
// returning false here stops the whole iteration
|
|
return true;
|
|
},
|
|
VisitOption::Default, // we want a recursive visit visiting also the top and
|
|
// adopted
|
|
[&dbg](Path p, const DomItem &, bool canonicalChild) {
|
|
// returning false here skips that branch
|
|
if (!canonicalChild) {
|
|
dbg << QStringLiteral(u" ").repeated(p.length()) << "+" << p.last()
|
|
<< " (adopted, will not recurse)\n";
|
|
} else if (p && p.headIndex(0) % 2 == 1) {
|
|
dbg << QStringLiteral(u" ").repeated(p.length()) << "-" << p.last()
|
|
<< " *recursive visit skipped*\n";
|
|
return false; // we skip odd entries in lists;
|
|
} else {
|
|
dbg << QStringLiteral(u" ").repeated(p.length()) << "+" << p.last()
|
|
<< "\n";
|
|
}
|
|
return true;
|
|
},
|
|
[&dbg](Path p, const DomItem &, bool) {
|
|
dbg << QStringLiteral(u" ").repeated(p.length()) << "=" << p.last() << "\n";
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// ### DomKind
|
|
// any DomItem belongs to one of 5 fundamental types
|
|
|
|
// 1. Object (a C++ object)
|
|
Q_ASSERT(qmlFile.domKind() == DomKind::Object);
|
|
// The underlying type of the c++ object can be found with .internalKind()
|
|
Q_ASSERT(qmlFile.internalKind() == DomType::QmlFile);
|
|
// .initernalKindStr() is a convenience string version of it
|
|
Q_ASSERT(qmlFile.internalKindStr() == u"QmlFile");
|
|
// the object attributes (fields) can be reached using .field(u"filedName")
|
|
// normally one should not use a string, but the Fields:: constant
|
|
DomItem qmlFile2 = tFile.field(Fields::currentItem);
|
|
// all the available fields can be listed via fields()
|
|
qDebug() << "The" << qmlObj.internalKindStr() << "at" << qmlObj.canonicalPath()
|
|
<< "has the following fields:" << qmlObj.fields();
|
|
// we can access the underlying C++ object with as<>
|
|
if (const QmlFile *qmlFilePtr = qmlFile.as<QmlFile>())
|
|
qDebug() << "The QmlFile lives at the address" << qmlFilePtr;
|
|
// We can get the shared pointer of the owner type (which for the file is the QmlFile itself
|
|
if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>())
|
|
qDebug() << "QmlFile uses shared pointers as ownership method, the underlying address "
|
|
"is the same"
|
|
<< qmlFilePtr.get();
|
|
|
|
// 2. a (Cbor-) Value, i.e a string, number,...
|
|
DomItem fPath = qmlFile.field(Fields::canonicalFilePath);
|
|
Q_ASSERT(fPath.domKind() == DomKind::Value);
|
|
// the Cbor representation of a value can be extracted with .value(), and in this case we
|
|
// can then call toString
|
|
qDebug() << "The filePath DomItem is " << fPath << " and it still 'knows' its path "
|
|
<< fPath.canonicalPath() << " but can have it also as value:" << fPath.value()
|
|
<< "or even better as string." << fPath.value().toString(QLatin1String("*none*"));
|
|
// a DomItem might have a valid value() even if it is not of type DomKind::Value, indeed
|
|
// CBor maps and lists are mapped to DomKind::Map and DomKind::List, and can be traversed
|
|
// thought that but also have a valid value().
|
|
|
|
// 3. a list
|
|
Q_ASSERT(imports.domKind() == DomKind::List);
|
|
// the number of elements can be sound with .indexes() and with .index(n) we access each
|
|
// element
|
|
qDebug() << "We have " << imports.indexes() << " imports, and the first is "
|
|
<< imports.index(0);
|
|
// If we want to just loop on the elements .values() is the most convenient way
|
|
// technically values *always* works even for objects and maps, iterating on the values
|
|
for (DomItem import : imports.values()) {
|
|
if (const Import *importPtr = import.as<Import>()) {
|
|
if (importPtr->implicit)
|
|
qDebug() << importPtr->uri.toString() << importPtr->version.stringValue();
|
|
}
|
|
}
|
|
|
|
// 4. a map
|
|
DomItem bindings = qmlObj.field(Fields::bindings);
|
|
Q_ASSERT(bindings.domKind() == DomKind::Map);
|
|
// The keys of the map can be reached either with .keys() or .sortedKeys(), each element
|
|
// with .key(k)
|
|
qDebug() << "bindings";
|
|
for (QString k : bindings.sortedKeys()) {
|
|
for (DomItem b : bindings.key(k).values()) {
|
|
qDebug() << k << ":" << b;
|
|
}
|
|
}
|
|
|
|
// 5 The empty element
|
|
DomItem empty;
|
|
Q_ASSERT(empty.domKind() == DomKind::Empty);
|
|
// The empty element is the only DomItem that casted to bool returns false, so checking for
|
|
// it can be just an implicit cast to bool
|
|
Q_ASSERT(bindings && !empty);
|
|
// the empty element supports all the previus operations so that one can traverse a non
|
|
// existing path without checking at every element, but only check the result
|
|
DomItem nonExisting = qmlFile.field(u"no-existing").key(u"a").index(0);
|
|
Q_ASSERT(!nonExisting);
|
|
|
|
// the index operator [] can be used instead of .index/.key/.field, it might be slightly
|
|
// less efficient but works
|
|
|
|
// find type
|
|
// access type
|
|
|
|
// ### write out
|
|
// it is possible to write out a qmlFile (actually also parts of it), which will
|
|
// automatically reformat it
|
|
QString reformattedFilePath =
|
|
QDir(QDir::tempPath())
|
|
.filePath(QFileInfo(testFilePath).baseName() + QLatin1String(".qml"));
|
|
qmlFile.writeOut(reformattedFilePath);
|
|
qDebug() << "reformatted written at " << reformattedFilePath;
|
|
|
|
// ## Jumping around
|
|
// ### Generic Methods
|
|
// from a DomItem you do no have just deeper in the tree, you can also go up the hierarch
|
|
// toward the root .container() just goes up one step in the canonicalPath of the object
|
|
Q_ASSERT(imports == firstImport.container());
|
|
// .containingObject() goes up to the containing DomKind::Object, skipping over all Maps and
|
|
// Lists
|
|
Q_ASSERT(qmlFile == firstImport.containingObject());
|
|
// .owner() returns the shared pointer based "owner" object, qmlFile and ScriptExpression
|
|
// are owningItems
|
|
Q_ASSERT(qmlFile == bindings.owner());
|
|
// .top() goes to the top of the tree, i.e the environment (or the universe)
|
|
Q_ASSERT(env == bindings.top());
|
|
// environment is normally the same as top, but making sure it is a actually a
|
|
// DomEnvironment
|
|
Q_ASSERT(env = bindings.environment());
|
|
// the universe is a cache of loaded files which for each file keeps two versions: the
|
|
// latest and the latest valid it can be reached with .universe(), from the universe you
|
|
// cannot get back to the environment.
|
|
Q_ASSERT(env.universe().internalKind() == DomType::DomUniverse);
|
|
|
|
// ## QML Oriented Methods
|
|
// The Dom model is not for generic json-like structures, so there are methods tailored for
|
|
// Qml and its structure
|
|
// The methods can succeed if there is a clearly defined unique result.
|
|
// sometime there is an obivious, but not necessarily unique choice (tipically going up the
|
|
// hierarchy), for example given a qml file the obvious choice for a component is the root
|
|
// component, but the file might contain other inline components, and for an object with
|
|
// different version exposed (C++ property versioning) the latest version is the natural
|
|
// choice, but other might be available. In these case passing GoTo::MostLikely as argument
|
|
// makes the method to this obivious choice (or possibly even only choice if no other
|
|
// versions/components are actually defined), instead of refusing any potentially ambiguous
|
|
// situation and returning the empty element.
|
|
|
|
// .fileObject() goes to the object representing the whole file
|
|
// (from either the external object returned by load or from inside the file)
|
|
DomItem fileObject = tFile.fileObject();
|
|
DomItem fileObject2 = imports.fileObject();
|
|
Q_ASSERT(fileObject == fileObject2 && fileObject.internalKind() == DomType::QmlFile);
|
|
// .component() goes to the component object.
|
|
Q_ASSERT(qmlObj.component() == qmlFile.component(GoTo::MostLikely));
|
|
// .pragmas gives access to the pragmas of the current component
|
|
Q_ASSERT(qmlFile.pragmas() == qmlFile.field(Fields::pragmas));
|
|
|
|
// QmlObject
|
|
// QmlObject if the main to represent the type information (methods, bindings,
|
|
// properties,...) of qml. Please note that QmlObject -> component operation is potentially
|
|
// lossy, when multiple version are exposed, so we represent a type through its root object,
|
|
// not through a component.
|
|
|
|
// .qmlObject() goes to the current QmlObject
|
|
Q_ASSERT(qmlObj == bindings.qmlObject());
|
|
|
|
// Given the centrality of QmlObject several of its attributes have convenience methods
|
|
// to access them:
|
|
|
|
// .children() makes subObjects contained inside a QmlObject accessible
|
|
// note that it is possible to add objects also by directly binding the children or data
|
|
// attribute, those children are not listed here, this accesses only those listed inside
|
|
// the QmlObject
|
|
Q_ASSERT(qmlObj.children() == qmlObj.field(Fields::children));
|
|
DomItem subObj0 = qmlObj.children().index(0);
|
|
// .child(<i>) is a shortcut for .children.index(<i>)
|
|
Q_ASSERT(subObj0 == qmlObj.child(0));
|
|
// rootQmlObject goes to the root qmlObject (unless one reaches an empty element)
|
|
Q_ASSERT(!subObj0 || subObj0.rootQmlObject() == qmlObj);
|
|
// .bindings() returns the bindings defined in the current object
|
|
Q_ASSERT(bindings == qmlObj.bindings());
|
|
DomItem mCompObj = qmlObj.child(0)
|
|
.child(0)
|
|
.bindings()
|
|
.key(u"delegate")
|
|
.index(0)
|
|
.field(Fields::value)
|
|
.child(1);
|
|
// .methods() gives methods definitions and signals
|
|
DomItem methods = mCompObj.methods();
|
|
qDebug() << "mCompObj methods:";
|
|
for (QString methodName : methods.sortedKeys()) {
|
|
for (DomItem method : methods.key(methodName).values()) {
|
|
if (const MethodInfo *methodPtr = method.as<MethodInfo>()) {
|
|
Q_ASSERT(methodName == methodPtr->name);
|
|
qDebug() << " " << methodPtr->name << methodPtr->methodType;
|
|
}
|
|
}
|
|
}
|
|
qDebug() << "mCompObj propertyDefs:";
|
|
// .propertyDefs() returns the properties defined in the current object
|
|
DomItem pDefs = mCompObj.propertyDefs();
|
|
for (QString pDefName : pDefs.sortedKeys()) {
|
|
for (DomItem pDef : pDefs.key(pDefName).values()) {
|
|
if (const PropertyDefinition *pDefPtr = pDef.as<PropertyDefinition>()) {
|
|
Q_ASSERT(pDefName == pDefPtr->name);
|
|
qDebug() << " " << pDefPtr->name << pDefPtr->typeName;
|
|
}
|
|
}
|
|
}
|
|
// binding and property definitions are about the ones defined in the current object
|
|
// often one is interested also to the inherited properties.
|
|
// Here PropertyInfo helps, it list all the definitions and bindings for a given property
|
|
// in the inheritance order (local definitions, parent definitions, parent parent
|
|
// definitions,...)
|
|
// .propertyInfos() gives access in the usual way (through a DomItem)
|
|
DomItem propertyInfos = mCompObj.propertyInfos();
|
|
// .propertyInfoWithName(<name>) directly accesses one
|
|
PropertyInfo pInfo = mCompObj.propertyInfoWithName(QStringLiteral(u"a"));
|
|
qDebug() << "bindings" << pInfo.bindings;
|
|
// .propertyInfoNames() gives the names of the properties
|
|
Q_ASSERT(propertyInfos.keys() == mCompObj.propertyInfoNames());
|
|
|
|
// .globalScope() goes to the globa scope object
|
|
Q_ASSERT(qmlObj.globalScope().internalKind() == DomType::GlobalScope);
|
|
// and scope to the containing scope
|
|
Q_ASSERT(bindings.scope() == qmlObj);
|
|
}
|
|
// mutate & edit
|
|
{
|
|
// DomItem handles read-only access, but if one wants to change something it cannot be used.
|
|
// MutableDomItem can be initialized with a DomItem, and provides also the methods to modify
|
|
// the item. It keeps the OwningItem and the path to the current item.
|
|
// Mutability can invalidate pointers to non owning items (and thus DomItem).
|
|
// For this reason one should not modify something that other code can have a DomItem
|
|
// pointer to, the best practice is to make shared object immutable and never change them.
|
|
// One should modify only a copy that is used only by a single thread, and
|
|
// do not shared untils all modifications are done.
|
|
// A MutableItem stays valid (or becomes Empty), but stays safe to use
|
|
//
|
|
// Assuming one guarantees that editing is ok, doing it in practice is just about using
|
|
// MutableDomItem instead of DomItem
|
|
// It is possible to simply initialize a mutable item with a DomItem
|
|
DomItem origFile = tFile.fileObject();
|
|
MutableDomItem myFile0(origFile);
|
|
// Normally it is better to have a separate environment. Is possible to avoid re-reading
|
|
// the files already read by sharing the Universe between two environments.
|
|
// But normally it is better and just as safe to work on a copy, so that one can be sure
|
|
// that no DomItem is kept by other code gets invalidated. The .makeCopy creates a deep
|
|
// copy, and by default (DomItem::CopyOption::EnvConnected) creates an environment which to
|
|
// takes all non local elements from the current environment (its parent environment) but
|
|
// replaces the file object with the copy. When finished one can replace the file object of
|
|
// the parent with the new one using .commitToBase().
|
|
MutableDomItem myFile = origFile.makeCopy();
|
|
Q_ASSERT(myFile.ownerAs<QmlFile>()
|
|
&& myFile.ownerAs<QmlFile>() != myFile0.ownerAs<QmlFile>());
|
|
Q_ASSERT(myFile.environment().ownerAs<DomEnvironment>()
|
|
&& myFile.environment().ownerAs<DomEnvironment>()
|
|
!= myFile0.environment().ownerAs<DomEnvironment>());
|
|
// we can check that the two files are really identical (.item() give back the DomItem of
|
|
// a MutableDomItem
|
|
Q_ASSERT(domCompareStrList(origFile, myFile, FieldFilter::compareFilter()).isEmpty());
|
|
// MutableDomItem has the same methods as DomItem
|
|
MutableDomItem qmlObj = myFile.qmlObject(GoTo::MostLikely);
|
|
MutableDomItem qmlObj2 = myFile.field(Fields::components)
|
|
.key(QString())
|
|
.index(0)
|
|
.field(Fields::objects)
|
|
.index(0);
|
|
Q_ASSERT(qmlObj && qmlObj == qmlObj2);
|
|
qDebug() << "mutable qmlObj has canonicalPath " << qmlObj.canonicalPath();
|
|
// but it adds methods to add
|
|
// * new PropertyDefinitions
|
|
PropertyDefinition b;
|
|
b.name = QLatin1String("xx");
|
|
b.typeName = QLatin1String("int");
|
|
// if we make t true we also have to give a value...
|
|
MutableDomItem addedPDef = qmlObj.addPropertyDef(b);
|
|
qDebug() << "added property definition at:" << addedPDef.pathFromOwner();
|
|
// * new bindings
|
|
MutableDomItem addedBinding0 = qmlObj.addBinding(
|
|
Binding("height",
|
|
std::shared_ptr<ScriptExpression>(new ScriptExpression(
|
|
QStringLiteral(u"243"),
|
|
ScriptExpression::ExpressionType::BindingExpression))));
|
|
// by default addBinding, addPropertyDef and addMethod have the AddOption::Override
|
|
// to make it more difficult to create invalid documents, so that only the
|
|
// following binding remains (where we use the convenience constructor that constucts
|
|
// the ScriptExpression internally
|
|
MutableDomItem addedBinding = qmlObj.addBinding(Binding("height", QStringLiteral(u"242")));
|
|
qDebug() << "added binding at:" << addedBinding.pathFromOwner();
|
|
// * new methods
|
|
MethodInfo mInfo;
|
|
mInfo.name = QLatin1String("foo2");
|
|
MethodParameter param;
|
|
param.name = QLatin1String("x");
|
|
mInfo.parameters.append(param);
|
|
mInfo.body = std::make_shared<ScriptExpression>(
|
|
QLatin1String("return 4*10+2 - x"), ScriptExpression::ExpressionType::FunctionBody);
|
|
// we can change the added binding
|
|
addedBinding.mutableAs<Binding>()->setValue(
|
|
std::make_unique<BindingValue>(std::make_shared<ScriptExpression>(
|
|
QLatin1String("245"),
|
|
ScriptExpression::ExpressionType::BindingExpression)));
|
|
|
|
MutableDomItem addedMethod = qmlObj.addMethod(mInfo);
|
|
qDebug() << "added method at:" << addedMethod.pathFromOwner();
|
|
// * new QmlObjects
|
|
QmlObject subObj;
|
|
subObj.setName(QLatin1String("Item"));
|
|
MutableDomItem addedSubObj = qmlObj.addChild(subObj);
|
|
qDebug() << "added subObject at:" << addedMethod.pathFromOwner();
|
|
// It is possible to modify the content of objects, using the mutableAs method
|
|
if (PropertyDefinition *addedPDefPtr = addedPDef.mutableAs<PropertyDefinition>()) {
|
|
addedPDefPtr->isRequired = true;
|
|
}
|
|
MutableDomItem firstChild = qmlObj.child(0);
|
|
qDebug() << "firstChild:" << firstChild;
|
|
// It is possible remove objects
|
|
if (QmlObject *qmlObjPtr = qmlObj.mutableAs<QmlObject>()) {
|
|
QList<QmlObject> children = qmlObjPtr->children();
|
|
children.removeAt(0);
|
|
qmlObjPtr->setChildren(children);
|
|
}
|
|
// But as MutableDomItem does not keep the identity, just the same position, the addedSubObj
|
|
// becomes invalid (and firstChild changes)
|
|
qDebug() << "after removal firstChild:" << firstChild;
|
|
qDebug() << "addedSubObj becomes invalid:" << addedSubObj;
|
|
qDebug() << "But the last object is the added one:"
|
|
<< qmlObj.child(qmlObj.children().indexes() - 1);
|
|
|
|
// now origFile are different
|
|
Q_ASSERT(!domCompareStrList(origFile, myFile, FieldFilter::compareFilter()).isEmpty());
|
|
// and we can look at the places where they differ
|
|
qDebug().noquote().nospace()
|
|
<< "Edits introduced the following diffs (ignoring file locations"
|
|
<< " and thus whitespace/reformatting changes):\n"
|
|
<< domCompareStrList(origFile, myFile, FieldFilter::noLocationFilter(),
|
|
DomCompareStrList::AllDiffs)
|
|
.join(QString());
|
|
|
|
QString reformattedFilePath =
|
|
QDir(QDir::tempPath())
|
|
.filePath(QStringLiteral(u"edited") + QFileInfo(testFilePath).baseName()
|
|
+ QLatin1String(".qml"));
|
|
/* since this was the only usecase of the WriteOut call returning an item
|
|
* this API has been removed in
|
|
* https://codereview.qt-project.org/c/qt/qtdeclarative/+/523217
|
|
|
|
MutableDomItem
|
|
reformattedEditedFile = myFile.writeOut(reformattedFilePath);
|
|
// the reformatted edited file might be different from the edited file
|
|
// but the differences are just in file location/formatting
|
|
Q_ASSERT(domCompareStrList(myFile, reformattedEditedFile, FieldFilter::noLocationFilter())
|
|
.isEmpty());
|
|
|
|
qDebug() << "The edited file was written at " << reformattedFilePath;
|
|
QString dumpFilePath =
|
|
QDir(QDir::tempPath())
|
|
.filePath(QStringLiteral(u"edited0") + QFileInfo(testFilePath).baseName()
|
|
+ QLatin1String(".dump.json"));
|
|
myFile.dump(dumpFilePath);
|
|
qDebug() << "The non reformatted edited file was dumped at " << dumpFilePath;
|
|
QString reformattedDumpFilePath =
|
|
QDir(QDir::tempPath())
|
|
.filePath(QStringLiteral(u"edited") + QFileInfo(testFilePath).baseName()
|
|
+ QLatin1String(".dump.json"));
|
|
reformattedEditedFile.dump(reformattedDumpFilePath);
|
|
qDebug() << "The edited file was dumped at " << reformattedDumpFilePath;
|
|
// The top environment still contains the original loaded file
|
|
Q_ASSERT(origFile.ownerAs<QmlFile>() != reformattedEditedFile.ownerAs<QmlFile>());
|
|
Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>());
|
|
Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>() == origFile.ownerAs<QmlFile>());
|
|
Q_ASSERT(tFile.fileObject().ownerAs<QmlFile>() == origFile.ownerAs<QmlFile>());
|
|
Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>()
|
|
!= reformattedEditedFile.ownerAs<QmlFile>());
|
|
// we can commit the reformatted file
|
|
if (!reformattedEditedFile.commitToBase()) {
|
|
qWarning() << "No reformatted file to commit";
|
|
}
|
|
// myFile might not be the same (If and updated check is requested, not the case here)
|
|
if (myFile.ownerAs<QmlFile>() != reformattedEditedFile.ownerAs<QmlFile>()
|
|
&& !myFile.commitToBase()) {
|
|
qWarning() << "Could not commit edited file";
|
|
}
|
|
// but refreshing it (looking up its canonical path) we always find the updated file
|
|
Q_ASSERT(myFile.refreshed().ownerAs<QmlFile>() == reformattedEditedFile.ownerAs<QmlFile>());
|
|
Q_ASSERT(tFile.fileObject().refreshed().ownerAs<QmlFile>()
|
|
== reformattedEditedFile.ownerAs<QmlFile>());
|
|
*/
|
|
}
|
|
}
|