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 qmlcodemarker.cpp
31 */
32
33 #include "qmlcodemarker.h"
34
35 #include "atom.h"
36 #include "node.h"
37 #include "qmlmarkupvisitor.h"
38 #include "text.h"
39 #include "tree.h"
40 #include "generator.h"
41
42 #ifndef QT_NO_DECLARATIVE
43 # include <private/qqmljsast_p.h>
44 # include <private/qqmljsastfwd_p.h>
45 # include <private/qqmljsengine_p.h>
46 # include <private/qqmljslexer_p.h>
47 # include <private/qqmljsparser_p.h>
48 #endif
49
50 QT_BEGIN_NAMESPACE
51
QmlCodeMarker()52 QmlCodeMarker::QmlCodeMarker() {}
53
~QmlCodeMarker()54 QmlCodeMarker::~QmlCodeMarker() {}
55
56 /*!
57 Returns \c true if the \a code is recognized by the parser.
58 */
recognizeCode(const QString & code)59 bool QmlCodeMarker::recognizeCode(const QString &code)
60 {
61 #ifndef QT_NO_DECLARATIVE
62 QQmlJS::Engine engine;
63 QQmlJS::Lexer lexer(&engine);
64 QQmlJS::Parser parser(&engine);
65
66 QString newCode = code;
67 extractPragmas(newCode);
68 lexer.setCode(newCode, 1);
69
70 return parser.parse();
71 #else
72 return false;
73 #endif
74 }
75
76 /*!
77 Returns \c true if \a ext is any of a list of file extensions
78 for the QML language.
79 */
recognizeExtension(const QString & ext)80 bool QmlCodeMarker::recognizeExtension(const QString &ext)
81 {
82 return ext == "qml";
83 }
84
85 /*!
86 Returns \c true if the \a language is recognized. Only "QML" is
87 recognized by this marker.
88 */
recognizeLanguage(const QString & language)89 bool QmlCodeMarker::recognizeLanguage(const QString &language)
90 {
91 return language == "QML";
92 }
93
94 /*!
95 Returns the type of atom used to represent QML code in the documentation.
96 */
atomType() const97 Atom::AtomType QmlCodeMarker::atomType() const
98 {
99 return Atom::Qml;
100 }
101
markedUpCode(const QString & code,const Node * relative,const Location & location)102 QString QmlCodeMarker::markedUpCode(const QString &code, const Node *relative,
103 const Location &location)
104 {
105 return addMarkUp(code, relative, location);
106 }
107
108 /*!
109 Constructs and returns the marked up name for the \a node.
110 If the node is any kind of QML or JS function (a method,
111 signal, or handler), "()" is appended to the marked up name.
112 */
markedUpName(const Node * node)113 QString QmlCodeMarker::markedUpName(const Node *node)
114 {
115 QString name = linkTag(node, taggedNode(node));
116 if (node->isFunction())
117 name += "()";
118 return name;
119 }
120
markedUpFullName(const Node * node,const Node * relative)121 QString QmlCodeMarker::markedUpFullName(const Node *node, const Node *relative)
122 {
123 if (node->name().isEmpty()) {
124 return "global";
125 } else {
126 QString fullName;
127 for (;;) {
128 fullName.prepend(markedUpName(node));
129 if (node->parent() == relative || node->parent()->name().isEmpty())
130 break;
131 fullName.prepend("<@op>::</@op>");
132 node = node->parent();
133 }
134 return fullName;
135 }
136 }
137
markedUpIncludes(const QStringList & includes)138 QString QmlCodeMarker::markedUpIncludes(const QStringList &includes)
139 {
140 QString code;
141
142 for (const auto &include : includes)
143 code += "import " + include + QLatin1Char('\n');
144
145 Location location;
146 return addMarkUp(code, nullptr, location);
147 }
148
functionBeginRegExp(const QString & funcName)149 QString QmlCodeMarker::functionBeginRegExp(const QString &funcName)
150 {
151 return QLatin1Char('^') + QRegExp::escape("function " + funcName) + QLatin1Char('$');
152 }
153
functionEndRegExp(const QString &)154 QString QmlCodeMarker::functionEndRegExp(const QString & /* funcName */)
155 {
156 return "^\\}$";
157 }
158
addMarkUp(const QString & code,const Node *,const Location & location)159 QString QmlCodeMarker::addMarkUp(const QString &code, const Node * /* relative */,
160 const Location &location)
161 {
162 #ifndef QT_NO_DECLARATIVE
163 QQmlJS::Engine engine;
164 QQmlJS::Lexer lexer(&engine);
165
166 QString newCode = code;
167 QVector<QQmlJS::SourceLocation> pragmas = extractPragmas(newCode);
168 lexer.setCode(newCode, 1);
169
170 QQmlJS::Parser parser(&engine);
171 QString output;
172
173 if (parser.parse()) {
174 QQmlJS::AST::UiProgram *ast = parser.ast();
175 // Pass the unmodified code to the visitor so that pragmas and other
176 // unhandled source text can be output.
177 QmlMarkupVisitor visitor(code, pragmas, &engine);
178 QQmlJS::AST::Node::accept(ast, &visitor);
179 if (visitor.hasError()) {
180 location.warning(location.fileName()
181 + tr("Unable to analyze QML snippet. The output is incomplete."));
182 }
183 output = visitor.markedUpCode();
184 } else {
185 location.warning(tr("Unable to parse QML snippet: \"%1\" at line %2, column %3")
186 .arg(parser.errorMessage())
187 .arg(parser.errorLineNumber())
188 .arg(parser.errorColumnNumber()));
189 output = protect(code);
190 }
191
192 return output;
193 #else
194 location.warning("QtDeclarative not installed; cannot parse QML or JS.");
195 return QString();
196 #endif
197 }
198
199 #ifndef QT_NO_DECLARATIVE
200 /*
201 Copied and pasted from
202 src/declarative/qml/qqmlscriptparser.cpp.
203 */
replaceWithSpace(QString & str,int idx,int n)204 static void replaceWithSpace(QString &str, int idx, int n)
205 {
206 QChar *data = str.data() + idx;
207 const QChar space(QLatin1Char(' '));
208 for (int ii = 0; ii < n; ++ii)
209 *data++ = space;
210 }
211
212 /*
213 Copied and pasted from
214 src/declarative/qml/qqmlscriptparser.cpp then modified to
215 return a list of removed pragmas.
216
217 Searches for ".pragma <value>" or ".import <stuff>" declarations
218 in \a script. Currently supported pragmas are: library
219 */
extractPragmas(QString & script)220 QVector<QQmlJS::SourceLocation> QmlCodeMarker::extractPragmas(QString &script)
221 {
222 const QString pragma(QLatin1String("pragma"));
223 const QString library(QLatin1String("library"));
224 QVector<QQmlJS::SourceLocation> removed;
225
226 QQmlJS::Lexer l(nullptr);
227 l.setCode(script, 0);
228
229 int token = l.lex();
230
231 while (true) {
232 if (token != QQmlJSGrammar::T_DOT)
233 return removed;
234
235 int startOffset = l.tokenOffset();
236 int startLine = l.tokenStartLine();
237 int startColumn = l.tokenStartColumn();
238
239 token = l.lex();
240
241 if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT)
242 return removed;
243 int endOffset = 0;
244 while (startLine == l.tokenStartLine()) {
245 endOffset = l.tokenLength() + l.tokenOffset();
246 token = l.lex();
247 }
248 replaceWithSpace(script, startOffset, endOffset - startOffset);
249 removed.append(QQmlJS::SourceLocation(startOffset, endOffset - startOffset, startLine,
250 startColumn));
251 }
252 return removed;
253 }
254 #endif
255
256 QT_END_NAMESPACE
257