1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 /*
30   qmlcodeparser.cpp
31 */
32 
33 #include "qmlcodeparser.h"
34 
35 #include "node.h"
36 #include "qmlvisitor.h"
37 
38 #ifndef QT_NO_DECLARATIVE
39 #    include <private/qqmljsast_p.h>
40 #    include <private/qqmljsastvisitor_p.h>
41 #endif
42 #include <qdebug.h>
43 
44 QT_BEGIN_NAMESPACE
45 
46 /*!
47   Constructs the QML code parser.
48  */
QmlCodeParser()49 QmlCodeParser::QmlCodeParser()
50 #ifndef QT_NO_DECLARATIVE
51     : lexer(nullptr), parser(nullptr)
52 #endif
53 {
54 }
55 
56 /*!
57   Destroys the QML code parser.
58  */
~QmlCodeParser()59 QmlCodeParser::~QmlCodeParser() {}
60 
61 /*!
62   Initializes the code parser base class.
63   Also creates a lexer and parser from QQmlJS.
64  */
initializeParser()65 void QmlCodeParser::initializeParser()
66 {
67     CodeParser::initializeParser();
68 
69 #ifndef QT_NO_DECLARATIVE
70     lexer = new QQmlJS::Lexer(&engine);
71     parser = new QQmlJS::Parser(&engine);
72 #endif
73 }
74 
75 /*!
76   Terminates the QML code parser. Deletes the lexer and parser
77   created by the constructor.
78  */
terminateParser()79 void QmlCodeParser::terminateParser()
80 {
81 #ifndef QT_NO_DECLARATIVE
82     delete lexer;
83     delete parser;
84 #endif
85 }
86 
87 /*!
88   Returns "QML".
89  */
language()90 QString QmlCodeParser::language()
91 {
92     return "QML";
93 }
94 
95 /*!
96   Returns a string list containing "*.qml". This is the only
97   file type parsed by the QMLN parser.
98  */
sourceFileNameFilter()99 QStringList QmlCodeParser::sourceFileNameFilter()
100 {
101     return QStringList() << "*.qml";
102 }
103 
104 /*!
105   Parses the source file at \a filePath and inserts the contents
106   into the database. The \a location is used for error reporting.
107 
108   If it can't open the file at \a filePath, it reports an error
109   and returns without doing anything.
110  */
parseSourceFile(const Location & location,const QString & filePath)111 void QmlCodeParser::parseSourceFile(const Location &location, const QString &filePath)
112 {
113     QFile in(filePath);
114     currentFile_ = filePath;
115     if (!in.open(QIODevice::ReadOnly)) {
116         location.error(tr("Cannot open QML file '%1'").arg(filePath));
117         currentFile_.clear();
118         return;
119     }
120 
121 #ifndef QT_NO_DECLARATIVE
122     QString document = in.readAll();
123     in.close();
124 
125     Location fileLocation(filePath);
126 
127     QString newCode = document;
128     extractPragmas(newCode);
129     lexer->setCode(newCode, 1);
130 
131     if (parser->parse()) {
132         QQmlJS::AST::UiProgram *ast = parser->ast();
133         QmlDocVisitor visitor(filePath, newCode, &engine, topicCommands() + commonMetaCommands(),
134                               topicCommands());
135         QQmlJS::AST::Node::accept(ast, &visitor);
136         if (visitor.hasError()) {
137             qDebug().nospace() << qPrintable(filePath) << ": Could not analyze QML file. "
138                                << "The output is incomplete.";
139         }
140     }
141     const auto &messages = parser->diagnosticMessages();
142     for (const auto &msg : messages) {
143         qDebug().nospace() << qPrintable(filePath) << ':'
144 #    if Q_QML_PRIVATE_API_VERSION >= 8
145                            << msg.loc.startLine << ": QML syntax error at col "
146                            << msg.loc.startColumn
147 #    else
148                            << msg.line << ": QML syntax error at col " << msg.column
149 #    endif
150                            << ": " << qPrintable(msg.message);
151     }
152     currentFile_.clear();
153 #else
154     location.warning("QtDeclarative not installed; cannot parse QML or JS.");
155 #endif
156 }
157 
158 static QSet<QString> topicCommands_;
159 /*!
160   Returns the set of strings representing the topic commands.
161  */
topicCommands()162 const QSet<QString> &QmlCodeParser::topicCommands()
163 {
164     if (topicCommands_.isEmpty()) {
165         topicCommands_ << COMMAND_VARIABLE << COMMAND_QMLCLASS << COMMAND_QMLTYPE
166                        << COMMAND_QMLPROPERTY << COMMAND_QMLPROPERTYGROUP // mws 13/03/2019
167                        << COMMAND_QMLATTACHEDPROPERTY << COMMAND_QMLSIGNAL
168                        << COMMAND_QMLATTACHEDSIGNAL << COMMAND_QMLMETHOD
169                        << COMMAND_QMLATTACHEDMETHOD << COMMAND_QMLBASICTYPE << COMMAND_JSTYPE
170                        << COMMAND_JSPROPERTY << COMMAND_JSPROPERTYGROUP // mws 13/03/2019
171                        << COMMAND_JSATTACHEDPROPERTY << COMMAND_JSSIGNAL << COMMAND_JSATTACHEDSIGNAL
172                        << COMMAND_JSMETHOD << COMMAND_JSATTACHEDMETHOD << COMMAND_JSBASICTYPE;
173     }
174     return topicCommands_;
175 }
176 
177 #ifndef QT_NO_DECLARATIVE
178 /*!
179   Copy and paste from src/declarative/qml/qdeclarativescriptparser.cpp.
180   This function blanks out the section of the \a str beginning at \a idx
181   and running for \a n characters.
182 */
replaceWithSpace(QString & str,int idx,int n)183 static void replaceWithSpace(QString &str, int idx, int n)
184 {
185     QChar *data = str.data() + idx;
186     const QChar space(QLatin1Char(' '));
187     for (int ii = 0; ii < n; ++ii)
188         *data++ = space;
189 }
190 
191 /*!
192   Copy & paste from src/declarative/qml/qdeclarativescriptparser.cpp,
193   then modified to return no values.
194 
195   Searches for ".pragma <value>" declarations within \a script.
196   Currently supported pragmas are: library
197 */
extractPragmas(QString & script)198 void QmlCodeParser::extractPragmas(QString &script)
199 {
200     const QString pragma(QLatin1String("pragma"));
201     const QString library(QLatin1String("library"));
202 
203     QQmlJS::Lexer l(nullptr);
204     l.setCode(script, 0);
205 
206     int token = l.lex();
207 
208     while (true) {
209         if (token != QQmlJSGrammar::T_DOT)
210             return;
211 
212         int startOffset = l.tokenOffset();
213         int startLine = l.tokenStartLine();
214 
215         token = l.lex();
216 
217         if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine
218             || script.mid(l.tokenOffset(), l.tokenLength()) != pragma)
219             return;
220 
221         token = l.lex();
222 
223         if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine)
224             return;
225 
226         QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength());
227         int endOffset = l.tokenLength() + l.tokenOffset();
228 
229         token = l.lex();
230         if (l.tokenStartLine() == startLine)
231             return;
232 
233         if (pragmaValue == QLatin1String("library"))
234             replaceWithSpace(script, startOffset, endOffset - startOffset);
235         else
236             return;
237     }
238     return;
239 }
240 #endif
241 
242 QT_END_NAMESPACE
243