Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmllshelputils.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QtQmlLS/private/qqmllsutils_p.h>
8#include <QtCore/private/qfactoryloader_p.h>
9#include <QtCore/qlibraryinfo.h>
10#include <QtCore/qdiriterator.h>
11#include <QtCore/qdir.h>
12#include <QtQmlCompiler/private/qqmljstyperesolver_p.h>
13#include <optional>
14
16
17Q_STATIC_LOGGING_CATEGORY(QQmlLSHelpUtilsLog, "qt.languageserver.helpUtils")
18
19using namespace QQmlJS::Dom;
20
21static QStringList documentationFiles(const QString &qtInstallationPath)
22{
23 QStringList result;
24 QDirIterator dirIterator(qtInstallationPath, QStringList{ "*.qch"_L1 }, QDir::Files);
25 while (dirIterator.hasNext()) {
26 const auto fileInfo = dirIterator.nextFileInfo();
27 result << fileInfo.absoluteFilePath();
28 }
29 return result;
30}
31
32HelpManager::HelpManager()
33{
34 const QFactoryLoader pluginLoader(QQmlLSHelpPluginInterface_iid, u"/help"_s);
35 const auto keys = pluginLoader.metaDataKeys();
36 for (qsizetype i = 0; i < keys.size(); ++i) {
37 auto instance = qobject_cast<QQmlLSHelpPluginInterface *>(pluginLoader.instance(i));
38 if (instance) {
39 m_helpPlugin =
40 instance->initialize(QDir::tempPath() + "/collectionFile.qhc"_L1, nullptr);
41 break;
42 }
43 }
44}
45
46void HelpManager::setDocumentationRootPath(const QString &path)
47{
48 QMutexLocker guard(&m_mutex);
49 if (m_docRootPath == path)
50 return;
51 m_docRootPath = path;
52
53 const auto foundQchFiles = documentationFiles(path);
54 if (foundQchFiles.isEmpty()) {
55 qCWarning(QQmlLSHelpUtilsLog)
56 << "No documentation files found in the Qt doc installation path: " << path;
57 return;
58 }
59
60 return registerDocumentations(foundQchFiles);
61}
62
63QString HelpManager::documentationRootPath() const
64{
65 QMutexLocker guard(&m_mutex);
66 return m_docRootPath;
67}
68
69void HelpManager::registerDocumentations(const QStringList &docs) const
70{
71 if (!m_helpPlugin)
72 return;
73 std::for_each(docs.cbegin(), docs.cend(),
74 [this](const auto &file) { m_helpPlugin->registerDocumentation(file); });
75}
76
77std::optional<QByteArray> HelpManager::extractDocumentation(const DomItem &item) const
78{
79 if (item.internalKind() == DomType::ScriptIdentifierExpression) {
80 const auto resolvedType =
81 QQmlLSUtils::resolveExpressionType(item, QQmlLSUtils::ResolveOwnerType);
82 if (!resolvedType)
83 return std::nullopt;
84 return extractDocumentationForIdentifiers(item, resolvedType.value());
85 } else {
86 return extractDocumentationForDomElements(item);
87 }
88
89 Q_UNREACHABLE_RETURN(std::nullopt);
90}
91
92std::optional<QByteArray>
93HelpManager::extractDocumentationForIdentifiers(const DomItem &item,
94 QQmlLSUtils::ExpressionType expr) const
95{
96 const auto links = collectDocumentationLinks(item, expr.semanticScope, expr.name.value_or(item.name()));
97 if (links.empty())
98 return std::nullopt;
99 switch (expr.type) {
100 case QQmlLSUtils::QmlObjectIdIdentifier:
101 case QQmlLSUtils::JavaScriptIdentifier:
102 case QQmlLSUtils::GroupedPropertyIdentifier:
103 case QQmlLSUtils::PropertyIdentifier: {
104 ExtractDocumentation extractor(DomType::PropertyDefinition);
105 return tryExtract(extractor, links, expr.name.value());
106 }
107 case QQmlLSUtils::PropertyChangedSignalIdentifier:
108 case QQmlLSUtils::PropertyChangedHandlerIdentifier:
109 case QQmlLSUtils::SignalIdentifier:
110 case QQmlLSUtils::SignalHandlerIdentifier:
111 case QQmlLSUtils::MethodIdentifier: {
112 ExtractDocumentation extractor(DomType::MethodInfo);
113 return tryExtract(extractor, links, expr.name.value());
114 }
115 case QQmlLSUtils::SingletonIdentifier:
116 case QQmlLSUtils::AttachedTypeIdentifier:
117 case QQmlLSUtils::AttachedTypeIdentifierInBindingTarget:
118 case QQmlLSUtils::QmlComponentIdentifier: {
119 const auto &keyword = item.field(Fields::identifier).value().toString();
120 // The keyword is a qmlobject. Keyword search should be sufficient.
121 // TODO: Still there can be multiple qmlobject documentation, with
122 // different Qt versions. We should pick the best one.
123 ExtractDocumentation extractor(DomType::QmlObject);
124 return tryExtract(extractor, m_helpPlugin->documentsForKeyword(keyword), keyword);
125 }
126
127 // Not implemented yet
128 case QQmlLSUtils::EnumeratorIdentifier:
129 case QQmlLSUtils::EnumeratorValueIdentifier:
130 default:
131 qCDebug(QQmlLSHelpUtilsLog)
132 << "Documentation extraction for" << expr.name.value() << "was not implemented";
133 return std::nullopt;
134 }
135 Q_UNREACHABLE_RETURN(std::nullopt);
136}
137
138std::optional<QByteArray> HelpManager::extractDocumentationForDomElements(const DomItem &item) const
139{
140 const auto qmlFile = item.containingFile().as<QmlFile>();
141 if (!qmlFile)
142 return std::nullopt;
143
144 const auto name = item.field(Fields::name).value().toString();
145 std::vector<QQmlLSHelpProviderBase::DocumentLink> links;
146 switch (item.internalKind()) {
147 case DomType::QmlObject: {
148 links = collectDocumentationLinks(item, item.nearestSemanticScope(), name);
149 break;
150 }
151 case DomType::PropertyDefinition: {
152 links = collectDocumentationLinks(
153 item, QQmlLSUtils::findDefiningScopeForProperty(item.nearestSemanticScope(), name),
154 name);
155 break;
156 }
157 case DomType::Binding: {
158 links = collectDocumentationLinks(
159 item, QQmlLSUtils::findDefiningScopeForBinding(item.nearestSemanticScope(), name),
160 name);
161 break;
162 }
163 case DomType::MethodInfo: {
164 links = collectDocumentationLinks(
165 item, QQmlLSUtils::findDefiningScopeForMethod(item.nearestSemanticScope(), name),
166 name);
167 break;
168 }
169 default:
170 qCDebug(QQmlLSHelpUtilsLog)
171 << item.internalKindStr() << "was not implemented for documentation extraction";
172 return std::nullopt;
173 }
174
175 ExtractDocumentation extractor(item.internalKind());
176 return tryExtract(extractor, links, name);
177}
178
179std::optional<QByteArray>
180HelpManager::tryExtract(ExtractDocumentation &extractor,
181 const std::vector<QQmlLSHelpProviderBase::DocumentLink> &links,
182 const QString &name) const
183{
184 if (!m_helpPlugin)
185 return std::nullopt;
186
187 for (auto &&link : links) {
188 const auto fileData = m_helpPlugin->fileData(link.url);
189 if (fileData.isEmpty()) {
190 qCDebug(QQmlLSHelpUtilsLog) << "No documentation found for" << link.url;
191 continue;
192 }
193 const auto &documentation = extractor.execute(QString::fromUtf8(fileData), name,
194 HtmlExtractor::ExtractionMode::Simplified);
195 if (documentation.isEmpty())
196 continue;
197 return documentation.toUtf8();
198 }
199
200 return std::nullopt;
201}
202
203std::optional<QByteArray>
204HelpManager::documentationForItem(const DomItem &file, QLspSpecification::Position position)
205{
206 QMutexLocker guard(&m_mutex);
207 if (!m_helpPlugin)
208 return std::nullopt;
209
210 if (m_helpPlugin->registeredNamespaces().empty())
211 return std::nullopt;
212
213 // Prepare Cpp types to Qml types mapping.
214 const auto fileItem = file.containingFile().as<QmlFile>();
215 if (!fileItem)
216 return std::nullopt;
217 const auto typeResolver = fileItem->typeResolver();
218 if (typeResolver) {
219 const auto &names = typeResolver->importedNames();
220 for (auto &&[scope, qmlName] : names.asKeyValueRange()) {
221 auto sc = scope;
222 // in some situations, scope->internalName() could be the same
223 // as qmlName. In those cases, the key we are looking for is the
224 // first scope which is non-composite type.
225 // This is mostly the case for templated controls.
226 // Popup <-> Popup
227 // T.Popup <-> Popup
228 // QQuickPopup <-> Popup
229 if (sc && sc->internalName() == qmlName) {
230 while (sc && sc->isComposite())
231 sc = sc->baseType();
232 }
233 if (sc && !m_cppTypesToQmlTypes.contains(sc->internalName()))
234 m_cppTypesToQmlTypes.insert(sc->internalName(), qmlName);
235 }
236 }
237
238 std::optional<QByteArray> result;
239 const auto [line, character] = position;
240 const auto itemLocations = QQmlLSUtils::itemsFromTextLocation(file, line, character);
241 // Process found item's internalKind and fetch its documentation.
242 for (const auto &entry : itemLocations) {
243 result = extractDocumentation(entry.domItem);
244 if (result.has_value())
245 break;
246 }
247
248 return result;
249}
250
251/*
252 * Returns the list of potential documentation links for the given item.
253 * A keyword is not necessarily a unique name, so we need to find the scope where
254 * the keyword is defined. If the item is a property, method or binding, it will
255 * search for the defining scope and return the documentation links by looking at
256 * the imported names. If the item is a QmlObject, it will return the documentation
257 * links for qmlobject name.
258 */
259std::vector<QQmlLSHelpProviderBase::DocumentLink>
260HelpManager::collectDocumentationLinks(const DomItem &item, const QQmlJSScope::ConstPtr &definingScope,
261 const QString &name) const
262{
263 if (!(m_helpPlugin && definingScope))
264 return {};
265 const auto &qmlFile = item.containingFile().as<QmlFile>();
266 if (!qmlFile)
267 return {};
268 const auto typeResolver = qmlFile->typeResolver();
269 if (!typeResolver)
270 return {};
271
272 std::vector<QQmlLSHelpProviderBase::DocumentLink> links;
273 const auto &foundScopeName = definingScope->internalName();
274 if (m_cppTypesToQmlTypes.contains(foundScopeName)) {
275 const QString id = m_cppTypesToQmlTypes.value(foundScopeName) + u"::"_s + name;
276 links = m_helpPlugin->documentsForIdentifier(id);
277 if (!links.empty())
278 return links;
279 }
280
281 const auto &containingObjectName = item.qmlObject().name();
282 auto scope = item.nearestSemanticScope();
283 while (scope && scope->isComposite()) {
284 const QString id = containingObjectName + u"::"_s + name;
285 links = m_helpPlugin->documentsForIdentifier(id);
286 if (!links.empty())
287 return links;
288 scope = scope->baseType();
289 }
290
291 while (scope && !m_cppTypesToQmlTypes.contains(scope->internalName())) {
292 const QString id = m_cppTypesToQmlTypes.value(scope->internalName()) + u"::"_s + name;
293 links = m_helpPlugin->documentsForIdentifier(id);
294 if (!links.empty())
295 return links;
296 scope = scope->baseType();
297 }
298
299 return m_helpPlugin->documentsForKeyword(name);
300}
301
302QT_END_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
static QStringList documentationFiles(const QString &qtInstallationPath)