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