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