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
19QT_BEGIN_NAMESPACE
20
21/*!
22 Returns \c true if the \a code is recognized by the parser.
23 */
24bool 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 */
46bool 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 */
55bool 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*/
63Atom::AtomType QmlCodeMarker::atomType() const
64{
65 return Atom::Qml;
66}
67
68QString 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 */
79QString 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
87QString QmlCodeMarker::markedUpInclude(const QString &include)
88{
89 return addMarkUp(code: "import " + include, relative: nullptr, location: Location{});
90}
91
92QString 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*/
132void 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*/
142QList<QQmlJS::SourceLocation> QmlCodeMarker::extractPragmas(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
175QT_END_NAMESPACE
176

source code of qttools/src/qdoc/qdoc/qmlcodemarker.cpp