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 "qmlcodeparser.h" |
5 | |
6 | #include "node.h" |
7 | #include "qmlvisitor.h" |
8 | #include "utilities.h" |
9 | |
10 | #include <private/qqmljsast_p.h> |
11 | |
12 | #include <qdebug.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /*! |
17 | Returns "QML". |
18 | */ |
19 | QString QmlCodeParser::language() |
20 | { |
21 | return "QML" ; |
22 | } |
23 | |
24 | /*! |
25 | Returns a string list containing "*.qml". This is the only |
26 | file type parsed by the QMLN parser. |
27 | */ |
28 | QStringList QmlCodeParser::sourceFileNameFilter() |
29 | { |
30 | return QStringList() << "*.qml" ; |
31 | } |
32 | |
33 | /*! |
34 | Parses the source file at \a filePath and inserts the contents |
35 | into the database. The \a location is used for error reporting. |
36 | |
37 | If it can't open the file at \a filePath, it reports an error |
38 | and returns without doing anything. |
39 | */ |
40 | void QmlCodeParser::parseSourceFile(const Location &location, const QString &filePath, CppCodeParser&) |
41 | { |
42 | static const QSet<QString> topic_commands{ |
43 | COMMAND_VARIABLE, COMMAND_QMLCLASS, COMMAND_QMLTYPE, COMMAND_QMLPROPERTY, |
44 | COMMAND_QMLPROPERTYGROUP, COMMAND_QMLATTACHEDPROPERTY, COMMAND_QMLSIGNAL, |
45 | COMMAND_QMLATTACHEDSIGNAL, COMMAND_QMLMETHOD, COMMAND_QMLATTACHEDMETHOD, |
46 | COMMAND_QMLVALUETYPE, COMMAND_QMLBASICTYPE, |
47 | }; |
48 | |
49 | QFile in(filePath); |
50 | if (!in.open(flags: QIODevice::ReadOnly)) { |
51 | location.error(QStringLiteral("Cannot open QML file '%1'" ).arg(a: filePath)); |
52 | return; |
53 | } |
54 | |
55 | QString document = in.readAll(); |
56 | in.close(); |
57 | |
58 | QString newCode = document; |
59 | extractPragmas(script&: newCode); |
60 | |
61 | QQmlJS::Engine engine{}; |
62 | QQmlJS::Lexer lexer{&engine}; |
63 | lexer.setCode(code: newCode, lineno: 1); |
64 | |
65 | QQmlJS::Parser parser{&engine}; |
66 | |
67 | if (parser.parse()) { |
68 | QQmlJS::AST::UiProgram *ast = parser.ast(); |
69 | QmlDocVisitor visitor(filePath, newCode, &engine, topic_commands + CodeParser::common_meta_commands, |
70 | topic_commands); |
71 | QQmlJS::AST::Node::accept(node: ast, visitor: &visitor); |
72 | if (visitor.hasError()) |
73 | Location(filePath).warning(message: "Could not analyze QML file, output is incomplete." ); |
74 | } |
75 | const auto &messages = parser.diagnosticMessages(); |
76 | for (const auto &msg : messages) { |
77 | qCDebug(lcQdoc, "%s: %d: %d: QML syntax error: %s" , qUtf8Printable(filePath), |
78 | msg.loc.startLine, msg.loc.startColumn, qUtf8Printable(msg.message)); |
79 | } |
80 | } |
81 | |
82 | /*! |
83 | Copy and paste from src/declarative/qml/qdeclarativescriptparser.cpp. |
84 | This function blanks out the section of the \a str beginning at \a idx |
85 | and running for \a n characters. |
86 | */ |
87 | void replaceWithSpace(QString &str, int idx, int n) // Also used in qmlcodemarker.cpp. |
88 | { |
89 | QChar *data = str.data() + idx; |
90 | const QChar space(QLatin1Char(' ')); |
91 | for (int ii = 0; ii < n; ++ii) |
92 | *data++ = space; |
93 | } |
94 | |
95 | /*! |
96 | Copy & paste from src/declarative/qml/qdeclarativescriptparser.cpp, |
97 | then modified to return no values. |
98 | |
99 | Searches for ".pragma <value>" declarations within \a script. |
100 | Currently supported pragmas are: library |
101 | */ |
102 | void QmlCodeParser::(QString &script) |
103 | { |
104 | const QString pragma(QLatin1String("pragma" )); |
105 | |
106 | QQmlJS::Lexer l(nullptr); |
107 | l.setCode(code: script, lineno: 0); |
108 | |
109 | int token = l.lex(); |
110 | |
111 | while (true) { |
112 | if (token != QQmlJSGrammar::T_DOT) |
113 | return; |
114 | |
115 | int startOffset = l.tokenOffset(); |
116 | int startLine = l.tokenStartLine(); |
117 | |
118 | token = l.lex(); |
119 | |
120 | if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine |
121 | || script.mid(l.tokenOffset(), l.tokenLength()) != pragma) |
122 | return; |
123 | |
124 | token = l.lex(); |
125 | |
126 | if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine) |
127 | return; |
128 | |
129 | QString pragmaValue = script.mid(position: l.tokenOffset(), n: l.tokenLength()); |
130 | int endOffset = l.tokenLength() + l.tokenOffset(); |
131 | |
132 | token = l.lex(); |
133 | if (l.tokenStartLine() == startLine) |
134 | return; |
135 | |
136 | if (pragmaValue == QLatin1String("library" )) |
137 | replaceWithSpace(str&: script, idx: startOffset, n: endOffset - startOffset); |
138 | else |
139 | return; |
140 | } |
141 | } |
142 | |
143 | QT_END_NAMESPACE |
144 | |