1// Copyright (C) 2023 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
4#include "qqmllsutils_p.h"
5
6#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
7#include <QtCore/qthreadpool.h>
8#include <QtCore/private/qduplicatetracker_p.h>
9#include <QtCore/QRegularExpression>
10#include <QtQmlDom/private/qqmldomexternalitems_p.h>
11#include <QtQmlDom/private/qqmldomtop_p.h>
12#include <QtQmlDom/private/qqmldomscriptelements_p.h>
13#include <QtQmlDom/private/qqmldom_utils_p.h>
14#include <memory>
15#include <optional>
16#include <stack>
17#include <type_traits>
18#include <utility>
19#include <variant>
20
21using namespace QLspSpecification;
22using namespace QQmlJS::Dom;
23using namespace Qt::StringLiterals;
24
25QT_BEGIN_NAMESPACE
26
27Q_LOGGING_CATEGORY(QQmlLSUtilsLog, "qt.languageserver.utils")
28
29/*!
30 \internal
31 Helper to check if item is a Field Member Expression \c {<someExpression>.propertyName}.
32*/
33static bool isFieldMemberExpression(DomItem &item)
34{
35 return item.internalKind() == DomType::ScriptBinaryExpression
36 && item.field(name: Fields::operation).value().toInteger()
37 == ScriptElements::BinaryExpression::FieldMemberAccess;
38}
39
40/*!
41 \internal
42 Helper to check if item is a Field Member Access \c memberAccess in
43 \c {<someExpression>.memberAccess}.
44*/
45static bool isFieldMemberAccess(DomItem &item)
46{
47 auto parent = item.directParent();
48 if (!isFieldMemberExpression(item&: parent))
49 return false;
50
51 DomItem rightHandSide = parent.field(name: Fields::right);
52 return item == rightHandSide;
53}
54
55/*!
56 \internal
57 The language server protocol calls "URI" what QML calls "URL".
58 According to RFC 3986, a URL is a special case of URI that not only
59 identifies a resource but also shows how to access it.
60 In QML, however, URIs are distinct from URLs. URIs are the
61 identifiers of modules, for example "QtQuick.Controls".
62 In order to not confuse the terms we interpret language server URIs
63 as URLs in the QML code model.
64 This method marks a point of translation between the terms, but does
65 not have to change the actual URI/URL.
66
67 \sa QQmlLSUtils::qmlUriToLspUrl
68 */
69QByteArray QQmlLSUtils::lspUriToQmlUrl(const QByteArray &uri)
70{
71 return uri;
72}
73
74QByteArray QQmlLSUtils::qmlUrlToLspUri(const QByteArray &url)
75{
76 return url;
77}
78
79/*!
80 \internal
81 \brief Converts a QQmlJS::SourceLocation to a LSP Range.
82
83 QQmlJS::SourceLocation starts counting lines and rows at 1, but the LSP Range starts at 0.
84 Also, the QQmlJS::SourceLocation contains startLine, startColumn and length while the LSP Range
85 contains startLine, startColumn, endLine and endColumn, which must be computed from the actual
86 qml code.
87 */
88QLspSpecification::Range QQmlLSUtils::qmlLocationToLspLocation(const QString &code,
89 QQmlJS::SourceLocation qmlLocation)
90{
91 Range range;
92
93 range.start.line = qmlLocation.startLine - 1;
94 range.start.character = qmlLocation.startColumn - 1;
95
96 auto end = QQmlLSUtils::textRowAndColumnFrom(code, offset: qmlLocation.end());
97 range.end.line = end.line;
98 range.end.character = end.character;
99 return range;
100}
101
102/*!
103 \internal
104 \brief Convert a text position from (line, column) into an offset.
105
106 Row, Column and the offset are all 0-based.
107 For example, \c{s[textOffsetFrom(s, 5, 55)]} returns the character of s at line 5 and column 55.
108
109 \sa QQmlLSUtils::textRowAndColumnFrom
110*/
111qsizetype QQmlLSUtils::textOffsetFrom(const QString &text, int row, int column)
112{
113 int targetLine = row;
114 qsizetype i = 0;
115 while (i != text.size() && targetLine != 0) {
116 QChar c = text.at(i: i++);
117 if (c == u'\n') {
118 --targetLine;
119 }
120 if (c == u'\r') {
121 if (i != text.size() && text.at(i) == u'\n')
122 ++i;
123 --targetLine;
124 }
125 }
126 qsizetype leftChars = column;
127 while (i != text.size() && leftChars) {
128 QChar c = text.at(i);
129 if (c == u'\n' || c == u'\r')
130 break;
131 ++i;
132 if (!c.isLowSurrogate())
133 --leftChars;
134 }
135 return i;
136}
137
138/*!
139 \internal
140 \brief Convert a text position from an offset into (line, column).
141
142 Row, Column and the offset are all 0-based.
143 For example, \c{textRowAndColumnFrom(s, 55)} returns the line and columns of the
144 character at \c {s[55]}.
145
146 \sa QQmlLSUtils::textOffsetFrom
147*/
148QQmlLSUtilsTextPosition QQmlLSUtils::textRowAndColumnFrom(const QString &text, qsizetype offset)
149{
150 int row = 0;
151 int column = 0;
152 qsizetype currentLineOffset = 0;
153 for (qsizetype i = 0; i < offset; i++) {
154 QChar c = text[i];
155 if (c == u'\n') {
156 row++;
157 currentLineOffset = i + 1;
158 } else if (c == u'\r') {
159 if (i > 0 && text[i - 1] == u'\n')
160 currentLineOffset++;
161 }
162 }
163 column = offset - currentLineOffset;
164
165 return { .line: row, .character: column };
166}
167
168/*!
169 \internal
170 \brief Find the DomItem representing the object situated in file at given line and
171 character/column.
172
173 If line and character point between two objects, two objects might be returned.
174 If line and character point to whitespace, it might return an inner node of the QmlDom-Tree.
175 */
176QList<QQmlLSUtilsItemLocation> QQmlLSUtils::itemsFromTextLocation(DomItem file, int line,
177 int character)
178{
179 QList<QQmlLSUtilsItemLocation> itemsFound;
180 std::shared_ptr<QmlFile> filePtr = file.ownerAs<QmlFile>();
181 if (!filePtr)
182 return itemsFound;
183 FileLocations::Tree t = filePtr->fileLocationsTree();
184 Q_ASSERT(t);
185 QString code = filePtr->code(); // do something more advanced wrt to changes wrt to this->code?
186 QList<QQmlLSUtilsItemLocation> toDo;
187 qsizetype targetPos = textOffsetFrom(text: code, row: line, column: character);
188 Q_ASSERT(targetPos >= 0);
189 auto containsTarget = [targetPos](QQmlJS::SourceLocation l) {
190 if constexpr (sizeof(qsizetype) <= sizeof(quint32)) {
191 return l.begin() <= quint32(targetPos) && quint32(targetPos) <= l.end();
192 } else {
193 return l.begin() <= targetPos && targetPos <= l.end();
194 }
195 };
196 if (containsTarget(t->info().fullRegion)) {
197 QQmlLSUtilsItemLocation loc;
198 loc.domItem = file;
199 loc.fileLocation = t;
200 toDo.append(t: loc);
201 }
202 while (!toDo.isEmpty()) {
203 QQmlLSUtilsItemLocation iLoc = toDo.last();
204 toDo.removeLast();
205
206 bool inParentButOutsideChildren = true;
207
208 auto subEls = iLoc.fileLocation->subItems();
209 for (auto it = subEls.begin(); it != subEls.end(); ++it) {
210 auto subLoc = std::static_pointer_cast<AttachedInfoT<FileLocations>>(r: it.value());
211 Q_ASSERT(subLoc);
212
213 if (containsTarget(subLoc->info().fullRegion)) {
214 QQmlLSUtilsItemLocation subItem;
215 subItem.domItem = iLoc.domItem.path(p: it.key());
216 if (!subItem.domItem) {
217 qCDebug(QQmlLSUtilsLog)
218 << "A DomItem child is missing or the FileLocationsTree structure does "
219 "not follow the DomItem Structure.";
220 continue;
221 }
222 subItem.fileLocation = subLoc;
223 toDo.append(t: subItem);
224 inParentButOutsideChildren = false;
225 }
226 }
227 if (inParentButOutsideChildren) {
228 itemsFound.append(t: iLoc);
229 }
230 }
231
232 // filtering step:
233 if (itemsFound.size() > 1) {
234 // if there are multiple items, take the smallest one + its neighbors
235 // this allows to prefer inline components over main components, when both contain the
236 // current textposition, and to disregard internal structures like property maps, which
237 // "contain" everything from their first-appearing to last-appearing property (e.g. also
238 // other stuff in between those two properties).
239 auto smallest = std::min_element(
240 first: itemsFound.begin(), last: itemsFound.end(),
241 comp: [](const QQmlLSUtilsItemLocation &a, const QQmlLSUtilsItemLocation &b) {
242 return a.fileLocation->info().fullRegion.length
243 < b.fileLocation->info().fullRegion.length;
244 });
245 QList<QQmlLSUtilsItemLocation> filteredItems;
246 filteredItems.append(t: *smallest);
247
248 const QQmlJS::SourceLocation smallestLoc = smallest->fileLocation->info().fullRegion;
249 const quint32 smallestBegin = smallestLoc.begin();
250 const quint32 smallestEnd = smallestLoc.end();
251
252 for (auto it = itemsFound.begin(); it != itemsFound.end(); it++) {
253 if (it == smallest)
254 continue;
255
256 const QQmlJS::SourceLocation itLoc = it->fileLocation->info().fullRegion;
257 const quint32 itBegin = itLoc.begin();
258 const quint32 itEnd = itLoc.end();
259 if (itBegin == smallestEnd || smallestBegin == itEnd) {
260 filteredItems.append(t: *it);
261 }
262 }
263 itemsFound = filteredItems;
264 }
265 return itemsFound;
266}
267
268DomItem QQmlLSUtils::baseObject(DomItem object)
269{
270 if (!object.as<QmlObject>())
271 return {};
272
273 auto prototypes = object.field(name: QQmlJS::Dom::Fields::prototypes);
274 switch (prototypes.indexes()) {
275 case 0:
276 return {};
277 case 1:
278 break;
279 default:
280 qDebug() << "Multiple prototypes found for " << object.name() << ", taking the first one.";
281 break;
282 }
283 QQmlJS::Dom::DomItem base = prototypes.index(0).proceedToScope();
284 return base;
285}
286
287/*!
288 \internal
289 \brief Extracts the QML object type of an \l DomItem.
290
291 For a \c PropertyDefinition, return the type of the property.
292 For a \c Binding, return the bound item's type if an QmlObject is bound, and otherwise the type
293 of the property.
294 For a \c QmlObject, do nothing and return it.
295 For an \c Id, return the object to
296 which the id resolves.
297 For a \c Methodparameter, return the type of the parameter. =
298 Otherwise, return an empty item.
299 */
300DomItem QQmlLSUtils::findTypeDefinitionOf(DomItem object)
301{
302 DomItem result;
303
304 switch (object.internalKind()) {
305 case QQmlJS::Dom::DomType::QmlComponent:
306 result = object.field(name: Fields::objects).index(0);
307 break;
308 case QQmlJS::Dom::DomType::QmlObject:
309 result = baseObject(object);
310 break;
311 case QQmlJS::Dom::DomType::Binding: {
312 auto binding = object.as<Binding>();
313 Q_ASSERT(binding);
314
315 // try to grab the type from the bound object
316 if (binding->valueKind() == BindingValueKind::Object) {
317 result = baseObject(object: object.field(name: Fields::value));
318 } else {
319 // use the type of the property it is bound on for scriptexpression etc.
320 DomItem propertyDefinition;
321 const QString bindingName = binding->name();
322 object.containingObject().visitLookup(
323 symbolName: bindingName,
324 visitor: [&propertyDefinition](DomItem &item) {
325 if (item.internalKind() == QQmlJS::Dom::DomType::PropertyDefinition) {
326 propertyDefinition = item;
327 return false;
328 }
329 return true;
330 },
331 type: LookupType::PropertyDef);
332 result = propertyDefinition.field(name: Fields::type).proceedToScope();
333 }
334 break;
335 }
336 case QQmlJS::Dom::DomType::Id:
337 result = object.field(name: Fields::referredObject).proceedToScope();
338 break;
339 case QQmlJS::Dom::DomType::PropertyDefinition:
340 case QQmlJS::Dom::DomType::MethodParameter:
341 case QQmlJS::Dom::DomType::MethodInfo:
342 result = object.field(name: Fields::type).proceedToScope();
343 break;
344 case QQmlJS::Dom::DomType::ScriptIdentifierExpression: {
345 if (object.directParent().internalKind() == DomType::ScriptType) {
346 DomItem type =
347 object.filterUp(filter: [](DomType k, DomItem &) { return k == DomType::ScriptType; },
348 options: FilterUpOptions::ReturnOuter);
349
350 const QString name = type.field(name: Fields::typeName).value().toString();
351 result = object.path(p: Paths::lookupTypePath(name));
352 break;
353 }
354 auto scope =
355 QQmlLSUtils::resolveExpressionType(item: object, QQmlLSUtilsResolveOptions::Everything);
356 if (!scope)
357 return {};
358
359 return QQmlLSUtils::sourceLocationToDomItem(file: object.containingFile(),
360 location: scope->sourceLocation());
361 }
362 case QQmlJS::Dom::DomType::Empty:
363 break;
364 default:
365 qDebug() << "QQmlLSUtils::findTypeDefinitionOf: Found unimplemented Type"
366 << object.internalKindStr();
367 result = {};
368 }
369
370 return result;
371}
372
373static DomItem findJSIdentifierDefinition(DomItem item, const QString &name)
374{
375 DomItem definitionOfItem;
376 item.visitUp(visitor: [&name, &definitionOfItem](DomItem &i) {
377 if (std::optional<QQmlJSScope::Ptr> scope = i.semanticScope(); scope) {
378 qCDebug(QQmlLSUtilsLog) << "Searching for definition in" << i.internalKindStr();
379 if (auto jsIdentifier = scope.value()->JSIdentifier(id: name)) {
380 qCDebug(QQmlLSUtilsLog) << "Found scope" << scope.value()->baseTypeName();
381 definitionOfItem = i;
382 return false;
383 }
384 }
385 // early exit: no JS definitions/usages outside the ScriptExpression DOM element.
386 if (i.internalKind() == DomType::ScriptExpression)
387 return false;
388 return true;
389 });
390
391 return definitionOfItem;
392}
393
394static void findUsagesOfPropertiesAndIds(DomItem item, const QString &name,
395 QList<QQmlLSUtilsLocation> &result)
396{
397 QQmlJSScope::ConstPtr targetType;
398 targetType = QQmlLSUtils::resolveExpressionType(item, QQmlLSUtilsResolveOptions::JustOwner);
399
400 auto findUsages = [&targetType, &result, &name](Path, DomItem &current, bool) -> bool {
401 bool resolveType = false;
402 bool continueForChildren = true;
403 DomItem toBeResolved = current;
404
405 if (auto scope = current.semanticScope()) {
406 // is the current property shadowed by some JS identifier? ignore current + its children
407 if (scope.value()->JSIdentifier(id: name)) {
408 return false;
409 }
410 }
411
412 switch (current.internalKind()) {
413 case DomType::PropertyDefinition: {
414 const QString propertyName = current.field(name: Fields::name).value().toString();
415 if (name == propertyName) {
416 resolveType = true;
417 } else {
418 return true;
419 }
420 break;
421 }
422 case DomType::ScriptIdentifierExpression: {
423 const QString propertyName = current.field(name: Fields::identifier).value().toString();
424 if (name != propertyName)
425 return true;
426
427 resolveType = true;
428 break;
429 }
430 default:
431 break;
432 };
433
434 if (resolveType) {
435 auto currentType = QQmlLSUtils::resolveExpressionType(
436 item: toBeResolved, QQmlLSUtilsResolveOptions::JustOwner);
437 qCDebug(QQmlLSUtilsLog) << "Will resolve type of" << toBeResolved.internalKindStr();
438 if (currentType == targetType) {
439 auto tree = FileLocations::treeOf(current);
440 QQmlLSUtilsLocation location{ .filename: current.canonicalFilePath(),
441 .location: tree->info().fullRegion };
442 result.append(t: location);
443 }
444 }
445 return continueForChildren;
446 };
447
448 item.containingFile()
449 .field(name: Fields::components)
450 .visitTree(basePath: Path(), visitor: emptyChildrenVisitor, options: VisitOption::Recurse | VisitOption::VisitSelf,
451 openingVisitor: findUsages);
452}
453
454static void findUsagesHelper(DomItem item, const QString &name, QList<QQmlLSUtilsLocation> &result)
455{
456 qCDebug(QQmlLSUtilsLog) << "Looking for JS identifier with name" << name;
457 DomItem definitionOfItem = findJSIdentifierDefinition(item, name);
458
459 // if there is no definition found: check if name was a property or an id instead
460 if (!definitionOfItem) {
461 qCDebug(QQmlLSUtilsLog) << "No defining JS-Scope found!";
462 findUsagesOfPropertiesAndIds(item, name, result);
463 return;
464 }
465
466 definitionOfItem.visitTree(
467 basePath: Path(), visitor: emptyChildrenVisitor, options: VisitOption::VisitAdopted | VisitOption::Recurse,
468 openingVisitor: [&name, &result](Path, DomItem &item, bool) -> bool {
469 qCDebug(QQmlLSUtilsLog) << "Visiting a " << item.internalKindStr();
470 if (item.internalKind() == DomType::ScriptIdentifierExpression
471 && item.field(name: Fields::identifier).value().toString() == name) {
472 // add this usage
473 auto fileLocation = FileLocations::treeOf(item);
474 if (!fileLocation) {
475 qCWarning(QQmlLSUtilsLog) << "Failed finding filelocation of found usage";
476 return true;
477 }
478 const QQmlJS::SourceLocation location = fileLocation->info().fullRegion;
479 const QString fileName = item.canonicalFilePath();
480 result.append(t: { .filename: fileName, .location: location });
481 return true;
482 } else if (std::optional<QQmlJSScope::Ptr> scope = item.semanticScope();
483 scope && scope.value()->JSIdentifier(id: name)) {
484 // current JS identifier has been redefined, do not visit children
485 return false;
486 }
487 return true;
488 });
489}
490
491QList<QQmlLSUtilsLocation> QQmlLSUtils::findUsagesOf(DomItem item)
492{
493 QList<QQmlLSUtilsLocation> result;
494
495 switch (item.internalKind()) {
496 case DomType::ScriptIdentifierExpression: {
497 const QString name = item.field(name: Fields::identifier).value().toString();
498 findUsagesHelper(item, name, result);
499 break;
500 }
501 case DomType::ScriptVariableDeclarationEntry: {
502 const QString name = item.field(name: Fields::identifier).value().toString();
503 findUsagesHelper(item, name, result);
504 break;
505 }
506 case DomType::PropertyDefinition: {
507 const QString name = item.field(name: Fields::name).value().toString();
508 findUsagesHelper(item, name, result);
509 break;
510 }
511 default:
512 qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
513 << "was not implemented for QQmlLSUtils::findUsagesOf";
514 return result;
515 }
516
517 std::sort(first: result.begin(), last: result.end());
518
519 if (QQmlLSUtilsLog().isDebugEnabled()) {
520 qCDebug(QQmlLSUtilsLog) << "Found following usages:";
521 for (auto r : result) {
522 qCDebug(QQmlLSUtilsLog)
523 << r.filename << " @ " << r.location.startLine << ":" << r.location.startColumn
524 << " with length " << r.location.length;
525 }
526 }
527
528 return result;
529}
530
531static QQmlJSScope::ConstPtr findPropertyIn(const QQmlJSScope::Ptr &referrerScope,
532 const QString &propertyName,
533 QQmlLSUtilsResolveOptions options)
534{
535 for (QQmlJSScope::Ptr current = referrerScope; current; current = current->parentScope()) {
536 if (auto property = current->property(name: propertyName); property.isValid()) {
537 switch (options) {
538 case JustOwner:
539 return current;
540 case Everything:
541 return property.type();
542 }
543 }
544 }
545 return {};
546}
547
548/*!
549 \internal
550 Resolves the type of the given DomItem, when possible (e.g., when there are enough type
551 annotations).
552*/
553QQmlJSScope::ConstPtr QQmlLSUtils::resolveExpressionType(QQmlJS::Dom::DomItem item,
554 QQmlLSUtilsResolveOptions options)
555{
556 switch (item.internalKind()) {
557 case DomType::ScriptIdentifierExpression: {
558 auto referrerScope = item.nearestSemanticScope();
559 if (!referrerScope)
560 return {};
561
562 const QString name = item.field(name: Fields::identifier).value().toString();
563
564 if (isFieldMemberAccess(item)) {
565 DomItem parent = item.directParent();
566 auto owner = QQmlLSUtils::resolveExpressionType(item: parent.field(name: Fields::left),
567 options: QQmlLSUtilsResolveOptions::Everything);
568 if (owner) {
569 if (auto property = owner->property(name); property.isValid()) {
570 switch (options) {
571 case JustOwner:
572 return owner;
573 case Everything:
574 return property.type();
575 }
576 }
577 } else {
578 return {};
579 }
580 } else {
581 DomItem definitionOfItem = findJSIdentifierDefinition(item, name);
582 if (definitionOfItem) {
583 Q_ASSERT_X(definitionOfItem.semanticScope().has_value()
584 && definitionOfItem.semanticScope()
585 .value()
586 ->JSIdentifier(name)
587 .has_value(),
588 "QQmlLSUtils::findDefinitionOf",
589 "JS definition does not actually define the JS identifer. "
590 "It should be empty.");
591 auto scope = definitionOfItem.semanticScope().value()->JSIdentifier(id: name)->scope.toStrongRef();;
592 return scope;
593 }
594
595 // check if its an (unqualified) property
596 if (QQmlJSScope::ConstPtr scope = findPropertyIn(referrerScope: *referrerScope, propertyName: name, options)) {
597 return scope;
598 }
599 }
600
601 // check if its an id
602 auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver();
603 if (!resolver)
604 return {};
605 QQmlJSScope::ConstPtr fromId = resolver.value()->scopeForId(id: name, referrer: referrerScope.value());
606 if (fromId)
607 return fromId;
608
609 return {};
610 }
611 case DomType::PropertyDefinition: {
612 auto propertyDefinition = item.as<PropertyDefinition>();
613 if (propertyDefinition && propertyDefinition->scope) {
614 switch (options) {
615 case JustOwner:
616 return propertyDefinition->scope.value();
617 case Everything:
618 return propertyDefinition->scope.value()->property(name: propertyDefinition->name).type();
619 }
620 }
621
622 return {};
623 }
624 case DomType::QmlObject: {
625 auto object = item.as<QmlObject>();
626 if (object && object->semanticScope())
627 return object->semanticScope().value();
628
629 return {};
630 }
631 case DomType::ScriptBinaryExpression: {
632 if (isFieldMemberExpression(item)) {
633 DomItem owner = item.field(name: Fields::left);
634 QString propertyName =
635 item.field(name: Fields::right).field(name: Fields::identifier).value().toString();
636 auto ownerType = QQmlLSUtils::resolveExpressionType(
637 item: owner, options: QQmlLSUtilsResolveOptions::Everything);
638 if (!ownerType)
639 return ownerType;
640 switch (options) {
641 case JustOwner:
642 return ownerType;
643 case Everything:
644 return ownerType->property(name: propertyName).type();
645 }
646 }
647 return {};
648 }
649 default: {
650 qCDebug(QQmlLSUtilsLog) << "Type" << item.internalKindStr()
651 << "is unimplemented in QQmlLSUtils::resolveExpressionType";
652 return {};
653 }
654 }
655 Q_UNREACHABLE();
656}
657
658DomItem QQmlLSUtils::sourceLocationToDomItem(DomItem file, const QQmlJS::SourceLocation &location)
659{
660 // QQmlJS::SourceLocation starts counting at 1 but the utils and the LSP start at 0.
661 auto items = QQmlLSUtils::itemsFromTextLocation(file, line: location.startLine - 1,
662 character: location.startColumn - 1);
663 switch (items.size()) {
664 case 0:
665 return {};
666 case 1:
667 return items.front().domItem;
668 case 2: {
669 // special case: because location points to the beginning of the type definition,
670 // itemsFromTextLocation might also return the type on its left, in case it is directly
671 // adjacent to it. In this case always take the right (=with the higher column-number)
672 // item.
673 auto &first = items.front();
674 auto &second = items.back();
675 Q_ASSERT_X(first.fileLocation->info().fullRegion.startLine
676 == second.fileLocation->info().fullRegion.startLine,
677 "QQmlLSUtils::findTypeDefinitionOf(DomItem)",
678 "QQmlLSUtils::itemsFromTextLocation returned non-adjacent items.");
679 if (first.fileLocation->info().fullRegion.startColumn
680 > second.fileLocation->info().fullRegion.startColumn)
681 return first.domItem;
682 else
683 return second.domItem;
684 break;
685 }
686 default:
687 qDebug() << "Found multiple candidates for type of scriptidentifierexpression";
688 break;
689 }
690 return {};
691}
692
693std::optional<QQmlLSUtilsLocation>
694findPropertyDefinitionOf(DomItem file, QQmlJS::SourceLocation propertyDefinitionLocation,
695 const QString &name)
696{
697 DomItem propertyOwner = QQmlLSUtils::sourceLocationToDomItem(file, location: propertyDefinitionLocation);
698 DomItem propertyDefinition = propertyOwner.field(name: Fields::propertyDefs).key(name).index(0);
699 if (!propertyDefinition)
700 return {};
701
702 QQmlLSUtilsLocation result;
703 result.location = FileLocations::treeOf(propertyDefinition)->info().fullRegion;
704 result.filename = propertyDefinition.canonicalFilePath();
705 return result;
706}
707
708std::optional<QQmlLSUtilsLocation> QQmlLSUtils::findDefinitionOf(DomItem item)
709{
710
711 switch (item.internalKind()) {
712 case QQmlJS::Dom::DomType::ScriptIdentifierExpression: {
713 // first check if its a JS Identifier
714
715 const QString name = item.value().toString();
716 if (isFieldMemberAccess(item)) {
717 if (auto ownerScope = QQmlLSUtils::resolveExpressionType(
718 item, options: QQmlLSUtilsResolveOptions::JustOwner)) {
719 const DomItem ownerFile = item.goToFile(filePath: ownerScope->filePath());
720 const QQmlJS::SourceLocation ownerLocation = ownerScope->sourceLocation();
721 if (auto propertyDefinition =
722 findPropertyDefinitionOf(file: ownerFile, propertyDefinitionLocation: ownerLocation, name)) {
723 return propertyDefinition;
724 }
725 }
726 return {};
727 }
728
729 // check: is it a JS identifier?
730 if (DomItem definitionOfItem = findJSIdentifierDefinition(item, name)) {
731 Q_ASSERT_X(definitionOfItem.semanticScope().has_value()
732 && definitionOfItem.semanticScope()
733 .value()
734 ->JSIdentifier(name)
735 .has_value(),
736 "QQmlLSUtils::findDefinitionOf",
737 "JS definition does not actually define the JS identifer. "
738 "It should be empty.");
739 QQmlJS::SourceLocation location =
740 definitionOfItem.semanticScope().value()->JSIdentifier(id: name).value().location;
741
742 QQmlLSUtilsLocation result = { .filename: definitionOfItem.canonicalFilePath(), .location: location };
743 return result;
744 }
745
746 // not a JS identifier, check for ids and properties
747 auto referrerScope = item.nearestSemanticScope();
748 if (!referrerScope)
749 return {};
750
751 if (QQmlJSScope::ConstPtr scope =
752 findPropertyIn(referrerScope: *referrerScope, propertyName: name, options: QQmlLSUtilsResolveOptions::JustOwner)) {
753 const QString canonicalPath = scope->filePath();
754 qDebug() << canonicalPath;
755 DomItem file = item.goToFile(filePath: canonicalPath);
756 return findPropertyDefinitionOf(file, propertyDefinitionLocation: scope->sourceLocation(), name);
757 }
758
759 // check if its an id
760 auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver();
761 if (!resolver)
762 return {};
763 QQmlJSScope::ConstPtr fromId = resolver.value()->scopeForId(id: name, referrer: referrerScope.value());
764 if (fromId) {
765 QQmlLSUtilsLocation result;
766 result.location = fromId->sourceLocation();
767 result.filename = item.canonicalFilePath();
768 return result;
769 }
770 return {};
771 }
772 default:
773 qDebug() << "QQmlLSUtils::findDefinitionOf: Found unimplemented Type "
774 << item.internalKindStr();
775 return {};
776 }
777
778 Q_UNREACHABLE_RETURN(std::nullopt);
779}
780
781QT_END_NAMESPACE
782

source code of qtdeclarative/src/qmlls/qqmllsutils.cpp