1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qmlcodemarker.h" |
5 | |
6 | #include <QtCore/qregularexpression.h> |
7 | |
8 | #include "atom.h" |
9 | #include "node.h" |
10 | #include "qmlmarkupvisitor.h" |
11 | #include "text.h" |
12 | |
13 | #include <private/qqmljsast_p.h> |
14 | #include <private/qqmljsastfwd_p.h> |
15 | #include <private/qqmljsengine_p.h> |
16 | #include <private/qqmljslexer_p.h> |
17 | #include <private/qqmljsparser_p.h> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | /*! |
22 | Returns \c true if the \a code is recognized by the parser. |
23 | */ |
24 | bool QmlCodeMarker::recognizeCode(const QString &code) |
25 | { |
26 | // Naive pre-check; starts with an import statement or 'CamelCase {' |
27 | static const QRegularExpression regExp(QStringLiteral("^\\s*(import |([A-Z][a-z0-9]*)+\\s?{)" )); |
28 | if (!regExp.match(subject: code).hasMatch()) |
29 | return false; |
30 | |
31 | QQmlJS::Engine engine; |
32 | QQmlJS::Lexer lexer(&engine); |
33 | QQmlJS::Parser parser(&engine); |
34 | |
35 | QString newCode = code; |
36 | extractPragmas(script&: newCode); |
37 | lexer.setCode(code: newCode, lineno: 1); |
38 | |
39 | return parser.parse(); |
40 | } |
41 | |
42 | /*! |
43 | Returns \c true if \a ext is any of a list of file extensions |
44 | for the QML language. |
45 | */ |
46 | bool QmlCodeMarker::recognizeExtension(const QString &ext) |
47 | { |
48 | return ext == "qml" ; |
49 | } |
50 | |
51 | /*! |
52 | Returns \c true if the \a language is recognized. Only "QML" is |
53 | recognized by this marker. |
54 | */ |
55 | bool QmlCodeMarker::recognizeLanguage(const QString &language) |
56 | { |
57 | return language == "QML" ; |
58 | } |
59 | |
60 | /*! |
61 | Returns the type of atom used to represent QML code in the documentation. |
62 | */ |
63 | Atom::AtomType QmlCodeMarker::atomType() const |
64 | { |
65 | return Atom::Qml; |
66 | } |
67 | |
68 | QString QmlCodeMarker::markedUpCode(const QString &code, const Node *relative, |
69 | const Location &location) |
70 | { |
71 | return addMarkUp(code, relative, location); |
72 | } |
73 | |
74 | /*! |
75 | Constructs and returns the marked up name for the \a node. |
76 | If the node is any kind of QML function (a method, |
77 | signal, or handler), "()" is appended to the marked up name. |
78 | */ |
79 | QString QmlCodeMarker::markedUpName(const Node *node) |
80 | { |
81 | QString name = linkTag(node, body: taggedNode(node)); |
82 | if (node->isFunction()) |
83 | name += "()" ; |
84 | return name; |
85 | } |
86 | |
87 | QString QmlCodeMarker::markedUpInclude(const QString &include) |
88 | { |
89 | return addMarkUp(code: "import " + include, relative: nullptr, location: Location{}); |
90 | } |
91 | |
92 | QString QmlCodeMarker::addMarkUp(const QString &code, const Node * /* relative */, |
93 | const Location &location) |
94 | { |
95 | QQmlJS::Engine engine; |
96 | QQmlJS::Lexer lexer(&engine); |
97 | |
98 | QString newCode = code; |
99 | QList<QQmlJS::SourceLocation> pragmas = extractPragmas(script&: newCode); |
100 | lexer.setCode(code: newCode, lineno: 1); |
101 | |
102 | QQmlJS::Parser parser(&engine); |
103 | QString output; |
104 | |
105 | if (parser.parse()) { |
106 | QQmlJS::AST::UiProgram *ast = parser.ast(); |
107 | // Pass the unmodified code to the visitor so that pragmas and other |
108 | // unhandled source text can be output. |
109 | QmlMarkupVisitor visitor(code, pragmas, &engine); |
110 | QQmlJS::AST::Node::accept(node: ast, visitor: &visitor); |
111 | if (visitor.hasError()) { |
112 | location.warning( |
113 | message: location.fileName() |
114 | + QStringLiteral("Unable to analyze QML snippet. The output is incomplete." )); |
115 | } |
116 | output = visitor.markedUpCode(); |
117 | } else { |
118 | location.warning(QStringLiteral("Unable to parse QML snippet: \"%1\" at line %2, column %3" ) |
119 | .arg(parser.errorMessage()) |
120 | .arg(parser.errorLineNumber()) |
121 | .arg(parser.errorColumnNumber())); |
122 | output = protect(string: code); |
123 | } |
124 | |
125 | return output; |
126 | } |
127 | |
128 | /* |
129 | Copied and pasted from |
130 | src/declarative/qml/qqmlscriptparser.cpp. |
131 | */ |
132 | void replaceWithSpace(QString &str, int idx, int n); // qmlcodeparser.cpp |
133 | |
134 | /* |
135 | Copied and pasted from |
136 | src/declarative/qml/qqmlscriptparser.cpp then modified to |
137 | return a list of removed pragmas. |
138 | |
139 | Searches for ".pragma <value>" or ".import <stuff>" declarations |
140 | in \a script. Currently supported pragmas are: library |
141 | */ |
142 | QList<QQmlJS::SourceLocation> QmlCodeMarker::(QString &script) |
143 | { |
144 | QList<QQmlJS::SourceLocation> removed; |
145 | |
146 | QQmlJS::Lexer l(nullptr); |
147 | l.setCode(code: script, lineno: 0); |
148 | |
149 | int token = l.lex(); |
150 | |
151 | while (true) { |
152 | if (token != QQmlJSGrammar::T_DOT) |
153 | break; |
154 | |
155 | int startOffset = l.tokenOffset(); |
156 | int startLine = l.tokenStartLine(); |
157 | int startColumn = l.tokenStartColumn(); |
158 | |
159 | token = l.lex(); |
160 | |
161 | if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT) |
162 | break; |
163 | int endOffset = 0; |
164 | while (startLine == l.tokenStartLine()) { |
165 | endOffset = l.tokenLength() + l.tokenOffset(); |
166 | token = l.lex(); |
167 | } |
168 | replaceWithSpace(str&: script, idx: startOffset, n: endOffset - startOffset); |
169 | removed.append(t: QQmlJS::SourceLocation(startOffset, endOffset - startOffset, startLine, |
170 | startColumn)); |
171 | } |
172 | return removed; |
173 | } |
174 | |
175 | QT_END_NAMESPACE |
176 | |