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::addMarkUp(const QString &code, const Node * /* relative */,
88 const Location &location)
89{
90 QQmlJS::Engine engine;
91 QQmlJS::Lexer lexer(&engine);
92
93 QString newCode = code;
94 QList<QQmlJS::SourceLocation> pragmas = extractPragmas(script&: newCode);
95 lexer.setCode(code: newCode, lineno: 1);
96
97 QQmlJS::Parser parser(&engine);
98 QString output;
99
100 if (parser.parse()) {
101 QQmlJS::AST::UiProgram *ast = parser.ast();
102 // Pass the unmodified code to the visitor so that pragmas and other
103 // unhandled source text can be output.
104 QmlMarkupVisitor visitor(code, pragmas, &engine);
105 QQmlJS::AST::Node::accept(node: ast, visitor: &visitor);
106 if (visitor.hasError()) {
107 location.warning(
108 message: location.fileName()
109 + QStringLiteral("Unable to analyze QML snippet. The output is incomplete."));
110 }
111 output = visitor.markedUpCode();
112 } else {
113 location.warning(QStringLiteral("Unable to parse QML snippet: \"%1\" at line %2, column %3")
114 .arg(parser.errorMessage())
115 .arg(parser.errorLineNumber())
116 .arg(parser.errorColumnNumber()));
117 output = protect(string: code);
118 }
119
120 return output;
121}
122
123/*
124 Copied and pasted from
125 src/declarative/qml/qqmlscriptparser.cpp.
126*/
127void replaceWithSpace(QString &str, int idx, int n); // qmlcodeparser.cpp
128
129/*
130 Copied and pasted from
131 src/declarative/qml/qqmlscriptparser.cpp then modified to
132 return a list of removed pragmas.
133
134 Searches for ".pragma <value>" or ".import <stuff>" declarations
135 in \a script. Currently supported pragmas are: library
136*/
137QList<QQmlJS::SourceLocation> QmlCodeMarker::extractPragmas(QString &script)
138{
139 QList<QQmlJS::SourceLocation> removed;
140
141 QQmlJS::Lexer l(nullptr);
142 l.setCode(code: script, lineno: 0);
143
144 int token = l.lex();
145
146 while (true) {
147 if (token != QQmlJSGrammar::T_DOT)
148 break;
149
150 int startOffset = l.tokenOffset();
151 int startLine = l.tokenStartLine();
152 int startColumn = l.tokenStartColumn();
153
154 token = l.lex();
155
156 if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT)
157 break;
158 int endOffset = 0;
159 while (startLine == l.tokenStartLine()) {
160 endOffset = l.tokenLength() + l.tokenOffset();
161 token = l.lex();
162 }
163 replaceWithSpace(str&: script, idx: startOffset, n: endOffset - startOffset);
164 removed.append(t: QQmlJS::SourceLocation(startOffset, endOffset - startOffset, startLine,
165 startColumn));
166 }
167 return removed;
168}
169
170QT_END_NAMESPACE
171

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