2022-05-13 13:12:05 +00:00
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
2019-12-13 15:10:46 +00:00
# include <QCoreApplication>
# include <QFile>
2020-06-18 08:25:30 +00:00
# include <QTextStream>
2019-12-13 15:10:46 +00:00
# include <QtQml/private/qqmljslexer_p.h>
# include <QtQml/private/qqmljsparser_p.h>
# include <QtQml/private/qqmljsengine_p.h>
# include <QtQml/private/qqmljsastvisitor_p.h>
# include <QtQml/private/qqmljsast_p.h>
2021-04-19 14:43:21 +00:00
# include <QtQmlDom/private/qqmldomitem_p.h>
# include <QtQmlDom/private/qqmldomexternalitems_p.h>
# include <QtQmlDom/private/qqmldomtop_p.h>
# include <QtQmlDom/private/qqmldomoutwriter_p.h>
2019-12-13 15:10:46 +00:00
# if QT_CONFIG(commandlineparser)
2021-04-19 14:43:21 +00:00
# include <QCommandLineParser>
2019-12-13 15:10:46 +00:00
# endif
2023-01-24 09:45:21 +00:00
# include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h>
2021-10-25 16:30:26 +00:00
2021-04-19 14:43:21 +00:00
using namespace QQmlJS : : Dom ;
2019-12-13 15:10:46 +00:00
2021-02-07 22:07:04 +00:00
struct Options
{
bool verbose = false ;
bool inplace = false ;
bool force = false ;
bool tabs = false ;
bool valid = false ;
2021-04-19 14:43:21 +00:00
bool normalize = false ;
2021-10-25 16:30:26 +00:00
bool ignoreSettings = false ;
bool writeDefaultSettings = false ;
2022-11-06 20:40:50 +00:00
bool objectsSpacing = false ;
2022-11-23 18:33:40 +00:00
bool functionsSpacing = false ;
2021-02-07 22:07:04 +00:00
int indentWidth = 4 ;
bool indentWidthSet = false ;
QString newline = " native " ;
QStringList files ;
QStringList arguments ;
QStringList errors ;
} ;
bool parseFile ( const QString & filename , const Options & options )
2019-12-13 15:10:46 +00:00
{
2023-12-28 20:31:13 +00:00
auto envPtr =
2021-04-19 14:43:21 +00:00
DomEnvironment : : create ( QStringList ( ) ,
QQmlJS : : Dom : : DomEnvironment : : Option : : SingleThreaded
| QQmlJS : : Dom : : DomEnvironment : : Option : : NoDependencies ) ;
DomItem tFile ; // place where to store the loaded file
2023-12-28 20:31:13 +00:00
envPtr - > loadFile (
FileToLoad : : fromFileSystem ( envPtr , filename ) ,
2021-04-19 14:43:21 +00:00
[ & 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)
} ,
LoadOption : : DefaultLoad ) ;
2024-01-08 16:52:18 +00:00
envPtr - > loadPendingDependencies ( ) ;
2021-04-19 14:43:21 +00:00
DomItem qmlFile = tFile . fileObject ( ) ;
std : : shared_ptr < QmlFile > qmlFilePtr = qmlFile . ownerAs < QmlFile > ( ) ;
if ( ! qmlFilePtr | | ! qmlFilePtr - > isValid ( ) ) {
qmlFile . iterateErrors (
2023-09-14 10:30:33 +00:00
[ ] ( const DomItem & , const ErrorMessage & msg ) {
2021-04-19 14:43:21 +00:00
errorToQDebug ( msg ) ;
return true ;
} ,
true ) ;
2019-12-13 15:10:46 +00:00
qWarning ( ) . noquote ( ) < < " Failed to parse " < < filename ;
return false ;
}
// Turn AST back into source code
2021-02-07 22:07:04 +00:00
if ( options . verbose )
2019-12-13 15:10:46 +00:00
qWarning ( ) . noquote ( ) < < " Dumping " < < filename ;
2021-04-19 14:43:21 +00:00
LineWriterOptions lwOptions ;
lwOptions . formatOptions . indentSize = options . indentWidth ;
lwOptions . formatOptions . useTabs = options . tabs ;
lwOptions . updateOptions = LineWriterOptions : : Update : : None ;
if ( options . newline = = " native " ) {
// find out current line endings...
QStringView code = qmlFilePtr - > code ( ) ;
int newlineIndex = code . indexOf ( QChar ( u ' \n ' ) ) ;
int crIndex = code . indexOf ( QChar ( u ' \r ' ) ) ;
if ( newlineIndex > = 0 ) {
if ( crIndex > = 0 ) {
if ( crIndex + 1 = = newlineIndex )
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : Windows ;
else
qWarning ( ) . noquote ( ) < < " Invalid line ending in file, using default " ;
} else {
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : Unix ;
2020-01-17 13:28:09 +00:00
}
2021-04-19 14:43:21 +00:00
} else if ( crIndex > = 0 ) {
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : OldMacOs ;
} else {
qWarning ( ) . noquote ( ) < < " Unknown line ending in file, using default " ;
2020-01-17 13:28:09 +00:00
}
2021-04-19 14:43:21 +00:00
} else if ( options . newline = = " macos " ) {
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : OldMacOs ;
} else if ( options . newline = = " windows " ) {
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : Windows ;
} else if ( options . newline = = " unix " ) {
lwOptions . lineEndings = LineWriterOptions : : LineEndings : : Unix ;
} else {
qWarning ( ) . noquote ( ) < < " Unknown line ending type " < < options . newline ;
return false ;
2020-01-17 13:28:09 +00:00
}
2021-04-19 14:43:21 +00:00
if ( options . normalize )
lwOptions . attributesSequence = LineWriterOptions : : AttributesSequence : : Normalize ;
else
lwOptions . attributesSequence = LineWriterOptions : : AttributesSequence : : Preserve ;
WriteOutChecks checks = WriteOutCheck : : Default ;
if ( options . force | | qmlFilePtr - > code ( ) . size ( ) > 32000 )
checks = WriteOutCheck : : None ;
2022-11-06 20:40:50 +00:00
lwOptions . objectsSpacing = options . objectsSpacing ;
2022-11-23 18:33:40 +00:00
lwOptions . functionsSpacing = options . functionsSpacing ;
2022-11-06 20:40:50 +00:00
2021-04-19 14:43:21 +00:00
MutableDomItem res ;
if ( options . inplace ) {
if ( options . verbose )
qWarning ( ) . noquote ( ) < < " Writing to file " < < filename ;
FileWriter fw ;
2022-10-19 10:50:31 +00:00
const unsigned numberOfBackupFiles = 0 ;
res = qmlFile . writeOut ( filename , numberOfBackupFiles , lwOptions , & fw , checks ) ;
2021-04-19 14:43:21 +00:00
} else {
QFile out ;
out . open ( stdout , QIODevice : : WriteOnly ) ;
LineWriter lw ( [ & out ] ( QStringView s ) { out . write ( s . toUtf8 ( ) ) ; } , filename , lwOptions ) ;
OutWriter ow ( lw ) ;
res = qmlFile . writeOutForFile ( ow , checks ) ;
ow . flush ( ) ;
}
return bool ( res ) ;
2019-12-13 15:10:46 +00:00
}
2021-02-07 22:07:04 +00:00
Options buildCommandLineOptions ( const QCoreApplication & app )
2019-12-13 15:10:46 +00:00
{
# if QT_CONFIG(commandlineparser)
QCommandLineParser parser ;
parser . setApplicationDescription ( " Formats QML files according to the QML Coding Conventions. " ) ;
parser . addHelpOption ( ) ;
parser . addVersionOption ( ) ;
2021-04-19 14:43:21 +00:00
parser . addOption (
QCommandLineOption ( { " V " , " verbose " } ,
QStringLiteral ( " Verbose mode. Outputs more detailed information. " ) ) ) ;
2019-12-13 15:10:46 +00:00
2021-10-25 16:30:26 +00:00
QCommandLineOption writeDefaultsOption (
QStringList ( ) < < " write-defaults " ,
QLatin1String ( " Writes defaults settings to .qmlformat.ini and exits (Warning: This "
" will overwrite any existing settings and comments!) " ) ) ;
parser . addOption ( writeDefaultsOption ) ;
QCommandLineOption ignoreSettings ( QStringList ( ) < < " ignore-settings " ,
QLatin1String ( " Ignores all settings files and only takes "
" command line options into consideration " ) ) ;
parser . addOption ( ignoreSettings ) ;
2021-04-19 14:43:21 +00:00
parser . addOption ( QCommandLineOption (
{ " i " , " inplace " } ,
QStringLiteral ( " Edit file in-place instead of outputting to stdout. " ) ) ) ;
2019-12-13 15:10:46 +00:00
2021-04-19 14:43:21 +00:00
parser . addOption ( QCommandLineOption ( { " f " , " force " } ,
QStringLiteral ( " Continue even if an error has occurred. " ) ) ) ;
2020-01-15 10:25:18 +00:00
2020-09-02 10:34:29 +00:00
parser . addOption (
QCommandLineOption ( { " t " , " tabs " } , QStringLiteral ( " Use tabs instead of spaces. " ) ) ) ;
parser . addOption ( QCommandLineOption ( { " w " , " indent-width " } ,
QStringLiteral ( " How many spaces are used when indenting. " ) ,
" width " , " 4 " ) ) ;
2021-04-19 14:43:21 +00:00
parser . addOption ( QCommandLineOption ( { " n " , " normalize " } ,
QStringLiteral ( " Reorders the attributes of the objects "
" according to the QML Coding Guidelines. " ) ) ) ;
2020-06-18 08:25:30 +00:00
parser . addOption ( QCommandLineOption (
{ " F " , " files " } , QStringLiteral ( " Format all files listed in file, in-place " ) , " file " ) ) ;
2021-04-19 14:43:21 +00:00
parser . addOption ( QCommandLineOption (
{ " l " , " newline " } ,
QStringLiteral ( " Override the new line format to use (native macos unix windows). " ) ,
" newline " , " native " ) ) ;
2020-03-18 08:19:31 +00:00
2022-11-06 20:40:50 +00:00
parser . addOption ( QCommandLineOption ( QStringList ( ) < < " objects-spacing " , QStringLiteral ( " Ensure spaces between objects (only works with normalize option). " ) ) ) ;
2022-11-23 18:33:40 +00:00
parser . addOption ( QCommandLineOption ( QStringList ( ) < < " functions-spacing " , QStringLiteral ( " Ensure spaces between functions (only works with normalize option). " ) ) ) ;
2019-12-13 15:10:46 +00:00
parser . addPositionalArgument ( " filenames " , " files to be processed by qmlformat " ) ;
parser . process ( app ) ;
2021-10-25 16:30:26 +00:00
if ( parser . isSet ( writeDefaultsOption ) ) {
Options options ;
options . writeDefaultSettings = true ;
options . valid = true ;
return options ;
}
2021-02-07 22:07:04 +00:00
bool indentWidthOkay = false ;
const int indentWidth = parser . value ( " indent-width " ) . toInt ( & indentWidthOkay ) ;
if ( ! indentWidthOkay ) {
Options options ;
options . errors . push_back ( " Error: Invalid value passed to -w " ) ;
return options ;
}
2019-12-13 15:10:46 +00:00
2021-02-07 22:07:04 +00:00
QStringList files ;
2021-04-19 14:43:21 +00:00
if ( ! parser . value ( " files " ) . isEmpty ( ) ) {
2021-04-13 11:13:14 +00:00
QFile file ( parser . value ( " files " ) ) ;
file . open ( QIODevice : : Text | QIODevice : : ReadOnly ) ;
if ( file . isOpen ( ) ) {
QTextStream in ( & file ) ;
while ( ! in . atEnd ( ) ) {
QString file = in . readLine ( ) ;
if ( file . isEmpty ( ) )
continue ;
files . push_back ( file ) ;
}
2021-02-07 22:07:04 +00:00
}
2020-09-02 10:34:29 +00:00
}
2021-02-07 22:07:04 +00:00
Options options ;
options . verbose = parser . isSet ( " verbose " ) ;
options . inplace = parser . isSet ( " inplace " ) ;
options . force = parser . isSet ( " force " ) ;
options . tabs = parser . isSet ( " tabs " ) ;
2021-04-19 14:43:21 +00:00
options . normalize = parser . isSet ( " normalize " ) ;
2021-10-25 16:30:26 +00:00
options . ignoreSettings = parser . isSet ( " ignore-settings " ) ;
2022-11-06 20:40:50 +00:00
options . objectsSpacing = parser . isSet ( " objects-spacing " ) ;
2022-11-23 18:33:40 +00:00
options . functionsSpacing = parser . isSet ( " functions-spacing " ) ;
2021-02-07 22:07:04 +00:00
options . valid = true ;
options . indentWidth = indentWidth ;
options . indentWidthSet = parser . isSet ( " indent-width " ) ;
options . newline = parser . value ( " newline " ) ;
options . files = files ;
options . arguments = parser . positionalArguments ( ) ;
return options ;
# else
return Options { } ;
# endif
}
int main ( int argc , char * argv [ ] )
{
QCoreApplication app ( argc , argv ) ;
QCoreApplication : : setApplicationName ( " qmlformat " ) ;
2021-06-22 08:19:46 +00:00
QCoreApplication : : setApplicationVersion ( QT_VERSION_STR ) ;
2021-02-07 22:07:04 +00:00
2021-10-25 16:30:26 +00:00
QQmlToolingSettings settings ( QLatin1String ( " qmlformat " ) ) ;
const QString & useTabsSetting = QStringLiteral ( " UseTabs " ) ;
settings . addOption ( useTabsSetting ) ;
const QString & indentWidthSetting = QStringLiteral ( " IndentWidth " ) ;
settings . addOption ( indentWidthSetting , 4 ) ;
const QString & normalizeSetting = QStringLiteral ( " NormalizeOrder " ) ;
settings . addOption ( normalizeSetting ) ;
const QString & newlineSetting = QStringLiteral ( " NewlineType " ) ;
settings . addOption ( newlineSetting , QStringLiteral ( " native " ) ) ;
2022-11-06 20:40:50 +00:00
const QString & objectsSpacingSetting = QStringLiteral ( " ObjectsSpacing " ) ;
settings . addOption ( objectsSpacingSetting ) ;
2022-11-23 18:33:40 +00:00
const QString & functionsSpacingSetting = QStringLiteral ( " FunctionsSpacing " ) ;
settings . addOption ( functionsSpacingSetting ) ;
2021-02-07 22:07:04 +00:00
const auto options = buildCommandLineOptions ( app ) ;
if ( ! options . valid ) {
for ( const auto & error : options . errors ) {
qWarning ( ) . noquote ( ) < < error ;
}
2020-09-02 10:34:29 +00:00
return - 1 ;
}
2021-10-25 16:30:26 +00:00
if ( options . writeDefaultSettings )
return settings . writeDefaults ( ) ? 0 : - 1 ;
auto getSettings = [ & ] ( const QString & file , Options options ) {
2023-10-13 11:30:27 +00:00
// Perform formatting inplace if --files option is set.
if ( ! options . files . isEmpty ( ) )
options . inplace = true ;
2021-10-25 16:30:26 +00:00
if ( options . ignoreSettings | | ! settings . search ( file ) )
return options ;
Options perFileOptions = options ;
// Allow for tab settings to be overwritten by the command line
if ( ! options . indentWidthSet ) {
if ( settings . isSet ( indentWidthSetting ) )
perFileOptions . indentWidth = settings . value ( indentWidthSetting ) . toInt ( ) ;
if ( settings . isSet ( useTabsSetting ) )
perFileOptions . tabs = settings . value ( useTabsSetting ) . toBool ( ) ;
}
if ( settings . isSet ( normalizeSetting ) )
perFileOptions . normalize = settings . value ( normalizeSetting ) . toBool ( ) ;
if ( settings . isSet ( newlineSetting ) )
perFileOptions . newline = settings . value ( newlineSetting ) . toString ( ) ;
2022-11-06 20:40:50 +00:00
if ( settings . isSet ( objectsSpacingSetting ) )
perFileOptions . objectsSpacing = settings . value ( objectsSpacingSetting ) . toBool ( ) ;
2022-11-23 18:33:40 +00:00
if ( settings . isSet ( functionsSpacingSetting ) )
perFileOptions . functionsSpacing = settings . value ( functionsSpacingSetting ) . toBool ( ) ;
2021-10-25 16:30:26 +00:00
return perFileOptions ;
} ;
2021-02-07 22:07:04 +00:00
bool success = true ;
if ( ! options . files . isEmpty ( ) ) {
if ( ! options . arguments . isEmpty ( ) )
2020-06-18 08:25:30 +00:00
qWarning ( ) < < " Warning: Positional arguments are ignored when -F is used " ;
2021-02-07 22:07:04 +00:00
for ( const QString & file : options . files ) {
Q_ASSERT ( ! file . isEmpty ( ) ) ;
2020-06-18 08:25:30 +00:00
2021-10-25 16:30:26 +00:00
if ( ! parseFile ( file , getSettings ( file , options ) ) )
2020-06-18 08:25:30 +00:00
success = false ;
}
} else {
2021-02-07 22:07:04 +00:00
for ( const QString & file : options . arguments ) {
2021-10-25 16:30:26 +00:00
if ( ! parseFile ( file , getSettings ( file , options ) ) )
2020-06-18 08:25:30 +00:00
success = false ;
}
2019-12-13 15:10:46 +00:00
}
return success ? 0 : 1 ;
}