qmldom: improve qmldom tool dump

Make the default dump more concise and useful:
* remove most large fields non related to the semantics (comments,
  file location, exports, pre/postCode,...)
* allow empty (catch all) filter
* call filter.setFiltred() to correctly filter custom filters
* dump the loaded files, and not the whole environment (unless
  requested with --path-to-dump $env)
* do not load dependencies by default

Change-Id: I5d26dc074bc0cbace31508401e9d08d90c99a254
Pick-to: 6.2
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Fawzi Mohamed 2021-09-03 10:33:12 +02:00
parent bd1a62be00
commit 4cfb323389
2 changed files with 124 additions and 58 deletions

View File

@ -37,6 +37,7 @@
**/
#include "qqmldomfieldfilter_p.h"
#include "qqmldompath_p.h"
#include "QtCore/qglobal.h"
QT_BEGIN_NAMESPACE
@ -44,6 +45,34 @@ QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
/*!
\internal
\class QQmljs::Dom::FieldFilter
\brief Class that represent a filter on DomItem, when dumping or comparing
DomItem can be duped or compared, but often one is interested only in a subset
of them, FieldFilter is a simple way to select a subset of them.
It uses two basic elements: the type of the object (internalKind) and the
name of fields.
A basic filter can be represented by <op><typeName>:<fieldName> or <op><fieldName>
where op is either + or - (if the matching elements should be added or removed)
Both typeName and fieldName can be the empty string (meaning any value matches).
Basic filters are ordered from the most specific to the least specific as follow:
type+field > type > field > empty.
When combining several filters the most specific always wins, so
-code,+ScriptExpression:code is the same as +ScriptExpression:code,-code and means
that normally the field code is not outputted but for a ScriptExpression DomItem
it is.
It is possible to get the string representation of the current filter with
FieldFilter::describeFieldsFilter(), and change the current filter with
FieldFilter::addFilter(), but after it one should call FieldFilter::setFiltred()
to ensure that the internal cache used to speed up comparisons is correct.
*/
QString FieldFilter::describeFieldsFilter() const
{
QString fieldFilterStr;
@ -104,9 +133,11 @@ bool FieldFilter::operator()(DomItem &base, const PathEls::PathComponent &c, Dom
bool FieldFilter::addFilter(QString fFields)
{
for (QString fField : fFields.split(QLatin1Char(','))) {
for (const QString &fField : fFields.split(QLatin1Char(','))) {
// parses a base filter of the form <op><typeName>:<fieldName> or <op><fieldName>
// as described in this class documentation
QRegularExpression fieldRe(QRegularExpression::anchoredPattern(QStringLiteral(
uR"((?<op>[-+])?(?:(?<type>[a-zA-Z0-9_]*):)?(?<field>[a-zA-Z0-9_]+))")));
uR"((?<op>[-+])?(?:(?<type>[a-zA-Z0-9_]*):)?(?<field>[a-zA-Z0-9_]*))")));
QRegularExpressionMatch m = fieldRe.match(fField);
if (m.hasMatch()) {
if (m.captured(u"op") == u"+") {
@ -128,11 +159,18 @@ FieldFilter FieldFilter::defaultFilter()
{
QMultiMap<QString, QString> fieldFilterAdd { { QLatin1String("ScriptExpression"),
QLatin1String("code") } };
QMultiMap<QString, QString> fieldFilterRemove { { QString(), QLatin1String("code") },
{ QString(), QLatin1String("propertyInfos") },
{ QLatin1String("AttachedInfo"),
QLatin1String("parent") } };
QMultiMap<QString, QString> fieldFilterRemove {
{ QString(), QString::fromUtf16(Fields::code) },
{ QString(), QString::fromUtf16(Fields::postCode) },
{ QString(), QString::fromUtf16(Fields::preCode) },
{ QString(), QString::fromUtf16(Fields::importScope) },
{ QString(), QString::fromUtf16(Fields::fileLocationsTree) },
{ QString(), QString::fromUtf16(Fields::astComments) },
{ QString(), QString::fromUtf16(Fields::comments) },
{ QString(), QString::fromUtf16(Fields::exports) },
{ QString(), QString::fromUtf16(Fields::propertyInfos) },
{ QLatin1String("AttachedInfo"), QString::fromUtf16(Fields::parent) }
};
return FieldFilter { fieldFilterAdd, fieldFilterRemove };
}

View File

@ -108,7 +108,9 @@ int main(int argc, char *argv[])
QCommandLineOption pathToDumpOption(
QStringList() << "path-to-dump",
QLatin1String("adds a path to dump (by default the root path is dumped)"),
QLatin1String("adds a path to dump. By default the base path of each file is dumped. "
"If any path starts with $ ($env for example) then the environment (and "
"not the loaded files) is used as basis."),
QLatin1String("pathToDump"));
parser.addOption(pathToDumpOption);
@ -116,7 +118,7 @@ int main(int argc, char *argv[])
QStringList() << "D"
<< "dependencies",
QLatin1String("Dependencies to load: none, required, reachable"),
QLatin1String("dependenciesToLoad"), QLatin1String("required"));
QLatin1String("dependenciesToLoad"), QLatin1String("none"));
parser.addOption(dependenciesOption);
QCommandLineOption reformatDirOption(
@ -146,11 +148,12 @@ int main(int argc, char *argv[])
if (parser.isSet(filterOption)) {
qDebug() << "filters: " << parser.values(filterOption);
for (QString fFields : parser.values(filterOption)) {
for (const QString &fFields : parser.values(filterOption)) {
if (!filter.addFilter(fFields)) {
return 1;
}
}
filter.setFiltred();
}
std::optional<DomType> fileType;
@ -158,7 +161,7 @@ int main(int argc, char *argv[])
fileType = DomType::QmlFile;
Dependencies dep = Dependencies::None;
for (QString depName : parser.values(dependenciesOption)) {
for (const QString &depName : parser.values(dependenciesOption)) {
QMetaEnum metaEnum = QMetaEnum::fromType<Dependencies>();
bool found = false;
for (int i = 0; i < metaEnum.keyCount(); ++i) {
@ -188,7 +191,7 @@ int main(int argc, char *argv[])
}
QList<Path> pathsToDump;
for (QString pStr : parser.values(pathToDumpOption)) {
for (const QString &pStr : parser.values(pathToDumpOption)) {
pathsToDump.append(Path::fromString(pStr));
}
if (pathsToDump.isEmpty())
@ -216,10 +219,10 @@ int main(int argc, char *argv[])
{
QDebug dbg = qDebug();
dbg << "dirs:\n";
foreach (QString d, qmltypeDirs)
for (const QString &d : qAsConst(qmltypeDirs))
dbg << " '" << d << "'\n";
dbg << "files:\n";
foreach (QString f, positionalArguments)
for (const QString &f : qAsConst(positionalArguments))
dbg << " '" << f << "'\n";
dbg << "fieldFilter: " << filter.describeFieldsFilter();
dbg << "\n";
@ -232,44 +235,55 @@ int main(int argc, char *argv[])
qDebug() << "will load\n";
if (dep != Dependencies::None)
env.loadBuiltins();
foreach (QString s, positionalArguments) {
env.loadFile(s, QString(), nullptr, LoadOption::DefaultLoad, fileType);
QList<DomItem> loadedFiles(positionalArguments.size());
qsizetype iPos = 0;
for (const QString &s : qAsConst(positionalArguments)) {
env.loadFile(
s, QString(),
[&loadedFiles, iPos](Path, const DomItem &, const DomItem &newIt) {
loadedFiles[iPos] = newIt;
},
LoadOption::DefaultLoad, fileType);
}
envPtr->loadPendingDependencies(env);
bool hadFailures = false;
const qsizetype largestFileSizeToCheck = 32000;
if (parser.isSet(reformatOption)) {
for (auto s : positionalArguments) {
DomItem qmlFile = env.path(Paths::qmlFilePath(QFileInfo(s).canonicalFilePath()));
if (qmlFile) {
qDebug() << "reformatting" << s;
FileWriter fw;
LineWriterOptions lwOptions;
WriteOutChecks checks = WriteOutCheck::Default;
if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>())
if (qmlFilePtr->code().size() > largestFileSizeToCheck)
checks = WriteOutCheck::None;
QString target = s;
QString rDir = parser.value(reformatDirOption);
if (!rDir.isEmpty()) {
QFileInfo f(s);
QDir d(rDir);
target = d.filePath(f.fileName());
}
MutableDomItem res = qmlFile.writeOut(target, nBackups, lwOptions, &fw, checks);
switch (fw.status) {
case FileWriter::Status::ShouldWrite:
case FileWriter::Status::SkippedDueToFailure:
qWarning() << "failure reformatting " << s;
break;
case FileWriter::Status::DidWrite:
qDebug() << "success";
break;
case FileWriter::Status::SkippedEqual:
qDebug() << "no change";
}
hadFailures = hadFailures || !bool(res);
for (auto &qmlFile : loadedFiles) {
QString qmlFilePath = qmlFile.canonicalFilePath();
if (qmlFile.internalKind() != DomType::QmlFile) {
qWarning() << "cannot reformat" << qmlFile.internalKindStr() << "(" << qmlFilePath
<< ")";
continue;
}
qDebug() << "reformatting" << qmlFilePath;
FileWriter fw;
LineWriterOptions lwOptions;
WriteOutChecks checks = WriteOutCheck::Default;
if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>())
if (qmlFilePtr->code().size() > largestFileSizeToCheck)
checks = WriteOutCheck::None;
QString target = qmlFilePath;
QString rDir = parser.value(reformatDirOption);
if (!rDir.isEmpty()) {
QFileInfo f(qmlFilePath);
QDir d(rDir);
target = d.filePath(f.fileName());
}
MutableDomItem res = qmlFile.writeOut(target, nBackups, lwOptions, &fw, checks);
switch (fw.status) {
case FileWriter::Status::ShouldWrite:
case FileWriter::Status::SkippedDueToFailure:
qWarning() << "failure reformatting " << qmlFilePath;
break;
case FileWriter::Status::DidWrite:
qDebug() << "success";
break;
case FileWriter::Status::SkippedEqual:
qDebug() << "no change";
}
hadFailures = hadFailures || !bool(res);
}
} else if (parser.isSet(dumpOption) || !parser.isSet(reformatOption)) {
qDebug() << "will dump\n";
@ -277,21 +291,35 @@ int main(int argc, char *argv[])
auto sink = [&ts](QStringView v) {
ts << v; /* ts.flush(); */
};
if (pathsToDump.length() > 1)
qsizetype iPathToDump = 0;
bool globalPaths = false;
for (auto p : pathsToDump)
if (p.headKind() == Path::Kind::Root)
globalPaths = true;
if (globalPaths)
loadedFiles = QList<DomItem>({ env });
bool dumpDict = pathsToDump.size() > 1 || loadedFiles.size() > 1;
if (dumpDict)
sink(u"{\n");
bool first = true;
for (Path p : pathsToDump) {
if (pathsToDump.length() > 1) {
if (first)
first = false;
else
sink(u",\n");
sinkEscaped(sink, p.toString());
sink(u":\n");
while (iPathToDump < pathsToDump.size()) {
for (auto &fileItem : loadedFiles) {
Path p = pathsToDump.at(iPathToDump++ % pathsToDump.size());
if (dumpDict) {
if (iPathToDump > 1)
sink(u",\n");
sink(u"\"");
if (fileItem.internalKind() != DomType::DomEnvironment) {
sinkEscaped(sink, fileItem.canonicalFilePath(),
EscapeOptions::NoOuterQuotes);
sink(u"/");
}
sinkEscaped(sink, p.toString(), EscapeOptions::NoOuterQuotes);
sink(u"\":\n");
}
fileItem.path(p).dump(sink, 0, filter);
}
env.path(p).dump(sink, 0, filter);
}
if (pathsToDump.length() > 1)
if (dumpDict)
sink(u"}\n");
Qt::endl(ts).flush();
}