| 1 | // Copyright (C) 2019 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include <QCoreApplication> |
| 5 | #include <QFile> |
| 6 | #include <QTextStream> |
| 7 | |
| 8 | #include <QtQml/private/qqmljslexer_p.h> |
| 9 | #include <QtQml/private/qqmljsparser_p.h> |
| 10 | #include <QtQml/private/qqmljsengine_p.h> |
| 11 | #include <QtQml/private/qqmljsastvisitor_p.h> |
| 12 | #include <QtQml/private/qqmljsast_p.h> |
| 13 | #include <QtQmlDom/private/qqmldomitem_p.h> |
| 14 | #include <QtQmlDom/private/qqmldomexternalitems_p.h> |
| 15 | #include <QtQmlDom/private/qqmldomtop_p.h> |
| 16 | #include <QtQmlDom/private/qqmldomoutwriter_p.h> |
| 17 | #include <QtQmlDom/private/qqmldomlinewriterfactory_p.h> |
| 18 | |
| 19 | #if QT_CONFIG(commandlineparser) |
| 20 | # include <QCommandLineParser> |
| 21 | #endif |
| 22 | |
| 23 | #include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h> |
| 24 | #include <QtQmlFormat/private/qqmlformatsettings_p.h> |
| 25 | #include <QtQmlFormat/private/qqmlformatoptions_p.h> |
| 26 | |
| 27 | using namespace QQmlJS::Dom; |
| 28 | |
| 29 | static void logParsingErrors(const DomItem &fileItem, const QString &filename) |
| 30 | { |
| 31 | fileItem.iterateErrors( |
| 32 | visitor: [](const DomItem &, const ErrorMessage &msg) { |
| 33 | errorToQDebug(msg); |
| 34 | return true; |
| 35 | }, |
| 36 | iterate: true); |
| 37 | qWarning().noquote() << "Failed to parse" << filename; |
| 38 | } |
| 39 | |
| 40 | // TODO |
| 41 | // refactor this workaround. ExternalOWningItem is not recognized as an owning type |
| 42 | // in ownerAs. |
| 43 | static std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem) |
| 44 | { |
| 45 | std::shared_ptr<ExternalOwningItem> filePtr = nullptr; |
| 46 | switch (fileItem.internalKind()) { |
| 47 | case DomType::JsFile: |
| 48 | filePtr = fileItem.ownerAs<JsFile>(); |
| 49 | break; |
| 50 | default: |
| 51 | filePtr = fileItem.ownerAs<QmlFile>(); |
| 52 | break; |
| 53 | } |
| 54 | return filePtr; |
| 55 | } |
| 56 | |
| 57 | // TODO refactor |
| 58 | // Introduce better encapsulation and separation of concerns and move to DOM API |
| 59 | // returns a DomItem corresponding to the loaded file and bool indicating the validity of the file |
| 60 | static std::pair<DomItem, bool> parse(const QString &filename) |
| 61 | { |
| 62 | auto envPtr = |
| 63 | DomEnvironment::create(loadPaths: QStringList(), |
| 64 | options: QQmlJS::Dom::DomEnvironment::Option::SingleThreaded |
| 65 | | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); |
| 66 | // placeholder for a node |
| 67 | // containing metadata (ExternalItemInfo) about the loaded file |
| 68 | DomItem fMetadataItem; |
| 69 | envPtr->loadFile(file: FileToLoad::fromFileSystem(environment: envPtr, canonicalPath: filename), |
| 70 | // callback called when everything is loaded that receives the |
| 71 | // loaded external file pair (path, oldValue, newValue) |
| 72 | callback: [&fMetadataItem](Path, const DomItem &, const DomItem &extItemInfo) { |
| 73 | fMetadataItem = extItemInfo; |
| 74 | }); |
| 75 | auto fItem = fMetadataItem.fileObject(); |
| 76 | auto filePtr = getFileItemOwner(fileItem: fItem); |
| 77 | return { fItem, filePtr && filePtr->isValid() }; |
| 78 | } |
| 79 | |
| 80 | static bool parseFile(const QString &filename, const QQmlFormatOptions &options) |
| 81 | { |
| 82 | const auto [fileItem, validFile] = parse(filename); |
| 83 | if (!validFile) { |
| 84 | logParsingErrors(fileItem, filename); |
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | // Turn AST back into source code |
| 89 | if (options.isVerbose()) |
| 90 | qWarning().noquote() << "Dumping" << filename; |
| 91 | |
| 92 | const auto &code = getFileItemOwner(fileItem)->code(); |
| 93 | auto lwOptions = options.optionsForCode(code); |
| 94 | WriteOutChecks checks = WriteOutCheck::Default; |
| 95 | //Disable writeOutChecks for some usecases |
| 96 | if (options.sortImports() || options.forceEnabled() || options.isMaxColumnWidthSet() || code.size() > 32000 |
| 97 | || fileItem.internalKind() == DomType::JsFile) { |
| 98 | checks = WriteOutCheck::None; |
| 99 | } |
| 100 | |
| 101 | bool res = false; |
| 102 | if (options.isInplace()) { |
| 103 | if (options.isVerbose()) |
| 104 | qWarning().noquote() << "Writing to file" << filename; |
| 105 | FileWriter fw; |
| 106 | const unsigned numberOfBackupFiles = 0; |
| 107 | res = fileItem.writeOut(path: filename, nBackups: numberOfBackupFiles, opt: lwOptions, fw: &fw, extraChecks: checks); |
| 108 | } else { |
| 109 | QFile out; |
| 110 | if (out.open(stdout, ioFlags: QIODevice::WriteOnly)) { |
| 111 | auto lw = createLineWriter(innerSink: [&out](QStringView s) { out.write(data: s.toUtf8()); }, fileName: filename, |
| 112 | options: lwOptions); |
| 113 | OutWriter ow(*lw); |
| 114 | res = fileItem.writeOutForFile(ow, extraChecks: checks); |
| 115 | ow.flush(); |
| 116 | } else { |
| 117 | res = false; |
| 118 | } |
| 119 | } |
| 120 | return res; |
| 121 | } |
| 122 | |
| 123 | int main(int argc, char *argv[]) |
| 124 | { |
| 125 | QCoreApplication app(argc, argv); |
| 126 | QCoreApplication::setApplicationName("qmlformat" ); |
| 127 | QCoreApplication::setApplicationVersion(QT_VERSION_STR); |
| 128 | |
| 129 | QQmlFormatSettings settings(QLatin1String("qmlformat" )); |
| 130 | const auto &options = QQmlFormatOptions::buildCommandLineOptions(args: app.arguments()); |
| 131 | if (!options.isValid()) { |
| 132 | for (const auto &error : options.errors()) { |
| 133 | qWarning().noquote() << error; |
| 134 | } |
| 135 | |
| 136 | return -1; |
| 137 | } |
| 138 | |
| 139 | if (options.writeDefaultSettingsEnabled()) |
| 140 | return settings.writeDefaults() ? 0 : -1; |
| 141 | |
| 142 | bool success = true; |
| 143 | if (!options.files().isEmpty()) { |
| 144 | if (!options.arguments().isEmpty()) |
| 145 | qWarning() << "Warning: Positional arguments are ignored when -F is used" ; |
| 146 | |
| 147 | for (const QString &file : options.files()) { |
| 148 | Q_ASSERT(!file.isEmpty()); |
| 149 | |
| 150 | if (!parseFile(filename: file, options: options.optionsForFile(fileName: file, settings: &settings))) |
| 151 | success = false; |
| 152 | } |
| 153 | } else { |
| 154 | for (const QString &file : options.arguments()) { |
| 155 | if (!parseFile(filename: file, options: options.optionsForFile(fileName: file, settings: &settings))) |
| 156 | success = false; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | return success ? 0 : 1; |
| 161 | } |
| 162 | |