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
qqmllsutils.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qassert.h>
8#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
9#include <QtCore/qthreadpool.h>
10#include <QtCore/private/qduplicatetracker_p.h>
11#include <QtCore/QRegularExpression>
12#include <QtQmlDom/private/qqmldomexternalitems_p.h>
13#include <QtQmlDom/private/qqmldomtop_p.h>
14#include <QtQmlDom/private/qqmldomscriptelements_p.h>
15#include <QtQmlDom/private/qqmldom_utils_p.h>
16#include <QtQml/private/qqmlsignalnames_p.h>
17#include <QtQml/private/qqmljslexer_p.h>
18#include <QtQmlCompiler/private/qqmljsutils_p.h>
19
20#include <algorithm>
21#include <memory>
22#include <optional>
23#include <queue>
24#include <utility>
25
27
28Q_LOGGING_CATEGORY(QQmlLSUtilsLog, "qt.languageserver.utils")
29
30using namespace QQmlJS::Dom;
31using namespace Qt::StringLiterals;
32
33namespace QQmlLSUtils {
34QString qualifiersFrom(const DomItem &el)
35{
36 const bool isAccess = QQmlLSUtils::isFieldMemberAccess(el);
37 if (!isAccess && !QQmlLSUtils::isFieldMemberExpression(el))
38 return {};
39
40 const DomItem fieldMemberExpressionBeginning = el.filterUp(
41 [](DomType, const DomItem &item) { return !QQmlLSUtils::isFieldMemberAccess(item); },
42 FilterUpOptions::ReturnOuter);
43 QStringList qualifiers =
44 QQmlLSUtils::fieldMemberExpressionBits(fieldMemberExpressionBeginning, el);
45
46 QString result;
47 for (const QString &qualifier : qualifiers)
48 result.append(qualifier).append(QChar(u'.'));
49 return result;
50}
51
52/*!
53 \internal
54 Helper to check if item is a Field Member Expression \c {<someExpression>.propertyName}.
55*/
56bool isFieldMemberExpression(const DomItem &item)
57{
58 return item.internalKind() == DomType::ScriptBinaryExpression
59 && item.field(Fields::operation).value().toInteger()
60 == ScriptElements::BinaryExpression::FieldMemberAccess;
61}
62
63/*!
64 \internal
65 Helper to check if item is a Field Member Access \c memberAccess in
66 \c {<someExpression>.memberAccess}.
67*/
68bool isFieldMemberAccess(const DomItem &item)
69{
70 auto parent = item.directParent();
71 if (!isFieldMemberExpression(parent))
72 return false;
73
74 DomItem rightHandSide = parent.field(Fields::right);
75 return item == rightHandSide;
76}
77
78/*!
79 \internal
80 Helper to check if item is a Field Member Base \c base in
81 \c {base.memberAccess}.
82*/
83bool isFieldMemberBase(const DomItem &item)
84{
85 auto parent = item.directParent();
86 if (!isFieldMemberExpression(parent))
87 return false;
88
89 // First case, checking `a` for being a base in `a.b`: a is the left hand side of the binary
90 // expression B(a,b).
91 const DomItem leftHandSide = parent.field(Fields::left);
92 if (item == leftHandSide)
93 return true;
94
95 // Second case, checking `d` for being a base in `a.b.c.d.e.f.g`: the binary expressions are
96 // nested as following: B(B(B(B(B(B(a,b),c),d),e),f),g) so for `d`, check whether its
97 // grandparent B(B(B(B(a,b),c),d),e), which has `e` on its right hand side, is a binary
98 // expression.
99 const DomItem grandParent = parent.directParent();
100 return isFieldMemberExpression(grandParent) && grandParent.field(Fields::left) == parent;
101}
102
103/*!
104 \internal
105 Get the bits of a field member expression, like \c{a}, \c{b} and \c{c} for \c{a.b.c}.
106
107 stopAtChild can either be an FieldMemberExpression, a ScriptIdentifierExpression or a default
108 constructed DomItem: This exits early before processing Field::right of an
109 FieldMemberExpression stopAtChild, or before processing a ScriptIdentifierExpression stopAtChild.
110 No early exits if stopAtChild is default constructed.
111*/
112QStringList fieldMemberExpressionBits(const DomItem &item, const DomItem &stopAtChild)
113{
114 const bool isAccess = isFieldMemberAccess(item);
115 const bool isExpression = isFieldMemberExpression(item);
116
117 // assume it is a non-qualified name
118 if (!isAccess && !isExpression)
119 return { item.value().toString() };
120
121 const DomItem stopMarker =
122 isFieldMemberExpression(stopAtChild) ? stopAtChild : stopAtChild.directParent();
123
124 QStringList result;
125 DomItem current =
126 isAccess ? item.directParent() : (isFieldMemberExpression(item) ? item : DomItem{});
127
128 for (; isFieldMemberExpression(current); current = current.field(Fields::right)) {
129 result << current.field(Fields::left).value().toString();
130
131 if (current == stopMarker)
132 return result;
133 }
134 result << current.value().toString();
135
136 return result;
137}
138
139/*!
140 \internal
141 The language server protocol calls "URI" what QML calls "URL".
142 According to RFC 3986, a URL is a special case of URI that not only
143 identifies a resource but also shows how to access it.
144 In QML, however, URIs are distinct from URLs. URIs are the
145 identifiers of modules, for example "QtQuick.Controls".
146 In order to not confuse the terms we interpret language server URIs
147 as URLs in the QML code model.
148 This method marks a point of translation between the terms, but does
149 not have to change the actual URI/URL.
150
151 \sa QQmlLSUtils::qmlUriToLspUrl
152 */
153QByteArray lspUriToQmlUrl(const QByteArray &uri)
154{
155 return uri;
156}
157
158QByteArray qmlUrlToLspUri(const QByteArray &url)
159{
160 return url;
161}
162
163/*!
164 \internal
165 \brief Converts a QQmlJS::SourceLocation to a LSP Range.
166
167 QQmlJS::SourceLocation starts counting lines and rows at 1, but the LSP Range starts at 0.
168 */
170{
171 QLspSpecification::Range range;
172
173 range.start.line = qmlLocation.sourceLocation().startLine - 1;
174 range.start.character = qmlLocation.sourceLocation().startColumn - 1;
175 range.end.line = qmlLocation.end().line;
176 range.end.character = qmlLocation.end().character;
177
178 return range;
179}
180
181/*!
182 \internal
183 \brief Convert a text position from (line, column) into an offset.
184
185 Row, Column and the offset are all 0-based.
186 For example, \c{s[textOffsetFrom(s, 5, 55)]} returns the character of s at line 5 and column 55.
187
188 \sa QQmlLSUtils::textRowAndColumnFrom
189*/
190qsizetype textOffsetFrom(const QString &text, int row, int column)
191{
192 return QQmlJS::SourceLocation::offsetFrom(text, row + 1, column + 1);
193}
194
195/*!
196 \internal
197 \brief Convert a text position from an offset into (line, column).
198
199 Row, Column and the offset are all 0-based.
200 For example, \c{textRowAndColumnFrom(s, 55)} returns the line and columns of the
201 character at \c {s[55]}.
202
203 \sa QQmlLSUtils::textOffsetFrom
204*/
205TextPosition textRowAndColumnFrom(const QString &text, qsizetype offset)
206{
207 auto [row, column] = QQmlJS::SourceLocation::rowAndColumnFrom(text, offset);
208
209 // special case: return last character when accessing after end of file
210 if (offset >= text.size())
211 --column;
212
213 return TextPosition{ int(row - 1), int(column - 1) };
214}
215
217handlePropertyDefinitionAndBindingOverlap(const QList<ItemLocation> &items, qsizetype offsetInFile)
218{
219 auto smallest = std::min_element(
220 items.begin(), items.end(), [](const ItemLocation &a, const ItemLocation &b) {
221 return a.fileLocation->info().fullRegion.length
222 < b.fileLocation->info().fullRegion.length;
223 });
224
225 if (smallest->domItem.internalKind() == DomType::Binding) {
226 // weird edge case: the filelocations of property definitions and property bindings are
227 // actually overlapping, which means that qmlls cannot distinguish between bindings and
228 // bindings in property definitions. Those need to be treated differently for
229 // autocompletion, for example.
230 // Therefore: when inside a binding and a propertydefinition, choose the property definition
231 // if offsetInFile is before the colon, like for example:
232 // property var helloProperty: Rectangle { /*...*/ }
233 // |----return propertydef---|-- return Binding ---|
234
235 // get the smallest property definition to avoid getting the property definition that the
236 // current QmlObject is getting bound to!
237 auto smallestPropertyDefinition = std::min_element(
238 items.begin(), items.end(), [](const ItemLocation &a, const ItemLocation &b) {
239 // make property definition smaller to avoid getting smaller items that are not
240 // property definitions
241 const bool aIsPropertyDefinition =
242 a.domItem.internalKind() == DomType::PropertyDefinition;
243 const bool bIsPropertyDefinition =
244 b.domItem.internalKind() == DomType::PropertyDefinition;
245 return aIsPropertyDefinition > bIsPropertyDefinition
246 && a.fileLocation->info().fullRegion.length
247 < b.fileLocation->info().fullRegion.length;
248 });
249
250 if (smallestPropertyDefinition->domItem.internalKind() != DomType::PropertyDefinition)
251 return smallest;
252
253 const auto propertyDefinitionColon =
254 smallestPropertyDefinition->fileLocation->info().regions[ColonTokenRegion];
255 const auto smallestColon = smallest->fileLocation->info().regions[ColonTokenRegion];
256 // sanity check: is it the definition of the current binding? check if they both have their
257 // ':' at the same location
258 if (propertyDefinitionColon.isValid() && propertyDefinitionColon == smallestColon
259 && offsetInFile < smallestColon.begin()) {
260 return smallestPropertyDefinition;
261 }
262 }
263 return smallest;
264}
265
266static QList<ItemLocation> filterItemsFromTextLocation(const QList<ItemLocation> &items,
267 qsizetype offsetInFile)
268{
269 if (items.size() < 2)
270 return items;
271
272 // if there are multiple items, take the smallest one + its neighbors
273 // this allows to prefer inline components over main components, when both contain the
274 // current textposition, and to disregard internal structures like property maps, which
275 // "contain" everything from their first-appearing to last-appearing property (e.g. also
276 // other stuff in between those two properties).
277
278 QList<ItemLocation> filteredItems;
279
280 auto smallest = handlePropertyDefinitionAndBindingOverlap(items, offsetInFile);
281
282 filteredItems.append(*smallest);
283
284 const QQmlJS::SourceLocation smallestLoc = smallest->fileLocation->info().fullRegion;
285 const qsizetype smallestBegin = smallestLoc.begin();
286 const qsizetype smallestEnd = smallestLoc.end();
287
288 for (auto it = items.begin(); it != items.end(); it++) {
289 if (it == smallest)
290 continue;
291
292 const QQmlJS::SourceLocation itLoc = it->fileLocation->info().fullRegion;
293 const qsizetype itBegin = itLoc.begin();
294 const qsizetype itEnd = itLoc.end();
295 if (itBegin == smallestEnd || smallestBegin == itEnd) {
296 filteredItems.append(*it);
297 }
298 }
299 return filteredItems;
300}
301
302/*!
303\internal
304\brief Find the DomItem representing the object situated in file at given line and
305character/column.
306
307If line and character point between two objects, two objects might be returned.
308If line and character point to whitespace, it might return an inner node of the QmlDom-Tree.
309
310We usually assume that sourcelocations have inclusive ends, for example
311we assume that auto-completion on `\n` in `someName\n` wants suggestions
312for `someName`, even if its technically one position "outside" the
313sourcelocation of `someName`. This is not true for
314ScriptBinaryExpressions, where auto-completion on `.` in `someName.` should
315not return suggestions for `someName`.
316The same also applies to all other binary expressions `+`, `-`, and so on.
317 */
318QList<ItemLocation> itemsFromTextLocation(const DomItem &file, int line, int character)
319{
320 QList<ItemLocation> itemsFound;
321 std::shared_ptr<QmlFile> filePtr = file.ownerAs<QmlFile>();
322 if (!filePtr)
323 return itemsFound;
324 FileLocations::Tree t = filePtr->fileLocationsTree();
325 Q_ASSERT(t);
326 QString code = filePtr->code(); // do something more advanced wrt to changes wrt to this->code?
327 QList<ItemLocation> toDo;
328 qsizetype targetPos = textOffsetFrom(code, line, character);
329 Q_ASSERT(targetPos >= 0);
330
331 enum ComparisonOption { Normal, ExcludePositionAfterLast };
332 auto containsTarget = [targetPos](QQmlJS::SourceLocation l, ComparisonOption c) {
333 return l.begin() <= targetPos && targetPos < l.end() + (c == Normal ? 1 : 0);
334 };
335 if (containsTarget(t->info().fullRegion, Normal)) {
336 ItemLocation loc;
337 loc.domItem = file;
338 loc.fileLocation = t;
339 toDo.append(loc);
340 }
341 while (!toDo.isEmpty()) {
342 ItemLocation iLoc = toDo.last();
343 toDo.removeLast();
344
345 bool inParentButOutsideChildren = true;
346
347 // Exclude the position behind the source location in ScriptBinaryExpressions to avoid
348 // returning `owner` in `owner.member` when completion is triggered on the \c{.}. This
349 // tells the code for the completion if the completion was triggered on `owner` or on `.`.
350 // Same is true for templateliterals, where ScriptTemplateExpressionParts and
351 // ScriptTemplateStringParts stop overlapping when using ExcludePositionAfterLast.
352 const ComparisonOption comparisonOption =
353 iLoc.domItem.internalKind() == DomType::ScriptBinaryExpression
354 || iLoc.domItem.directParent().internalKind()
355 == DomType::ScriptTemplateLiteral
356 ? ExcludePositionAfterLast
357 : Normal;
358
359 auto subEls = iLoc.fileLocation->subItems();
360 for (auto it = subEls.begin(); it != subEls.end(); ++it) {
361 auto subLoc = it.value();
362 Q_ASSERT(subLoc);
363
364 if (containsTarget(subLoc->info().fullRegion, comparisonOption)) {
365 ItemLocation subItem;
366 subItem.domItem = iLoc.domItem.path(it.key());
367 if (!subItem.domItem) {
368 qCDebug(QQmlLSUtilsLog)
369 << "A DomItem child is missing or the FileLocationsTree structure does "
370 "not follow the DomItem Structure.";
371 continue;
372 }
373 // the parser inserts empty Script Expressions for bindings that are not completely
374 // written out yet. Ignore them here.
375 if (subItem.domItem.internalKind() == DomType::ScriptExpression
376 && subLoc->info().fullRegion.length == 0) {
377 continue;
378 }
379 subItem.fileLocation = subLoc;
380 toDo.append(subItem);
381 inParentButOutsideChildren = false;
382 }
383 }
384 if (inParentButOutsideChildren) {
385 itemsFound.append(iLoc);
386 }
387 }
388
389 // filtering step:
390 auto filtered = filterItemsFromTextLocation(itemsFound, targetPos);
391 return filtered;
392}
393
394/*!
395 \internal
396 \brief Returns the location of the type definition pointed by object.
397
398 For a \c PropertyDefinition, return the location of the type of the property.
399 For a \c Binding, return the bound item's type location if an QmlObject is bound, and otherwise
400 the type of the property.
401 For a \c QmlObject, return the location of the QmlObject's base.
402 For an \c Id, return the location of the object to which the id resolves.
403 For a \c Methodparameter, return the location of the type of the parameter.
404 Otherwise, return std::nullopt.
405 */
406std::optional<Location> findTypeDefinitionOf(const DomItem &object)
407{
408 auto expressionType = resolveExpressionType(object, ResolveOptions::ResolveOwnerType);
409
410 if (!expressionType)
411 return { };
412
413 QQmlJSScope::ConstPtr scope = expressionType->semanticScope;
414 if (!scope)
415 return { };
416 switch (expressionType->type) {
417 case PropertyIdentifier: {
418 if (!expressionType->name)
419 return { };
420 QQmlJSScope::ConstPtr propertyType = scope->property(*expressionType->name).type();
421 if (!propertyType)
422 return { };
423 return Location::tryFrom(propertyType->filePath(), propertyType->sourceLocation(), object);
424 }
426 if (!expressionType->name)
427 return { };
428 auto jsIdentifier = scope->jsIdentifier(*expressionType->name);
429 if (!jsIdentifier || !jsIdentifier->typeName)
430 return { };
431 auto qmlFile = object.fileObject().as<QmlFile>();
432 if (!qmlFile)
433 return { };
434 auto definition = qmlFile->typeResolver()->typeForName(*jsIdentifier->typeName);
435 if (!definition)
436 return { };
437 return Location::tryFrom(definition->filePath(), definition->sourceLocation(), object);
438 }
445 case NotAnIdentifier:
446 case SignalIdentifier:
448 case MethodIdentifier:
455 return Location::tryFrom(scope->filePath(), scope->sourceLocation(), object);
456 }
457 Q_UNREACHABLE_RETURN({ });
458}
459
460static bool findDefinitionFromItem(const DomItem &item, const QString &name)
461{
462 if (const QQmlJSScope::ConstPtr &scope = item.semanticScope()) {
463 qCDebug(QQmlLSUtilsLog) << "Searching for definition in" << item.internalKindStr();
464 if (auto jsIdentifier = scope->ownJSIdentifier(name)) {
465 qCDebug(QQmlLSUtilsLog) << "Found scope" << scope->baseTypeName();
466 return true;
467 }
468 }
469 return false;
470}
471
472static DomItem findJSIdentifierDefinition(const DomItem &item, const QString &name)
473{
474 DomItem definitionOfItem;
475 item.visitUp([&name, &definitionOfItem](const DomItem &i) {
476 if (findDefinitionFromItem(i, name)) {
477 definitionOfItem = i;
478 return false;
479 }
480 // early exit: no JS definitions/usages outside the ScriptExpression DOM element.
481 if (i.internalKind() == DomType::ScriptExpression)
482 return false;
483 return true;
484 });
485
486 if (definitionOfItem)
487 return definitionOfItem;
488
489 // special case: somebody asks for usages of a function parameter from its definition
490 // function parameters are defined in the method's scope
491 if (DomItem res = item.filterUp([](DomType k, const DomItem &) { return k == DomType::MethodInfo; },
492 FilterUpOptions::ReturnOuter)) {
493 DomItem candidate = res.field(Fields::body).field(Fields::scriptElement);
494 if (findDefinitionFromItem(candidate, name)) {
495 return candidate;
496 }
497 }
498
499 // lambda function parameters are defined in the FunctionExpression scope
500 if (DomItem res = item.filterUp(
501 [](DomType k, const DomItem &) { return k == DomType::ScriptFunctionExpression; },
502 FilterUpOptions::ReturnOuter)) {
503 if (findDefinitionFromItem(res, name)) {
504 return res;
505 }
506 }
507
508 return definitionOfItem;
509}
510
511/*!
512\internal
513Represents a signal, signal handler, property, property changed signal or a property changed
514handler.
515 */
517{
518 /*!
519 \internal The name of the signal or property, independent of whether this is a changed signal
520 or handler.
521 */
524};
525
526/*!
527\internal
528\brief Find out if \c{name} is a signal, signal handler, property, property changed signal, or a
529property changed handler in the given QQmlJSScope.
530
531Heuristic to find if name is a property, property changed signal, .... because those can appear
532under different names, for example \c{mySignal} and \c{onMySignal} for a signal.
533This will give incorrect results as soon as properties/signals/methods are called \c{onMySignal},
534\c{on<some already existing property>Changed}, ..., but the good news is that the engine also
535will act weird in these cases (e.g. one cannot bind to a property called like an already existing
536signal or a property changed handler).
537For future reference: you can always add additional checks to check the existence of those buggy
538properties/signals/methods by looking if they exist in the QQmlJSScope.
539*/
540static std::optional<SignalOrProperty> resolveNameInQmlScope(const QString &name,
541 const QQmlJSScope::ConstPtr &owner)
542{
543 if (owner->hasProperty(name)) {
544 return SignalOrProperty{ name, PropertyIdentifier };
545 }
546
547 if (const auto propertyName = QQmlSignalNames::changedHandlerNameToPropertyName(name)) {
548 if (owner->hasProperty(*propertyName)) {
549 const QString signalName = *QQmlSignalNames::changedHandlerNameToSignalName(name);
550 const QQmlJSMetaMethod signal = owner->methods(signalName).front();
551 // PropertyChangedHandlers don't have parameters: treat all other as regular signal
552 // handlers. Even if they appear in the notify of the property.
553 if (signal.parameterNames().size() == 0)
554 return SignalOrProperty{ *propertyName, PropertyChangedHandlerIdentifier };
555 else
556 return SignalOrProperty{ signalName, SignalHandlerIdentifier };
557 }
558 }
559
560 if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(name)) {
561 if (auto methods = owner->methods(*signalName); !methods.isEmpty()) {
562 if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) {
563 return SignalOrProperty{ *signalName, SignalHandlerIdentifier };
564 }
565 }
566 }
567
568 if (const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(name)) {
569 if (owner->hasProperty(*propertyName)) {
570 return SignalOrProperty{ *propertyName, PropertyChangedSignalIdentifier };
571 }
572 }
573
574 if (auto methods = owner->methods(name); !methods.isEmpty()) {
575 if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) {
576 return SignalOrProperty{ name, SignalIdentifier };
577 }
578 return SignalOrProperty{ name, MethodIdentifier };
579 }
580 return std::nullopt;
581}
582
583/*!
584\internal
585Returns a list of names, that when belonging to the same targetType, should be considered equal.
586This is used to find signal handlers as usages of their corresponding signals, for example.
587*/
588static QStringList namesOfPossibleUsages(const QString &name,
589 const DomItem &item,
590 const QQmlJSScope::ConstPtr &targetType)
591{
592 QStringList namesToCheck = { name };
593 if (item.internalKind() == DomType::EnumItem || item.internalKind() == DomType::EnumDecl)
594 return namesToCheck;
595
596 auto namings = resolveNameInQmlScope(name, targetType);
597 if (!namings)
598 return namesToCheck;
599 switch (namings->type) {
600 case PropertyIdentifier: {
601 // for a property, also find bindings to its onPropertyChanged handler + propertyChanged
602 // signal
603 const QString propertyChangedHandler =
604 QQmlSignalNames::propertyNameToChangedHandlerName(namings->name);
605 namesToCheck.append(propertyChangedHandler);
606
607 const QString propertyChangedSignal =
608 QQmlSignalNames::propertyNameToChangedSignalName(namings->name);
609 namesToCheck.append(propertyChangedSignal);
610 break;
611 }
613 // for a property changed handler, also find the usages of its property + propertyChanged
614 // signal
615 namesToCheck.append(namings->name);
616 namesToCheck.append(QQmlSignalNames::propertyNameToChangedSignalName(namings->name));
617 break;
618 }
620 // for a property changed signal, also find the usages of its property + onPropertyChanged
621 // handlers
622 namesToCheck.append(namings->name);
623 namesToCheck.append(QQmlSignalNames::propertyNameToChangedHandlerName(namings->name));
624 break;
625 }
626 case SignalIdentifier: {
627 // for a signal, also find bindings to its onSignalHandler.
628 namesToCheck.append(QQmlSignalNames::signalNameToHandlerName(namings->name));
629 break;
630 }
632 // for a signal handler, also find the usages of the signal it handles
633 namesToCheck.append(namings->name);
634 break;
635 }
636 default: {
637 break;
638 }
639 }
640 return namesToCheck;
641}
642
643template<typename Predicate>
645 const QQmlJSScope::ConstPtr &referrerScope, Predicate &&check)
646{
647 QQmlJSScope::ConstPtr result;
648 QQmlJSUtils::searchBaseAndExtensionTypes(
649 referrerScope, [&](const QQmlJSScope::ConstPtr &scope) {
650 if (check(scope)) {
651 result = scope;
652 return true;
653 }
654 return false;
655 });
656
657 return result;
658}
659
660/*!
661\internal
662\brief Finds the scope where a property is first defined.
663
664Starts looking for the name starting from the given scope and traverse through base and
665extension types.
666*/
667QQmlJSScope::ConstPtr findDefiningScopeForProperty(const QQmlJSScope::ConstPtr &referrerScope,
668 const QString &nameToCheck)
669{
670 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
671 return scope->hasOwnProperty(nameToCheck);
672 });
673}
674
675/*!
676\internal
677See also findDefiningScopeForProperty().
678
679Special case: you can also bind to a signal handler.
680*/
681QQmlJSScope::ConstPtr findDefiningScopeForBinding(const QQmlJSScope::ConstPtr &referrerScope,
682 const QString &nameToCheck)
683{
684 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
685 return scope->hasOwnProperty(nameToCheck) || scope->hasOwnMethod(nameToCheck);
686 });
687}
688
689/*!
690\internal
691See also findDefiningScopeForProperty().
692*/
693QQmlJSScope::ConstPtr findDefiningScopeForMethod(const QQmlJSScope::ConstPtr &referrerScope,
694 const QString &nameToCheck)
695{
696 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
697 return scope->hasOwnMethod(nameToCheck);
698 });
699}
700
701/*!
702\internal
703See also findDefiningScopeForProperty().
704*/
705QQmlJSScope::ConstPtr findDefiningScopeForEnumeration(const QQmlJSScope::ConstPtr &referrerScope,
706 const QString &nameToCheck)
707{
708 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
709 return scope->hasOwnEnumeration(nameToCheck);
710 });
711}
712
713/*!
714\internal
715See also findDefiningScopeForProperty().
716*/
717QQmlJSScope::ConstPtr findDefiningScopeForEnumerationKey(const QQmlJSScope::ConstPtr &referrerScope,
718 const QString &nameToCheck)
719{
720 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
721 return scope->hasOwnEnumerationKey(nameToCheck);
722 });
723}
724
725/*!
726 Filter away the parts of the Dom not needed for find usages, by following the profiler's
727 information.
728 1. "propertyInfos" tries to require all inherited properties of some QmlObject. That is super
729 slow (profiler says it eats 90% of the time needed by `tst_qmlls_utils findUsages`!) and is not
730 needed for usages.
731 2. "get" tries to resolve references, like base types saved in prototypes for example, and is not
732 needed to find usages. Profiler says it eats 70% of the time needed by `tst_qmlls_utils
733 findUsages`.
734 3. "defaultPropertyName" also recurses through base types and is not needed to find usages.
735*/
737{
738 FieldFilter filter{ {},
739 {
740 { QString(), Fields::propertyInfos.toString() },
741 { QString(), Fields::defaultPropertyName.toString() },
742 { QString(), Fields::get.toString() },
743 } };
744 return filter;
745};
746
747static void findUsagesOfNonJSIdentifiers(const DomItem &item, const QString &name, Usages &result)
748{
749 const auto expressionType = resolveExpressionType(item, ResolveOwnerType);
750 if (!expressionType)
751 return;
752
753 // for Qml file components: add their filename as an usage for the renaming operation
754 if (expressionType->type == QmlComponentIdentifier
755 && !expressionType->semanticScope->isInlineComponent()) {
756 result.appendFilenameUsage(expressionType->semanticScope->filePath());
757 }
758
759 const QStringList namesToCheck = namesOfPossibleUsages(name, item, expressionType->semanticScope);
760
761 const auto addLocationIfTypeMatchesTarget =
762 [&result, &expressionType, &item](const DomItem &toBeResolved, FileLocationRegion subRegion) {
763 const auto currentType =
764 resolveExpressionType(toBeResolved, ResolveOptions::ResolveOwnerType);
765 if (!currentType)
766 return;
767
768 const QQmlJSScope::ConstPtr target = expressionType->semanticScope;
769 const QQmlJSScope::ConstPtr current = currentType->semanticScope;
770 if (target == current) {
771 auto tree = FileLocations::treeOf(toBeResolved);
772 QQmlJS::SourceLocation sourceLocation;
773
774 sourceLocation = FileLocations::region(tree, subRegion);
775 if (!sourceLocation.isValid())
776 return;
777
778 if (auto location = Location::tryFrom(toBeResolved.canonicalFilePath(),
779 sourceLocation, item)) {
780 result.appendUsage(*location);
781 }
782 }
783 };
784
785 auto findUsages = [&addLocationIfTypeMatchesTarget, &name,
786 &namesToCheck](Path, const DomItem &current, bool) -> bool {
787 bool continueForChildren = true;
788 if (auto scope = current.semanticScope()) {
789 // is the current property shadowed by some JS identifier? ignore current + its children
790 if (scope->ownJSIdentifier(name)) {
791 return false;
792 }
793 }
794 switch (current.internalKind()) {
795 case DomType::QmlObject:
796 case DomType::Binding:
797 case DomType::MethodInfo:
798 case DomType::PropertyDefinition: {
799 const QString propertyName = current.field(Fields::name).value().toString();
800 if (namesToCheck.contains(propertyName))
801 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
802 return continueForChildren;
803 }
804 case DomType::ScriptIdentifierExpression: {
805 const QString identifierName = current.field(Fields::identifier).value().toString();
806 if (namesToCheck.contains(identifierName))
807 addLocationIfTypeMatchesTarget(current, MainRegion);
808 return continueForChildren;
809 }
810 case DomType::ScriptLiteral: {
811 const QString literal = current.field(Fields::value).value().toString();
812 if (namesToCheck.contains(literal))
813 addLocationIfTypeMatchesTarget(current, MainRegion);
814 return continueForChildren;
815 }
816 case DomType::EnumItem: {
817 // Only look for the first enum defined. The inner enums
818 // have no way to be accessed.
819 const auto parentPath = current.containingObject().pathFromOwner();
820 const auto index = parentPath.last().headIndex();
821 if (index != 0)
822 return continueForChildren;
823 const QString enumValue = current.field(Fields::name).value().toString();
824 if (namesToCheck.contains(enumValue))
825 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
826 return continueForChildren;
827 }
828 case DomType::EnumDecl: {
829 // Only look for the first enum defined. The inner enums
830 // have no way to be accessed.
831 const auto parentPath = current.pathFromOwner();
832 const auto index = parentPath.last().headIndex();
833 if (index != 0)
834 return continueForChildren;
835 const QString enumValue = current.field(Fields::name).value().toString();
836 if (namesToCheck.contains(enumValue))
837 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
838 return continueForChildren;
839 }
840 default:
841 return continueForChildren;
842 };
843
844 Q_UNREACHABLE_RETURN(continueForChildren);
845 };
846
847 const DomItem qmlFiles = item.top().field(Fields::qmlFileWithPath);
848 const auto filter = filterForFindUsages();
849 for (const QString &file : qmlFiles.keys()) {
850 const DomItem currentFileComponents =
851 qmlFiles.key(file).field(Fields::currentItem).field(Fields::components);
852 currentFileComponents.visitTree(Path(), emptyChildrenVisitor,
853 VisitOption::Recurse | VisitOption::VisitSelf, findUsages,
854 emptyChildrenVisitor, filter);
855 }
856}
857
858static std::optional<Location> locationFromJSIdentifierDefinition(const DomItem &definitionOfItem,
859 const QString &name)
860{
861 Q_ASSERT_X(!definitionOfItem.semanticScope().isNull()
862 && definitionOfItem.semanticScope()->ownJSIdentifier(name).has_value(),
863 "QQmlLSUtils::locationFromJSIdentifierDefinition",
864 "JS definition does not actually define the JS identifier. "
865 "Did you obtain definitionOfItem from findJSIdentifierDefinition() ?");
866 const QQmlJS::SourceLocation location =
867 definitionOfItem.semanticScope()->ownJSIdentifier(name).value().location;
868
869 return Location::tryFrom(definitionOfItem.canonicalFilePath(), location, definitionOfItem);
870}
871
872static void findUsagesHelper(const DomItem &item, const QString &name, Usages &result)
873{
874 qCDebug(QQmlLSUtilsLog) << "Looking for JS identifier with name" << name;
875 DomItem definitionOfItem = findJSIdentifierDefinition(item, name);
876
877 // if there is no definition found: check if name was a property or an id instead
878 if (!definitionOfItem) {
879 qCDebug(QQmlLSUtilsLog) << "No defining JS-Scope found!";
880 findUsagesOfNonJSIdentifiers(item, name, result);
881 return;
882 }
883
884 definitionOfItem.visitTree(
885 Path(), emptyChildrenVisitor, VisitOption::VisitAdopted | VisitOption::Recurse,
886 [&name, &result](Path, const DomItem &item, bool) -> bool {
887 qCDebug(QQmlLSUtilsLog) << "Visiting a " << item.internalKindStr();
888 if (item.internalKind() == DomType::ScriptIdentifierExpression
889 && item.field(Fields::identifier).value().toString() == name) {
890 // add this usage
891 auto fileLocation = FileLocations::treeOf(item);
892 if (!fileLocation) {
893 qCWarning(QQmlLSUtilsLog) << "Failed finding filelocation of found usage";
894 return true;
895 }
896 const QQmlJS::SourceLocation sourceLocation = fileLocation->info().fullRegion;
897 const QString fileName = item.canonicalFilePath();
898 if (auto location = Location::tryFrom(fileName, sourceLocation, item))
899 result.appendUsage(*location);
900 return true;
901 }
902 QQmlJSScope::ConstPtr scope = item.semanticScope();
903 // Do not visit children if current JS identifier has been redefined.
904 if (scope && scope != item.directParent().semanticScope()
905 && scope->ownJSIdentifier(name)) {
906 // Note that if the current semantic scope == parent's semantic scope, then no
907 // redefinition actually took place. This happens for example in
908 // FunctionExpressions, where the body's semantic scope is the
909 // FunctionExpression's semantic scope.
910 return false;
911 }
912 return true;
913 },
914 emptyChildrenVisitor, filterForFindUsages());
915
916 if (const auto definition = locationFromJSIdentifierDefinition(definitionOfItem, name))
917 result.appendUsage(*definition);
918}
919
920Usages findUsagesOf(const DomItem &item)
921{
922 Usages result;
923
924 switch (item.internalKind()) {
925 case DomType::ScriptIdentifierExpression: {
926 const QString name = item.field(Fields::identifier).value().toString();
927 findUsagesHelper(item, name, result);
928 break;
929 }
930 case DomType::ScriptVariableDeclarationEntry: {
931 const QString name = item.field(Fields::identifier).value().toString();
932 findUsagesHelper(item, name, result);
933 break;
934 }
935 case DomType::EnumDecl:
936 case DomType::EnumItem:
937 case DomType::QmlObject:
938 case DomType::PropertyDefinition:
939 case DomType::Binding:
940 case DomType::MethodInfo: {
941 const QString name = item.field(Fields::name).value().toString();
942 findUsagesHelper(item, name, result);
943 break;
944 }
945 case DomType::QmlComponent: {
946 QString name = item.field(Fields::name).value().toString();
947
948 // get rid of extra qualifiers
949 if (const auto dotIndex = name.indexOf(u'.'); dotIndex != -1)
950 name = name.sliced(dotIndex + 1);
951 findUsagesHelper(item, name, result);
952 break;
953 }
954 default:
955 qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
956 << "was not implemented for QQmlLSUtils::findUsagesOf";
957 return result;
958 }
959
960 result.sort();
961
962 if (QQmlLSUtilsLog().isDebugEnabled()) {
963 qCDebug(QQmlLSUtilsLog) << "Found following usages in files:";
964 for (auto r : result.usagesInFile()) {
965 qCDebug(QQmlLSUtilsLog) << r.filename() << " @ " << r.sourceLocation().startLine << ":"
966 << r.sourceLocation().startColumn << " with length "
967 << r.sourceLocation().length;
968 }
969 qCDebug(QQmlLSUtilsLog) << "And following usages in file names:"
970 << result.usagesInFilename();
971 }
972
973 return result;
974}
975
976static std::optional<IdentifierType> hasMethodOrSignal(const QQmlJSScope::ConstPtr &scope,
977 const QString &name)
978{
979 auto methods = scope->methods(name);
980 if (methods.isEmpty())
981 return {};
982
983 const bool isSignal = methods.front().methodType() == QQmlJSMetaMethodType::Signal;
984 IdentifierType type =
986 return type;
987}
988
989/*!
990\internal
991Searches for a method by traversing the parent scopes.
992
993We assume here that it is possible to call methods from parent scope to simplify things, as the
994linting module already warns about calling methods from parent scopes.
995
996Note: in QML, one can only call methods from the current scope, and from the QML file root scope.
997Everything else needs a qualifier.
998*/
999static std::optional<ExpressionType>
1000methodFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name,
1001 ResolveOptions options)
1002{
1003 for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) {
1004 if (auto type = hasMethodOrSignal(current, name)) {
1005 switch (options) {
1006 case ResolveOwnerType:
1007 return ExpressionType{ name, findDefiningScopeForMethod(current, name), *type };
1009 // QQmlJSScopes were not implemented for methods yet, but JS functions have methods
1010 // and properties see
1011 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
1012 // for the list of properties/methods of functions. Therefore return a null scope.
1013 // see also code below for non-qualified method access
1014 return ExpressionType{ name, {}, *type };
1015 }
1016 }
1017
1018 if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(name)) {
1019 if (auto type = hasMethodOrSignal(current, *signalName)) {
1020 switch (options) {
1021 case ResolveOwnerType:
1022 return ExpressionType{ name, findDefiningScopeForMethod(current, *signalName),
1023 SignalHandlerIdentifier };
1025 // Properties and methods of JS methods are not supported yet
1026 return ExpressionType{ name, {}, SignalHandlerIdentifier };
1027 }
1028 }
1029 }
1030 }
1031 return {};
1032}
1033
1034
1035/*!
1036\internal
1037See comment on methodFromReferrerScope: the same applies to properties.
1038*/
1039static std::optional<ExpressionType>
1040propertyFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &propertyName,
1041 ResolveOptions options)
1042{
1043 for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) {
1044 const auto resolved = resolveNameInQmlScope(propertyName, current);
1045 if (!resolved)
1046 continue;
1047
1048 if (auto property = current->property(resolved->name); property.isValid()) {
1049 switch (options) {
1050 case ResolveOwnerType:
1051 return ExpressionType{ propertyName,
1052 findDefiningScopeForProperty(current, resolved->name),
1053 resolved->type };
1055 return ExpressionType{ propertyName, property.type(), resolved->type };
1056 }
1057 }
1058 }
1059 return {};
1060}
1061
1062/*!
1063\internal
1064See comment on methodFromReferrerScope: the same applies to property bindings.
1065
1066If resolver is not null then it is used to resolve the id with which a generalized grouped
1067properties starts.
1068*/
1069static std::optional<ExpressionType>
1070propertyBindingFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name,
1071 ResolveOptions options, QQmlJSTypeResolver *resolverForIds)
1072{
1073 const auto bindings = referrerScope->propertyBindings(name);
1074 if (bindings.isEmpty())
1075 return {};
1076
1077 const auto binding = bindings.begin();
1078 const auto bindingType = binding->bindingType();
1079 const bool bindingIsAttached = bindingType == QQmlSA::BindingType::AttachedProperty;
1080 if (!bindingIsAttached && bindingType != QQmlSA::BindingType::GroupedProperty)
1081 return {};
1082
1083 // Generalized grouped properties, like Bindings or PropertyChanges, for example, have bindings
1084 // starting in an id (like `someId.someProperty: ...`).
1085 // If `someid` is not a property and is a deferred name, then it should be an id.
1086 if (!bindingIsAttached && !referrerScope->hasProperty(name)
1087 && referrerScope->isNameDeferred(name)) {
1088 if (!resolverForIds)
1089 return {};
1090
1091 return ExpressionType {
1092 name,
1093 resolverForIds->typeForId(referrerScope, name,
1094 QQmlJSScopesByIdOption::AssumeComponentsAreBound),
1096 };
1097 }
1098
1099 const auto typeIdentifier =
1101
1102 const auto getScope = [bindingIsAttached, binding]() -> QQmlJSScope::ConstPtr {
1103 if (bindingIsAttached)
1104 return binding->attachedType();
1105
1106 return binding->groupType();
1107 };
1108
1109 switch (options) {
1110 case ResolveOwnerType: {
1111 return ExpressionType{ name,
1112 // note: always return the type of the attached type as the owner.
1113 // Find usages on "Keys.", for example, should yield all usages of
1114 // the "Keys" attached property.
1115 bindingIsAttached
1116 ? getScope()
1117 : findDefiningScopeForProperty(referrerScope, name),
1118 typeIdentifier };
1119 }
1121 return ExpressionType{ name, getScope(), typeIdentifier };
1122 }
1123 Q_UNREACHABLE_RETURN({});
1124}
1125
1126/*! \internal
1127 Finds the scope within the special elements like Connections,
1128 PropertyChanges, Bindings or AnchorChanges.
1129*/
1131 const QQmlJSScope::ConstPtr &scope, const DomItem &item)
1132{
1133 if (!scope)
1134 return {};
1135
1136 const QSet<QString> specialItems = {u"QQmlConnections"_s,
1137 u"QQuickPropertyChanges"_s,
1138 u"QQmlBind"_s,
1139 u"QQuickAnchorChanges"_s};
1140
1141 const auto special = QQmlJSUtils::searchBaseAndExtensionTypes(
1142 scope, [&specialItems](const QQmlJSScope::ConstPtr &visitedScope) {
1143 const auto typeName = visitedScope->internalName();
1144 if (specialItems.contains(typeName))
1145 return true;
1146 return false;
1147 });
1148
1149 if (!special)
1150 return {};
1151
1152 // Perform target name search if there is binding to property "target"
1153 QString targetName;
1154 if (scope->hasOwnPropertyBindings(u"target"_s)) {
1155 // TODO: propagate the whole binding.
1156 // We can figure out the meaning of target in more cases.
1157
1158 DomItem current = item.qmlObject();
1159 auto target = current.bindings().key(u"target"_s).index(0);
1160 if (target) {
1161 targetName = target.field(Fields::value)
1162 .field(Fields::scriptElement)
1163 .field(Fields::identifier)
1164 .value()
1165 .toString();
1166 }
1167 }
1168
1169 if (!targetName.isEmpty()) {
1170 // target: someId
1171 auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver();
1172 if (!resolver)
1173 return {};
1174
1175 // Note: It does not have to be an ID. It can be a property.
1176 return resolver->scopedType(scope, targetName);
1177 }
1178
1179 if (item.internalKind() == DomType::Binding &&
1180 item.field(Fields::bindingType).value().toInteger() == int(BindingType::OnBinding)) {
1181 // Binding on sth : {} syntax
1182 // Target scope is the current scope
1183 return scope;
1184 }
1185 return scope->parentScope();
1186}
1187
1188/*!
1189\internal
1190\brief Distinguishes singleton types from attached types and "regular" qml components.
1191 */
1192static std::optional<ExpressionType>
1193resolveTypeName(const std::shared_ptr<QQmlJSTypeResolver> &resolver, const QString &name,
1194 const DomItem &item, ResolveOptions options)
1195{
1196 const auto scope = resolver->typeForName(name);
1197 if (!scope)
1198 return {};
1199
1200 if (scope->isSingleton())
1202
1203 // A type not followed by a field member expression is just a type. Otherwise, it could either
1204 // be a type or an attached type!
1205 if (!isFieldMemberBase(item))
1206 return ExpressionType{ name, scope, QmlComponentIdentifier };
1207
1208 // take the right hand side and unwrap in case its a nested fieldmemberexpression
1209 const DomItem rightHandSide = [&item]() {
1210 const DomItem candidate = item.directParent().field(Fields::right);
1211 // case B(a,b) for the right hand side of `a` in `a.b`
1212 if (candidate != item)
1213 return candidate;
1214 // case B(B(a,b),c) for the right hand side of `b` in `a.b.c`
1215 return item.directParent().directParent().field(Fields::right);
1216 }();
1217
1218 if (rightHandSide.internalKind() != DomType::ScriptIdentifierExpression)
1219 return ExpressionType{ name, scope, QmlComponentIdentifier };
1220
1221 const QString fieldMemberAccessName = rightHandSide.value().toString();
1222 if (fieldMemberAccessName.isEmpty() || !fieldMemberAccessName.front().isLower())
1223 return ExpressionType{ name, scope, QmlComponentIdentifier };
1224
1225 if (scope->attachedType()) {
1226 return ExpressionType{ name, options == ResolveOwnerType ? scope : scope->attachedType(),
1228 }
1229 return {};
1230}
1231
1232static std::optional<ExpressionType> resolveFieldMemberExpressionType(const DomItem &item,
1233 ResolveOptions options)
1234{
1235 const QString name = item.field(Fields::identifier).value().toString();
1236 DomItem parent = item.directParent();
1237 auto owner = resolveExpressionType(parent.field(Fields::left),
1238 ResolveOptions::ResolveActualTypeForFieldMemberExpression);
1239 if (!owner)
1240 return {};
1241
1242 if (!owner->semanticScope) {
1243 // JS objects can get new members and methods during runtime and therefore has no
1244 // qqmljsscopes. Therefore, just label everything inside a JavaScriptIdentifier as being
1245 // another JavaScriptIdentifier.
1246 if (owner->type == JavaScriptIdentifier) {
1247 return ExpressionType{ name, {}, JavaScriptIdentifier };
1248 } else if (owner->type == QualifiedModuleIdentifier) {
1249 const QmlFile *qmlFile = item.fileObject().as<QmlFile>();
1250 if (!qmlFile)
1251 return {};
1252 if (auto scope = resolveTypeName(
1253 qmlFile->typeResolver(), u"%1.%2"_s.arg(*owner->name, name), item,
1254 options)) {
1255 // remove the qualified module name from the type name
1256 scope->name = name;
1257 return scope;
1258 }
1259 }
1260 return {};
1261 }
1262
1263 if (auto scope = methodFromReferrerScope(owner->semanticScope, name, options))
1264 return *scope;
1265
1266 if (auto scope = propertyBindingFromReferrerScope(owner->semanticScope, name, options, nullptr))
1267 return *scope;
1268
1269 if (auto scope = propertyFromReferrerScope(owner->semanticScope, name, options))
1270 return *scope;
1271
1272 if (owner->type == QmlComponentIdentifier || owner->type == EnumeratorIdentifier) {
1273 // Check if name is a enum value <TypeName>.<EnumValue> or ...<EnumName>.<EnumValue>
1274 // Enumerations should live under the root element scope of the file that defines the enum,
1275 // therefore use the DomItem to find the root element of the qml file instead of directly
1276 // using owner->semanticScope.
1277 if (const auto scope = owner->semanticScope->isComposite()
1278 ? item.goToFile(owner->semanticScope->filePath())
1279 .rootQmlObject(GoTo::MostLikely)
1280 .semanticScope()
1281 : owner->semanticScope) {
1282 if (scope->hasEnumerationKey(name)) {
1283 return ExpressionType{ name, scope, EnumeratorValueIdentifier };
1284 }
1285 // Or it is a enum name <TypeName>.<EnumName>.<EnumValue>
1286 else if (scope->hasEnumeration(name)) {
1287 return ExpressionType{ name, scope, EnumeratorIdentifier };
1288 }
1289 }
1290
1291 // check inline components <TypeName>.<InlineComponentName>
1292 for (auto it = owner->semanticScope->childScopesBegin(),
1293 end = owner->semanticScope->childScopesEnd();
1294 it != end; ++it) {
1295 if ((*it)->inlineComponentName() == name) {
1296 return ExpressionType{ name, *it, QmlComponentIdentifier };
1297 }
1298 }
1299 return {};
1300 }
1301
1302 qCDebug(QQmlLSUtilsLog) << "Could not find identifier expression for" << item.internalKindStr();
1303 return owner;
1304}
1305
1306/*!
1307\internal
1308Resolves the expression type of a binding for signal handlers, like the function expression
1309\c{(x) => ...} in
1310
1311\qml
1312onHelloSignal: (x) => ...
1313\endqml
1314
1315would be resolved to the \c{onHelloSignal} expression type, for example.
1316*/
1317static std::optional<ExpressionType> resolveBindingIfSignalHandler(const DomItem &functionExpression)
1318{
1319 if (functionExpression.internalKind() != DomType::ScriptFunctionExpression)
1320 return {};
1321
1322 const DomItem parent = functionExpression.directParent();
1323 if (parent.internalKind() != DomType::ScriptExpression)
1324 return {};
1325
1326 const DomItem grandParent = parent.directParent();
1327 if (grandParent.internalKind() != DomType::Binding)
1328 return {};
1329
1330 auto bindingType = resolveExpressionType(grandParent, ResolveOwnerType);
1331 return bindingType;
1332}
1333
1334/*!
1335\internal
1336In a signal handler
1337
1338\qml
1339 onSomeSignal: (x, y, z) => ....
1340\endqml
1341
1342the parameters \c x, \c y and \c z are not allowed to have type annotations: instead, their type is
1343defined by the signal definition itself.
1344
1345This code detects signal handler parameters and resolves their type using the signal's definition.
1346*/
1347static std::optional<ExpressionType>
1348resolveSignalHandlerParameterType(const DomItem &parameterDefinition, const QString &name,
1349 ResolveOptions options)
1350{
1351 const std::optional<QQmlJSScope::JavaScriptIdentifier> jsIdentifier =
1352 parameterDefinition.semanticScope()->jsIdentifier(name);
1353 if (!jsIdentifier || jsIdentifier->kind != QQmlJSScope::JavaScriptIdentifier::Parameter)
1354 return {};
1355
1356 const DomItem handlerFunctionExpression =
1357 parameterDefinition.internalKind() == DomType::ScriptBlockStatement
1358 ? parameterDefinition.directParent()
1359 : parameterDefinition;
1360
1361 const std::optional<ExpressionType> bindingType =
1362 resolveBindingIfSignalHandler(handlerFunctionExpression);
1363 if (!bindingType)
1364 return {};
1365
1366 if (bindingType->type == PropertyChangedHandlerIdentifier)
1367 return ExpressionType{};
1368
1369 if (bindingType->type != SignalHandlerIdentifier)
1370 return {};
1371
1372 const DomItem parameters = handlerFunctionExpression[Fields::parameters];
1373 const int indexOfParameter = [&parameters, &name]() {
1374 for (int i = 0; i < parameters.indexes(); ++i) {
1375 if (parameters[i][Fields::identifier].value().toString() == name)
1376 return i;
1377 }
1378 Q_ASSERT_X(false, "resolveSignalHandlerParameter",
1379 "can't find JS identifier with Parameter kind in the parameters");
1380 Q_UNREACHABLE_RETURN(-1);
1381 }();
1382
1383 const std::optional<QString> signalName =
1384 QQmlSignalNames::handlerNameToSignalName(*bindingType->name);
1385 Q_ASSERT_X(signalName.has_value(), "resolveSignalHandlerParameterType",
1386 "handlerNameToSignalName failed on a SignalHandler");
1387
1388 const QQmlJSMetaMethod signalDefinition =
1389 bindingType->semanticScope->methods(*signalName).front();
1390 const QList<QQmlJSMetaParameter> parameterList = signalDefinition.parameters();
1391
1392 // not a signal handler parameter after all
1393 if (parameterList.size() <= indexOfParameter)
1394 return {};
1395
1396 // now we can return an ExpressionType, even if the indexOfParameter calculation result is only
1397 // needed to check whether this is a signal handler parameter or not.
1398 if (options == ResolveOwnerType)
1399 return ExpressionType{ name, bindingType->semanticScope, JavaScriptIdentifier };
1400 else {
1401 const QQmlJSScope::ConstPtr parameterType = parameterList[indexOfParameter].type();
1402 return ExpressionType{ name, parameterType, JavaScriptIdentifier };
1403 }
1404}
1405
1406/*!
1407\internal
1408resolve an identifier in the same order the QML engine would, that is:
1409* first check for local JS variables (`let x = ...`)
1410* then check for ids
1411* then check for properties
1412* then check for methods
1413* then check for properties and methods of the global object
1414 */
1415static std::optional<ExpressionType> resolveIdentifierExpressionType(const DomItem &item,
1416 ResolveOptions options)
1417{
1418 if (isFieldMemberAccess(item) || isFieldMemberExpression(item)) {
1419 return resolveFieldMemberExpressionType(item, options);
1420 }
1421
1422 const QString name = item.field(Fields::identifier).value().toString();
1423
1424 if (const DomItem definitionOfItem = findJSIdentifierDefinition(item, name)) {
1425 Q_ASSERT_X(!definitionOfItem.semanticScope().isNull()
1426 && definitionOfItem.semanticScope()->ownJSIdentifier(name),
1427 "QQmlLSUtils::findDefinitionOf",
1428 "JS definition does not actually define the JS identifer. "
1429 "It should be empty.");
1430 if (auto parameter = resolveSignalHandlerParameterType(definitionOfItem, name, options))
1431 return parameter;
1432
1433 const auto scope = definitionOfItem.semanticScope();
1434 return ExpressionType{ name,
1435 options == ResolveOwnerType
1436 ? scope
1437 : QQmlJSScope::ConstPtr(
1438 scope->ownJSIdentifier(name)->scope.toStrongRef()),
1439 IdentifierType::JavaScriptIdentifier };
1440 }
1441
1442 const auto referrerScope = item.nearestSemanticScope();
1443 if (!referrerScope)
1444 return {};
1445
1446 const auto qmlFile = item.containingFile().ownerAs<QmlFile>();
1447 if (!qmlFile)
1448 return {};
1449
1450 const auto resolver = qmlFile->typeResolver();
1451 if (!resolver)
1452 return {};
1453
1454 // check if its an id
1455 if (const QQmlJSScope::ConstPtr fromId =
1456 resolver->typeForId(referrerScope, name, QQmlJSScopesByIdOption::AssumeComponentsAreBound)) {
1457 return ExpressionType{ name, fromId, QmlObjectIdIdentifier };
1458 }
1459
1460 // check if its found as a property binding
1461 if (const auto scope = propertyBindingFromReferrerScope(
1462 referrerScope, name, options, resolver.get())) {
1463 return *scope;
1464 }
1465
1466 // check if its an (unqualified) property
1467 if (const auto scope = propertyFromReferrerScope(referrerScope, name, options))
1468 return *scope;
1469
1470 // check if its a method
1471 if (auto scope = methodFromReferrerScope(referrerScope, name, options))
1472 return scope;
1473
1474 if (resolver->seenModuleQualifiers().contains(name))
1475 return ExpressionType{ name, {}, QualifiedModuleIdentifier };
1476
1477 if (const auto scope = resolveTypeName(resolver, name, item, options))
1478 return scope;
1479
1480 // the global object doesn't have properties and methods with the same name, therefore the
1481 // order in which we resolve methods and properties on the global object doesn't matter
1482 const QQmlJSScope::ConstPtr jsGlobal = resolver->jsGlobalObject();
1483 // check if its a JS global method
1484 if (auto scope = methodFromReferrerScope(jsGlobal, name, options)) {
1485 scope->type = JavaScriptIdentifier;
1486 return scope;
1487 }
1488
1489 // check if its an JS global property
1490 if (auto scope = propertyFromReferrerScope(jsGlobal, name, options)) {
1491 scope->type = JavaScriptIdentifier;
1492 return scope;
1493 }
1494
1495 return {};
1496}
1497
1498static std::optional<ExpressionType>
1499resolveSignalOrPropertyExpressionType(const QString &name, const QQmlJSScope::ConstPtr &scope,
1500 ResolveOptions options)
1501{
1502 const auto signalOrProperty = resolveNameInQmlScope(name, scope);
1503 if (!signalOrProperty)
1504 return {};
1505
1506 switch (signalOrProperty->type) {
1507 case PropertyIdentifier:
1508 switch (options) {
1509 case ResolveOwnerType:
1510 return ExpressionType{ name, findDefiningScopeForProperty(scope, name),
1511 signalOrProperty->type };
1513 return ExpressionType{ name, scope->property(name).type(), signalOrProperty->type };
1514 }
1515 Q_UNREACHABLE_RETURN({});
1517 switch (options) {
1518 case ResolveOwnerType:
1519 return ExpressionType{ name,
1520 findDefiningScopeForProperty(scope, signalOrProperty->name),
1521 signalOrProperty->type };
1523 // Properties and methods are not implemented on methods.
1524 Q_UNREACHABLE_RETURN({});
1525 }
1526 Q_UNREACHABLE_RETURN({});
1529 case SignalIdentifier:
1530 case MethodIdentifier:
1531 switch (options) {
1532 case ResolveOwnerType: {
1533 return ExpressionType{ name, findDefiningScopeForMethod(scope, signalOrProperty->name),
1534 signalOrProperty->type };
1535 }
1537 // Properties and methods are not implemented on methods.
1538 Q_UNREACHABLE_RETURN({});
1539 }
1540 Q_UNREACHABLE_RETURN({});
1541 default:
1542 Q_UNREACHABLE_RETURN({});
1543 }
1544}
1545
1546/*!
1547 \internal
1548 \brief
1549 Resolves the type of the given DomItem, when possible (e.g., when there are enough type
1550 annotations).
1551
1552 Might return an ExpressionType without(!) semantic scope when no type information is available, for
1553 example resolving the type of x in someJSObject.x (where `let someJSObject = { x: 42 };`) then
1554 the name and type of x is known but no semantic scope can be obtained.
1555*/
1556std::optional<ExpressionType> resolveExpressionType(const QQmlJS::Dom::DomItem &item,
1557 ResolveOptions options)
1558{
1559 switch (item.internalKind()) {
1560 case DomType::ScriptIdentifierExpression: {
1561 return resolveIdentifierExpressionType(item, options);
1562 }
1563 case DomType::PropertyDefinition: {
1564 auto propertyDefinition = item.as<PropertyDefinition>();
1565 if (propertyDefinition && propertyDefinition->semanticScope()) {
1566 const auto &scope = propertyDefinition->semanticScope();
1567 switch (options) {
1568 case ResolveOwnerType:
1569 return ExpressionType{ propertyDefinition->name, scope, PropertyIdentifier };
1571 // There should not be any PropertyDefinition inside a FieldMemberExpression.
1572 Q_UNREACHABLE_RETURN({});
1573 }
1574 Q_UNREACHABLE_RETURN({});
1575 }
1576 return {};
1577 }
1578 case DomType::Binding: {
1579 auto binding = item.as<Binding>();
1580 if (binding) {
1581 std::optional<QQmlJSScope::ConstPtr> owner = item.qmlObject().semanticScope();
1582 if (!owner)
1583 return {};
1584 const QString name = binding->name();
1585
1586 if (name == u"id")
1587 return ExpressionType{ name, owner.value(), QmlObjectIdIdentifier };
1588
1589 if (const QQmlJSScope::ConstPtr targetScope
1590 = findScopeOfSpecialItems(owner.value(), item)) {
1591 const auto signalOrProperty = resolveNameInQmlScope(name, targetScope);
1592 if (!signalOrProperty)
1593 return {};
1594 switch (options) {
1595 case ResolveOwnerType:
1596 return ExpressionType{
1597 name, findDefiningScopeForBinding(targetScope, signalOrProperty->name),
1598 signalOrProperty->type
1599 };
1601 // Bindings can't be inside of FieldMemberExpressions.
1602 Q_UNREACHABLE_RETURN({});
1603 }
1604 }
1605 if (auto result = resolveSignalOrPropertyExpressionType(name, owner.value(), options)) {
1606 return result;
1607 }
1608 qDebug(QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"
1609 "type of a Binding.";
1610 }
1611
1612 return {};
1613 }
1614 case DomType::QmlObject: {
1615 auto object = item.as<QmlObject>();
1616 if (!object)
1617 return {};
1618 if (auto scope = object->semanticScope()) {
1619 const auto name = item.name();
1620 const bool isComponent = name.front().isUpper();
1621 if (isComponent)
1622 scope = scope->baseType();
1623 const IdentifierType type =
1625 switch (options) {
1626 case ResolveOwnerType:
1627 return ExpressionType{ name, scope, type };
1629 return ExpressionType{ name, scope, type };
1630 }
1631 }
1632 return {};
1633 }
1634 case DomType::QmlComponent: {
1635 auto component = item.as<QmlComponent>();
1636 if (!component)
1637 return {};
1638 const auto scope = component->semanticScope();
1639 if (!scope)
1640 return {};
1641
1642 QString name = item.name();
1643 if (auto dotIndex = name.indexOf(u'.'); dotIndex != -1)
1644 name = name.sliced(dotIndex + 1);
1645 switch (options) {
1646 case ResolveOwnerType:
1647 return ExpressionType{ name, scope, QmlComponentIdentifier };
1649 return ExpressionType{ name, scope, QmlComponentIdentifier };
1650 }
1651 Q_UNREACHABLE_RETURN({});
1652 }
1653 case DomType::ScriptFunctionExpression: {
1654 return ExpressionType{ {}, item.semanticScope(), LambdaMethodIdentifier };
1655 }
1656 case DomType::MethodInfo: {
1657 const auto object = item.as<MethodInfo>();
1658 if (object && object->semanticScope()) {
1659 std::optional<QQmlJSScope::ConstPtr> scope = object->semanticScope();
1660 if (!scope)
1661 return {};
1662
1663 if (const QQmlJSScope::ConstPtr targetScope =
1664 findScopeOfSpecialItems(scope.value()->parentScope(), item)) {
1665 const auto signalOrProperty = resolveNameInQmlScope(object->name, targetScope);
1666 if (!signalOrProperty)
1667 return {};
1668
1669 switch (options) {
1670 case ResolveOwnerType:
1671 return ExpressionType{ object->name,
1672 findDefiningScopeForMethod(targetScope,
1673 signalOrProperty->name),
1674 signalOrProperty->type };
1676 // not supported for methods
1677 return {};
1678 }
1679 }
1680
1681 // in case scope is the semantic scope for the function bodies: grab the owner's scope
1682 // this happens for all methods but not for signals (they do not have a body)
1683 if (QQmlSA::isFunctionScope(scope.value()->scopeType()))
1684 scope = scope.value()->parentScope();
1685
1686 if (const auto result = resolveSignalOrPropertyExpressionType(
1687 object->name, scope.value(), options)) {
1688 return result;
1689 }
1690 qDebug(QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"
1691 "type of a MethodInfo.";
1692 }
1693
1694 return {};
1695 }
1696 case DomType::ScriptBinaryExpression: {
1697 if (isFieldMemberExpression(item)) {
1698 return resolveExpressionType(item.field(Fields::right), options);
1699 }
1700 return {};
1701 }
1702 case DomType::ScriptLiteral: {
1703 /* special case
1704 Binding { target: someId; property: "someProperty"}
1705 */
1706 const auto scope = item.qmlObject().semanticScope();
1707 const auto name = item.field(Fields::value).value().toString();
1708 if (const QQmlJSScope::ConstPtr targetScope = findScopeOfSpecialItems(scope, item)) {
1709 const auto signalOrProperty = resolveNameInQmlScope(name, targetScope);
1710 if (!signalOrProperty)
1711 return {};
1712 switch (options) {
1713 case ResolveOwnerType:
1714 return ExpressionType{
1715 name, findDefiningScopeForProperty(targetScope, signalOrProperty->name),
1716 signalOrProperty->type
1717 };
1719 // ScriptLiteral's can't be inside of FieldMemberExpression's, especially when they
1720 // are inside a special item.
1721 Q_UNREACHABLE_RETURN({});
1722 }
1723 }
1724 return {};
1725 }
1726 case DomType::EnumItem: {
1727 const QString enumValue = item.field(Fields::name).value().toString();
1728 const QQmlJSScope::ConstPtr referrerScope
1729 = item.rootQmlObject(GoTo::MostLikely).semanticScope();
1730 if (!referrerScope->hasEnumerationKey(enumValue))
1731 return {};
1732 switch (options) {
1733 // special case: use the owner's scope here, as enums do not have their own
1734 // QQmlJSScope.
1735 case ResolveActualTypeForFieldMemberExpression:
1736 case ResolveOwnerType:
1737 return ExpressionType {
1738 enumValue,
1739 findDefiningScopeForEnumerationKey(referrerScope, enumValue),
1740 EnumeratorValueIdentifier
1741 };
1742 }
1743 Q_UNREACHABLE_RETURN({});
1744 }
1745 case DomType::EnumDecl: {
1746 const QString enumName = item.field(Fields::name).value().toString();
1747 const QQmlJSScope::ConstPtr referrerScope
1748 = item.rootQmlObject(GoTo::MostLikely).semanticScope();
1749 if (!referrerScope->hasEnumeration(enumName))
1750 return {};
1751 switch (options) {
1752 // special case: use the owner's scope here, as enums do not have their own QQmlJSScope.
1753 case ResolveActualTypeForFieldMemberExpression:
1754 case ResolveOwnerType:
1755 return ExpressionType {
1756 enumName,
1757 findDefiningScopeForEnumeration(referrerScope, enumName),
1758 EnumeratorIdentifier
1759 };
1760 }
1761
1762 Q_UNREACHABLE_RETURN({});
1763 }
1764 case DomType::Import: {
1765 // we currently only support qualified module identifiers
1766 if (!item[Fields::importId])
1767 return {};
1768 return ExpressionType{ item[Fields::importId].value().toString(),
1769 {},
1770 QualifiedModuleIdentifier };
1771 }
1772 case DomType::ScriptNewMemberExpression: {
1773 const auto name = item.field(Fields::base).value().toString();
1774 return ExpressionType{ name, {}, JavaScriptIdentifier };
1775 }
1776 case DomType::ScriptCallExpression: {
1777 const DomItem callee = item.field(Fields::callee);
1778 const auto calleeExpressionType = resolveExpressionType(callee, ResolveOwnerType);
1779
1780 if (!calleeExpressionType || !calleeExpressionType->semanticScope
1781 || !calleeExpressionType->name || calleeExpressionType->type != MethodIdentifier) {
1782 return {};
1783 }
1784
1785 const auto methods =
1786 calleeExpressionType->semanticScope->methods(*calleeExpressionType->name);
1787 if (methods.isEmpty())
1788 return {};
1789
1790 const auto returnType = methods.front().returnType();
1791 return ExpressionType{ {}, returnType, NotAnIdentifier };
1792 }
1793 default: {
1794 qCDebug(QQmlLSUtilsLog) << "Type" << item.internalKindStr()
1795 << "is unimplemented in QQmlLSUtils::resolveExpressionType";
1796 return {};
1797 }
1798 }
1799 Q_UNREACHABLE();
1800}
1801
1802DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocation &location)
1803{
1804 // QQmlJS::SourceLocation starts counting at 1 but the utils and the LSP start at 0.
1805 auto items = QQmlLSUtils::itemsFromTextLocation(file, location.startLine - 1,
1806 location.startColumn - 1);
1807 switch (items.size()) {
1808 case 0:
1809 return {};
1810 case 1:
1811 return items.front().domItem;
1812 case 2: {
1813 // special case: because location points to the beginning of the type definition,
1814 // itemsFromTextLocation might also return the type on its left, in case it is directly
1815 // adjacent to it. In this case always take the right (=with the higher column-number)
1816 // item.
1817 auto &first = items.front();
1818 auto &second = items.back();
1819 Q_ASSERT_X(first.fileLocation->info().fullRegion.startLine
1820 == second.fileLocation->info().fullRegion.startLine,
1821 "QQmlLSUtils::findTypeDefinitionOf(DomItem)",
1822 "QQmlLSUtils::itemsFromTextLocation returned non-adjacent items.");
1823 if (first.fileLocation->info().fullRegion.startColumn
1824 > second.fileLocation->info().fullRegion.startColumn)
1825 return first.domItem;
1826 else
1827 return second.domItem;
1828 break;
1829 }
1830 default:
1831 qDebug() << "Found multiple candidates for type of scriptidentifierexpression";
1832 break;
1833 }
1834 return {};
1835}
1836
1837static std::optional<Location>
1838findEnumDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
1839{
1840 const DomItem enumeration = [&file, &location, &name]() -> DomItem {
1841 const DomItem enumerations = QQmlLSUtils::sourceLocationToDomItem(file, location)
1842 .qmlObject()
1843 .component()
1844 .field(Fields::enumerations);
1845 const QSet<QString> enumerationNames = enumerations.keys();
1846 for (const QString &enumName : enumerationNames) {
1847 const DomItem currentKey = enumerations.key(enumName).index(0);
1848 if (enumName == name)
1849 return currentKey;
1850 const DomItem values = currentKey.field(Fields::values);
1851 for (int i = 0, end = values.size(); i < end; ++i) {
1852 const DomItem currentValue = values.index(i);
1853 if (currentValue.field(Fields::name).value().toStringView() == name)
1854 return currentValue;
1855 }
1856 }
1857 return {};
1858 }();
1859
1860 auto fileLocation = FileLocations::treeOf(enumeration);
1861
1862 if (!fileLocation)
1863 return {};
1864
1865 auto regions = fileLocation->info().regions;
1866
1867 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1868 return Location::tryFrom(enumeration.canonicalFilePath(), *it, file);
1869 }
1870
1871 return {};
1872}
1873
1874static std::optional<Location>
1875findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
1876{
1877 DomItem owner = QQmlLSUtils::sourceLocationToDomItem(file, location).qmlObject();
1878 DomItem method = owner.field(Fields::methods).key(name).index(0);
1879 auto fileLocation = FileLocations::treeOf(method);
1880 if (!fileLocation)
1881 return {};
1882
1883 auto regions = fileLocation->info().regions;
1884
1885 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1886 return Location::tryFrom(method.canonicalFilePath(), *it, file);
1887 }
1888
1889 return {};
1890}
1891
1892static std::optional<Location>
1893findPropertyDefinitionOf(const DomItem &file, QQmlJS::SourceLocation propertyDefinitionLocation,
1894 const QString &name)
1895{
1896 DomItem propertyOwner =
1897 QQmlLSUtils::sourceLocationToDomItem(file, propertyDefinitionLocation).qmlObject();
1898 DomItem propertyDefinition = propertyOwner.field(Fields::propertyDefs).key(name).index(0);
1899 auto fileLocation = FileLocations::treeOf(propertyDefinition);
1900 if (!fileLocation)
1901 return {};
1902
1903 auto regions = fileLocation->info().regions;
1904
1905 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1906 return Location::tryFrom(propertyDefinition.canonicalFilePath(), *it, file);
1907 }
1908
1909 return {};
1910}
1911
1912static QQmlJS::SourceLocation sourceLocationOrDefault(const QQmlJS::SourceLocation &location)
1913{
1914 return location.startLine == 0 ? QQmlJS::s_documentOrigin : location;
1915}
1916
1917static std::optional<Location> createCppTypeLocation(const QString &fileName,
1918 const QStringList &headerLocations,
1919 const QQmlJS::SourceLocation &location)
1920{
1921 const TextPosition endPosition{ static_cast<int>(location.startLine) + 1, 1 };
1922 const QFileInfo filePathInfo(fileName);
1923 if (filePathInfo.isAbsolute() && filePathInfo.exists())
1924 return Location{ fileName, location, endPosition };
1925
1926 const QString filePath = findFilePathFromFileName(headerLocations, fileName, { });
1927 if (filePath.isEmpty()) {
1928 qCWarning(QQmlLSUtilsLog) << "Couldn't find file '%1'."_L1.arg(fileName);
1929 return {};
1930 }
1931
1932 return Location{ filePath, location, endPosition };
1933}
1934
1935/*!
1936\internal
1937Note that C++ types with foreign can have 2 locations for the definition: one where the
1938QML(_NAMED|)_ELEMENT macro is used and the second is the type targeted by QML_FOREIGN.
1939*/
1940static QList<Location> findDefinitionOfType(const QQmlJSScope::ConstPtr &scope, const DomItem &item,
1941 const QStringList &headerDirectories)
1942{
1943 if (!scope)
1944 return {};
1945 if (scope->isComposite()) {
1946 if (const auto result =
1947 Location::tryFrom(scope->filePath(), scope->sourceLocation(), item)) {
1948 return { *result };
1949 }
1950 }
1951 QList<Location> result;
1952 auto locationOfTypeOrForeign = createCppTypeLocation(
1953 scope->filePath(), headerDirectories, sourceLocationOrDefault(scope->sourceLocation()));
1954 if (locationOfTypeOrForeign)
1955 result << *locationOfTypeOrForeign;
1956
1957 auto resolvedTypeLocation = createCppTypeLocation(
1958 scope->resolvedFilePath(), headerDirectories,
1959 QQmlJS::SourceLocation{ 0u, 0u, scope->lineNumberInResolvedFile(), 1u });
1960 if (resolvedTypeLocation)
1961 result << *resolvedTypeLocation;
1962
1963 return result;
1964}
1965
1966template <typename T>
1967QList<T> optionalToList(std::optional<T> &&optional)
1968{
1969 if (optional)
1970 return { *optional };
1971 return { };
1972}
1973
1974QList<Location> findDefinitionOf(const DomItem &item, const QStringList &headerDirectories)
1975{
1976 auto resolvedExpression = resolveExpressionType(item, ResolveOptions::ResolveOwnerType);
1977
1978 if (!resolvedExpression || !resolvedExpression->name
1979 || (!resolvedExpression->semanticScope
1980 && resolvedExpression->type != QualifiedModuleIdentifier)) {
1981 qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf: Type could not be resolved.";
1982 return {};
1983 }
1984
1985 switch (resolvedExpression->type) {
1986 case JavaScriptIdentifier: {
1987 const auto jsIdentifier =
1988 resolvedExpression->semanticScope->ownJSIdentifier(*resolvedExpression->name);
1989 if (!jsIdentifier)
1990 return {};
1991
1992 return optionalToList(Location::tryFrom(resolvedExpression->semanticScope->filePath(),
1993 jsIdentifier->location, item));
1994 }
1995
1997 case PropertyIdentifier: {
1998 if (!resolvedExpression->semanticScope->isComposite()) {
1999 return optionalToList(createCppTypeLocation(
2000 resolvedExpression->semanticScope->filePath(), headerDirectories,
2001 resolvedExpression->semanticScope->property(*resolvedExpression->name)
2002 .sourceLocation()));
2003 }
2004 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2005 const QQmlJS::SourceLocation ownerLocation =
2006 resolvedExpression->semanticScope->sourceLocation();
2007 return optionalToList(
2008 findPropertyDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name));
2009 }
2012 case SignalIdentifier:
2014 case MethodIdentifier: {
2015 if (!resolvedExpression->semanticScope->isComposite()) {
2016 const auto methods =
2017 resolvedExpression->semanticScope->methods(*resolvedExpression->name);
2018 if (methods.isEmpty())
2019 return {};
2020 return optionalToList(
2021 createCppTypeLocation(resolvedExpression->semanticScope->filePath(),
2022 headerDirectories, methods.front().sourceLocation()));
2023 }
2024 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2025 const QQmlJS::SourceLocation ownerLocation =
2026 resolvedExpression->semanticScope->sourceLocation();
2027 return optionalToList(
2028 findMethodDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name));
2029 }
2030 case QmlObjectIdIdentifier: {
2031 DomItem qmlObject = QQmlLSUtils::sourceLocationToDomItem(
2032 item.containingFile(), resolvedExpression->semanticScope->sourceLocation());
2033 // in the Dom, the id is saved in a QMultiHash inside the Component of an QmlObject.
2034 const DomItem domId = qmlObject.component()
2035 .field(Fields::ids)
2036 .key(*resolvedExpression->name)
2037 .index(0)
2038 .field(Fields::value);
2039 if (!domId) {
2040 qCDebug(QQmlLSUtilsLog)
2041 << "QmlComponent in Dom structure has no id, was it misconstructed?";
2042 return {};
2043 }
2044
2045 return optionalToList(Location::tryFrom(
2046 domId.canonicalFilePath(), FileLocations::treeOf(domId)->info().fullRegion, domId));
2047 }
2048 case AttachedTypeIdentifier:
2049 return findDefinitionOfType(resolvedExpression->semanticScope->attachedType(), item,
2050 headerDirectories);
2051 case AttachedTypeIdentifierInBindingTarget:
2052 return findDefinitionOfType(resolvedExpression->semanticScope->baseType(), item,
2053 headerDirectories);
2054 case QmlComponentIdentifier:
2055 case SingletonIdentifier:
2056 return findDefinitionOfType(resolvedExpression->semanticScope, item, headerDirectories);
2058 const DomItem imports = item.fileObject().field(Fields::imports);
2059 for (int i = 0; i < imports.indexes(); ++i) {
2060 if (imports[i][Fields::importId].value().toString() == resolvedExpression->name) {
2061 const auto fileLocations = FileLocations::treeOf(imports[i]);
2062 if (!fileLocations)
2063 continue;
2064
2065 return optionalToList(Location::tryFrom(item.canonicalFilePath(),
2066 fileLocations->info().regions[IdNameRegion],
2067 item));
2068 }
2069 }
2070 return {};
2071 }
2074 if (!resolvedExpression->semanticScope->isComposite()) {
2075 const auto enumerations = resolvedExpression->semanticScope->enumerations();
2076 for (const auto &enumeration : enumerations) {
2077 if (enumeration.hasKey(*resolvedExpression->name)
2078 || enumeration.name() == *resolvedExpression->name) {
2079 return optionalToList(createCppTypeLocation(
2080 resolvedExpression->semanticScope->filePath(), headerDirectories,
2081 sourceLocationOrDefault(QQmlJS::SourceLocation::fromQSizeType(
2082 0, 0, enumeration.lineNumber(), 1))));
2083 }
2084 }
2085 return {};
2086 }
2087 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2088 const QQmlJS::SourceLocation ownerLocation =
2089 resolvedExpression->semanticScope->sourceLocation();
2090 return optionalToList(
2091 findEnumDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name));
2092 }
2093
2095 case NotAnIdentifier:
2096 qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf was not implemented for type"
2097 << resolvedExpression->type;
2098 return {};
2099 }
2100 Q_UNREACHABLE_RETURN({});
2101}
2102
2103static QQmlJSScope::ConstPtr propertyOwnerFrom(const QQmlJSScope::ConstPtr &type,
2104 const QString &name)
2105{
2106 Q_ASSERT(!name.isEmpty());
2107 Q_ASSERT(type);
2108
2109 QQmlJSScope::ConstPtr typeWithDefinition = type;
2110 while (typeWithDefinition && !typeWithDefinition->hasOwnProperty(name))
2111 typeWithDefinition = typeWithDefinition->baseType();
2112
2113 if (!typeWithDefinition) {
2114 qCDebug(QQmlLSUtilsLog)
2115 << "QQmlLSUtils::checkNameForRename cannot find property definition,"
2116 " ignoring.";
2117 }
2118
2119 return typeWithDefinition;
2120}
2121
2122static QQmlJSScope::ConstPtr methodOwnerFrom(const QQmlJSScope::ConstPtr &type,
2123 const QString &name)
2124{
2125 Q_ASSERT(!name.isEmpty());
2126 Q_ASSERT(type);
2127
2128 QQmlJSScope::ConstPtr typeWithDefinition = type;
2129 while (typeWithDefinition && !typeWithDefinition->hasOwnMethod(name))
2130 typeWithDefinition = typeWithDefinition->baseType();
2131
2132 if (!typeWithDefinition) {
2133 qCDebug(QQmlLSUtilsLog)
2134 << "QQmlLSUtils::checkNameForRename cannot find method definition,"
2135 " ignoring.";
2136 }
2137
2138 return typeWithDefinition;
2139}
2140
2142{
2143 switch (ownerType.type) {
2144 case PropertyIdentifier:
2145 return propertyOwnerFrom(ownerType.semanticScope, *ownerType.name);
2147 const auto propertyName =
2148 QQmlSignalNames::changedHandlerNameToPropertyName(*ownerType.name);
2149 return propertyOwnerFrom(ownerType.semanticScope, *propertyName);
2150 break;
2151 }
2153 const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(*ownerType.name);
2154 return propertyOwnerFrom(ownerType.semanticScope, *propertyName);
2155 }
2156 case MethodIdentifier:
2157 case SignalIdentifier:
2158 return methodOwnerFrom(ownerType.semanticScope, *ownerType.name);
2160 const auto signalName = QQmlSignalNames::handlerNameToSignalName(*ownerType.name);
2161 return methodOwnerFrom(ownerType.semanticScope, *signalName);
2162 }
2174 case NotAnIdentifier:
2175 return ownerType.semanticScope;
2176 }
2177 return {};
2178}
2179
2180std::optional<ErrorMessage> checkNameForRename(const DomItem &item, const QString &dirtyNewName,
2181 const std::optional<ExpressionType> &ownerType)
2182{
2183 if (!ownerType) {
2184 if (const auto resolved = resolveExpressionType(item, ResolveOwnerType))
2185 return checkNameForRename(item, dirtyNewName, resolved);
2186 }
2187
2188 // general checks for ECMAscript identifiers
2189 if (!isValidEcmaScriptIdentifier(dirtyNewName))
2190 return ErrorMessage{ 0, u"Invalid EcmaScript identifier!"_s };
2191
2192 const auto userSemanticScope = item.nearestSemanticScope();
2193
2194 if (!ownerType || !userSemanticScope) {
2195 return ErrorMessage{ 0, u"Requested item cannot be renamed."_s };
2196 }
2197
2198 // type specific checks
2199 switch (ownerType->type) {
2201 if (!QQmlSignalNames::isChangedSignalName(dirtyNewName)) {
2202 return ErrorMessage{ 0, u"Invalid name for a property changed signal."_s };
2203 }
2204 break;
2205 }
2207 if (!QQmlSignalNames::isChangedHandlerName(dirtyNewName)) {
2208 return ErrorMessage{ 0, u"Invalid name for a property changed handler identifier."_s };
2209 }
2210 break;
2211 }
2213 if (!QQmlSignalNames::isHandlerName(dirtyNewName)) {
2214 return ErrorMessage{ 0, u"Invalid name for a signal handler identifier."_s };
2215 }
2216 break;
2217 }
2218 // TODO: any other specificities?
2220 if (dirtyNewName.front().isLetter() && !dirtyNewName.front().isLower()) {
2221 return ErrorMessage{ 0, u"Object id names cannot start with an upper case letter."_s };
2222 }
2223 break;
2225 case PropertyIdentifier:
2226 case SignalIdentifier:
2227 case MethodIdentifier:
2228 default:
2229 break;
2230 };
2231
2232 auto typeWithDefinition = expressionTypeWithDefinition(*ownerType);
2233
2234 if (!typeWithDefinition) {
2235 return ErrorMessage{
2236 0,
2237 u"Renaming has not been implemented for the requested item."_s,
2238 };
2239 }
2240
2241 // is it not defined in QML?
2242 if (!typeWithDefinition->isComposite()) {
2243 return ErrorMessage{ 0, u"Cannot rename items defined in non-QML files."_s };
2244 }
2245
2246 // is it defined in the current module?
2247 const QString moduleOfDefinition = ownerType->semanticScope->moduleName();
2248 const QString moduleOfCurrentItem = userSemanticScope->moduleName();
2249 if (moduleOfDefinition != moduleOfCurrentItem) {
2250 return ErrorMessage{
2251 0,
2252 u"Cannot rename items defined in the \"%1\" module from a usage in the \"%2\" module."_s
2253 .arg(moduleOfDefinition, moduleOfCurrentItem),
2254 };
2255 }
2256
2257 return {};
2258}
2259
2260static std::optional<QString> oldNameFrom(const DomItem &item)
2261{
2262 switch (item.internalKind()) {
2263 case DomType::ScriptIdentifierExpression: {
2264 return item.field(Fields::identifier).value().toString();
2265 }
2266 case DomType::ScriptVariableDeclarationEntry: {
2267 return item.field(Fields::identifier).value().toString();
2268 }
2269 case DomType::PropertyDefinition:
2270 case DomType::Binding:
2271 case DomType::MethodInfo: {
2272 return item.field(Fields::name).value().toString();
2273 }
2274 default:
2275 qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
2276 << "was not implemented for QQmlLSUtils::renameUsagesOf";
2277 return std::nullopt;
2278 }
2279 Q_UNREACHABLE_RETURN(std::nullopt);
2280}
2281
2282static std::optional<QString> newNameFrom(const QString &dirtyNewName, IdentifierType alternative)
2283{
2284 // When renaming signal/property changed handlers and property changed signals:
2285 // Get the actual corresponding signal name (for signal handlers) or property name (for
2286 // property changed signal + handlers) that will be used for the renaming.
2287 switch (alternative) {
2289 return QQmlSignalNames::handlerNameToSignalName(dirtyNewName);
2290 }
2292 return QQmlSignalNames::changedHandlerNameToPropertyName(dirtyNewName);
2293 }
2295 return QQmlSignalNames::changedSignalNameToPropertyName(dirtyNewName);
2296 }
2297 case SignalIdentifier:
2298 case PropertyIdentifier:
2299 default:
2300 return std::nullopt;
2301 }
2302 Q_UNREACHABLE_RETURN(std::nullopt);
2303}
2304
2305/*!
2306\internal
2307\brief Rename the appearance of item to newName.
2308
2309Special cases:
2310\list
2311 \li Renaming a property changed signal or property changed handler does the same as renaming
2312 the underlying property, except that newName gets
2313 \list
2314 \li its "on"-prefix and "Changed"-suffix chopped of if item is a property changed handlers
2315 \li its "Changed"-suffix chopped of if item is a property changed signals
2316 \endlist
2317 \li Renaming a signal handler does the same as renaming a signal, but the "on"-prefix in newName
2318 is chopped of.
2319
2320 All of the chopping operations are done using the static helpers from QQmlSignalNames.
2321\endlist
2322*/
2323RenameUsages renameUsagesOf(const DomItem &item, const QString &dirtyNewName,
2324 const std::optional<ExpressionType> &targetType)
2325{
2326 RenameUsages result;
2327 const Usages locations = findUsagesOf(item);
2328 if (locations.isEmpty())
2329 return result;
2330
2331 auto oldName = oldNameFrom(item);
2332 if (!oldName)
2333 return result;
2334
2335 QQmlJSScope::ConstPtr semanticScope;
2336 if (targetType) {
2337 semanticScope = targetType->semanticScope;
2338 } else if (const auto resolved =
2339 QQmlLSUtils::resolveExpressionType(item, ResolveOptions::ResolveOwnerType)) {
2340 semanticScope = resolved->semanticScope;
2341 } else {
2342 return result;
2343 }
2344
2345 QString newName;
2346 if (const auto resolved = resolveNameInQmlScope(*oldName, semanticScope)) {
2347 newName = newNameFrom(dirtyNewName, resolved->type).value_or(dirtyNewName);
2348 oldName = resolved->name;
2349 } else {
2350 newName = dirtyNewName;
2351 }
2352
2353 const qsizetype oldNameLength = oldName->length();
2354 const qsizetype oldHandlerNameLength =
2355 QQmlSignalNames::signalNameToHandlerName(*oldName).length();
2356 const qsizetype oldChangedSignalNameLength =
2357 QQmlSignalNames::propertyNameToChangedSignalName(*oldName).length();
2358 const qsizetype oldChangedHandlerNameLength =
2359 QQmlSignalNames::propertyNameToChangedHandlerName(*oldName).length();
2360
2361 const QString newHandlerName = QQmlSignalNames::signalNameToHandlerName(newName);
2362 const QString newChangedSignalName = QQmlSignalNames::propertyNameToChangedSignalName(newName);
2363 const QString newChangedHandlerName =
2364 QQmlSignalNames::propertyNameToChangedHandlerName(newName);
2365
2366 // set the new name at the found usages, but add "on"-prefix and "Changed"-suffix if needed
2367 for (const auto &location : locations.usagesInFile()) {
2368 const qsizetype currentLength = location.sourceLocation().length;
2369 Edit edit;
2370 edit.location = location;
2371 if (oldNameLength == currentLength) {
2372 // normal case, nothing to do
2373 edit.replacement = newName;
2374
2375 } else if (oldHandlerNameLength == currentLength) {
2376 // signal handler location
2377 edit.replacement = newHandlerName;
2378
2379 } else if (oldChangedSignalNameLength == currentLength) {
2380 // property changed signal location
2381 edit.replacement = newChangedSignalName;
2382
2383 } else if (oldChangedHandlerNameLength == currentLength) {
2384 // property changed handler location
2385 edit.replacement = newChangedHandlerName;
2386
2387 } else {
2388 qCDebug(QQmlLSUtilsLog) << "Found usage with wrong identifier length, ignoring...";
2389 continue;
2390 }
2391 result.appendRename(edit);
2392 }
2393
2394 for (const auto &filename : locations.usagesInFilename()) {
2395 // assumption: we only rename files ending in .qml or .ui.qml in qmlls
2396 QString extension;
2397 if (filename.endsWith(u".ui.qml"_s))
2398 extension = u".ui.qml"_s;
2399 else if (filename.endsWith(u".qml"_s))
2400 extension = u".qml"_s;
2401 else
2402 continue;
2403
2404 QFileInfo info(filename);
2405 // do not rename the file if it has a custom type name in the qmldir
2406 if (!info.isFile() || info.baseName() != oldName)
2407 continue;
2408
2409 const QString newFilename =
2410 QDir::cleanPath(filename + "/.."_L1) + '/'_L1 + newName + extension;
2411 result.appendRename({ filename, newFilename });
2412 }
2413
2414 return result;
2415}
2416
2417std::optional<Location> Location::tryFrom(const QString &fileName,
2418 const QQmlJS::SourceLocation &sourceLocation,
2419 const QQmlJS::Dom::DomItem &someItem)
2420{
2421 auto qmlFile = someItem.goToFile(fileName).ownerAs<QQmlJS::Dom::QmlFile>();
2422 if (!qmlFile) {
2423 qDebug() << "Could not find file" << fileName << "in the dom!";
2424 return {};
2425 }
2426 return Location{ fileName, sourceLocation,
2427 textRowAndColumnFrom(qmlFile->code(), sourceLocation.end()) };
2428}
2429
2430Location Location::from(const QString &fileName, const QQmlJS::SourceLocation &sourceLocation, const QString &code)
2431{
2432 return Location{ fileName, sourceLocation, textRowAndColumnFrom(code, sourceLocation.end()) };
2433}
2434
2435Location Location::from(const QString &fileName, const QString &code, qsizetype startLine,
2436 qsizetype startCharacter, qsizetype length)
2437{
2438 const auto offset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
2439 return from(fileName,
2440 QQmlJS::SourceLocation::fromQSizeType(offset, length, startLine, startCharacter),
2441 code);
2442}
2443
2444Edit Edit::from(const QString &fileName, const QString &code, qsizetype startLine,
2445 qsizetype startCharacter, qsizetype length, const QString &newName)
2446{
2447 Edit rename;
2448 rename.location = Location::from(fileName, code, startLine, startCharacter, length);
2449 rename.replacement = newName;
2450 return rename;
2451}
2452
2453bool isValidEcmaScriptIdentifier(QStringView identifier)
2454{
2455 QQmlJS::Lexer lexer(nullptr);
2456 lexer.setCode(identifier.toString(), 0);
2457 const int token = lexer.lex();
2458 if (token != static_cast<int>(QQmlJS::Lexer::T_IDENTIFIER))
2459 return false;
2460 // make sure there is nothing following the lexed identifier
2461 const int eofToken = lexer.lex();
2462 return eofToken == static_cast<int>(QQmlJS::Lexer::EOF_SYMBOL);
2463}
2464
2465
2466/*!
2467\internal
2468Returns the name of the cmake program along with the arguments needed to build the
2469qmltyperegistration. This command generates the .qmltypes, qmldir and .qrc files required for qmlls
2470to provide correct information on C++ defined QML elements.
2471
2472We assume here that CMake is available in the path. This should be the case for linux and macOS by
2473default.
2474For windows, having CMake in the path is not too unrealistic, for example,
2475https://doc.qt.io/qt-6/windows-building.html#step-2-install-build-requirements claims that you need
2476to have CMake in your path to build Qt. So a developer machine running qmlls has a high chance of
2477having CMake in their path, if CMake is installed and used.
2478*/
2479std::pair<QString, QStringList> cmakeBuildCommand(const QString &path, int cmakeJobs)
2480{
2481 std::pair<QString, QStringList> result{
2482 u"cmake"_s, { u"--build"_s, path, u"-t"_s, u"all_qmltyperegistrations"_s }
2483 };
2484 if (cmakeJobs > 0)
2485 result.second << "-j"_L1 << QString::number(cmakeJobs);
2486 return result;
2487}
2488
2490{
2491 std::sort(m_usagesInFile.begin(), m_usagesInFile.end());
2492 std::sort(m_usagesInFilename.begin(), m_usagesInFilename.end());
2493}
2494
2495bool Usages::isEmpty() const
2496{
2497 return m_usagesInFilename.isEmpty() && m_usagesInFile.isEmpty();
2498}
2499
2500Usages::Usages(const QList<Location> &usageInFile, const QList<QString> &usageInFilename)
2502{
2503 std::sort(m_usagesInFile.begin(), m_usagesInFile.end());
2504 std::sort(m_usagesInFilename.begin(), m_usagesInFilename.end());
2505}
2506
2507RenameUsages::RenameUsages(const QList<Edit> &renamesInFile,
2508 const QList<FileRename> &renamesInFilename)
2510{
2511 std::sort(m_renamesInFile.begin(), m_renamesInFile.end());
2512 std::sort(m_renamesInFilename.begin(), m_renamesInFilename.end());
2513}
2514
2516static QStringList findFilePathsFromFileNamesImpl(const QStringList &rootDirs,
2517 const QStringList &fileNamesToSearch,
2518 SearchOption option,
2519 const QSet<QString> &ignoredFilePaths)
2520{
2521 if (fileNamesToSearch.isEmpty() || rootDirs.isEmpty())
2522 return {};
2523
2524 const qint64 maxFilesToSearch =
2525 qEnvironmentVariableIntegerValue("QMLLS_MAX_FILES_TO_SEARCH")
2526 .value_or(20'000); // 20'000 entries need around one second on my machine
2527 qint64 visitedItems = 0;
2528
2529 QStringList result;
2530 std::queue<QString> toVisit;
2531 for (const QString &rootDir : rootDirs)
2532 toVisit.push(rootDir);
2533
2534 while (!toVisit.empty()) {
2535 const QString current = toVisit.front();
2536 toVisit.pop();
2537
2538 for (const auto &entry : QDirListing{ current, QDirListing::IteratorFlag::ExcludeOther }) {
2539 // timeout to avoid qmlls from recursing too much, in case rootDir is the filesystem
2540 // root for example
2541 if (++visitedItems > maxFilesToSearch) {
2542 qInfo(QQmlLSUtilsLog).noquote().nospace()
2543 << "Aborting search for \"" << fileNamesToSearch.join("\", \""_L1)
2544 << "\" inside \"" << rootDirs.join("\", \""_L1)
2545 << "\" after reaching QMLLS_MAX_FILES_TO_SEARCH (currently set to "
2546 << maxFilesToSearch
2547 << "). Set the environment variable \"QMLLS_MAX_FILES_TO_SEARCH\" to a "
2548 "higher value to spend more time on searching.";
2549 return result;
2550 }
2551
2552 if (entry.isDir()) {
2553 toVisit.push(entry.filePath());
2554 continue;
2555 }
2556 Q_ASSERT(entry.isFile());
2557 if (!fileNamesToSearch.contains(entry.fileName()))
2558 continue;
2559
2560 if (ignoredFilePaths.contains(entry.absoluteFilePath()))
2561 continue;
2562
2563 if (option == FindFirst)
2564 return { entry.absoluteFilePath() };
2565
2566 result << entry.absoluteFilePath();
2567 }
2568 }
2569
2570 return result;
2571}
2572
2573QStringList findFilePathsFromFileNames(const QString &rootDir, const QStringList &fileNamesToSearch,
2574 const QSet<QString> &ignoredFilePaths)
2575{
2576 return findFilePathsFromFileNamesImpl({ rootDir }, fileNamesToSearch, FindAll,
2577 ignoredFilePaths);
2578}
2579
2580QString findFilePathFromFileName(const QStringList &rootDirs, const QString &fileNameToSearch,
2581 const QSet<QString> &ignoredFilePaths)
2582{
2583 const QStringList result = findFilePathsFromFileNamesImpl(rootDirs, { fileNameToSearch },
2584 FindFirst, ignoredFilePaths);
2585 return result.isEmpty() ? QString{} : result.front();
2586}
2587
2588} // namespace QQmlLSUtils
2589
2590QT_END_NAMESPACE
TextPosition end() const
Represents the locations where a renaming should take place.
RenameUsages(const QList< Edit > &renamesInFile, const QList< FileRename > &renamesInFilename)
Represents the locations where some highlighting should take place, like in the "find allreferences" ...
void appendUsage(const Location &edit)
Usages(const QList< Location > &usageInFile, const QList< QString > &usageInFilename)
static std::optional< QString > oldNameFrom(const DomItem &item)
RenameUsages renameUsagesOf(const DomItem &item, const QString &newName, const std::optional< ExpressionType > &targetType=std::nullopt)
Rename the appearance of item to newName.
static std::optional< Location > createCppTypeLocation(const QString &fileName, const QStringList &headerLocations, const QQmlJS::SourceLocation &location)
static std::optional< ExpressionType > resolveFieldMemberExpressionType(const DomItem &item, ResolveOptions options)
static QList< Location > findDefinitionOfType(const QQmlJSScope::ConstPtr &scope, const DomItem &item, const QStringList &headerDirectories)
QQmlJSScope::ConstPtr findDefiningScopeForProperty(const QQmlJSScope::ConstPtr &referrerScope, const QString &nameToCheck)
Finds the scope where a property is first defined.
bool isFieldMemberBase(const DomItem &item)
QStringList findFilePathsFromFileNames(const QString &rootDir, const QStringList &fileNamesToSearch, const QSet< QString > &ignoredPaths)
@ EnumeratorValueIdentifier
@ GroupedPropertyIdentifier
@ AttachedTypeIdentifierInBindingTarget
@ QualifiedModuleIdentifier
@ PropertyChangedHandlerIdentifier
@ PropertyChangedSignalIdentifier
@ SignalHandlerIdentifier
bool isValidEcmaScriptIdentifier(QStringView view)
QLspSpecification::Range qmlLocationToLspLocation(Location qmlLocation)
Converts a QQmlJS::SourceLocation to a LSP Range.
static FieldFilter filterForFindUsages()
Filter away the parts of the Dom not needed for find usages, by following the profiler's information.
static QStringList findFilePathsFromFileNamesImpl(const QStringList &rootDirs, const QStringList &fileNamesToSearch, SearchOption option, const QSet< QString > &ignoredFilePaths)
static bool findDefinitionFromItem(const DomItem &item, const QString &name)
static std::optional< Location > findPropertyDefinitionOf(const DomItem &file, QQmlJS::SourceLocation propertyDefinitionLocation, const QString &name)
QQmlJSScope::ConstPtr findDefiningScopeForMethod(const QQmlJSScope::ConstPtr &referrerScope, const QString &nameToCheck)
static std::optional< SignalOrProperty > resolveNameInQmlScope(const QString &name, const QQmlJSScope::ConstPtr &owner)
Find out if {name} is a signal, signal handler, property, property changed signal,...
static std::optional< QString > newNameFrom(const QString &dirtyNewName, IdentifierType alternative)
QQmlJSScope::ConstPtr findDefiningScopeForBinding(const QQmlJSScope::ConstPtr &referrerScope, const QString &nameToCheck)
static QQmlJSScope::ConstPtr findScopeOfSpecialItems(const QQmlJSScope::ConstPtr &scope, const DomItem &item)
qsizetype textOffsetFrom(const QString &code, int row, int character)
Convert a text position from (line, column) into an offset.
static std::optional< ExpressionType > resolveSignalHandlerParameterType(const DomItem &parameterDefinition, const QString &name, ResolveOptions options)
static std::optional< ExpressionType > propertyBindingFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name, ResolveOptions options, QQmlJSTypeResolver *resolverForIds)
QByteArray lspUriToQmlUrl(const QByteArray &uri)
DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocation &location)
static std::optional< Location > findEnumDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
static std::optional< ExpressionType > resolveBindingIfSignalHandler(const DomItem &functionExpression)
QStringList fieldMemberExpressionBits(const DomItem &item, const DomItem &stopAtChild={})
QByteArray qmlUrlToLspUri(const QByteArray &url)
static QList< ItemLocation >::const_iterator handlePropertyDefinitionAndBindingOverlap(const QList< ItemLocation > &items, qsizetype offsetInFile)
@ ResolveActualTypeForFieldMemberExpression
static std::optional< IdentifierType > hasMethodOrSignal(const QQmlJSScope::ConstPtr &scope, const QString &name)
TextPosition textRowAndColumnFrom(const QString &code, qsizetype offset)
Convert a text position from an offset into (line, column).
QList< Location > findDefinitionOf(const DomItem &item, const QStringList &headerDirectories)
std::optional< ExpressionType > resolveExpressionType(const DomItem &item, ResolveOptions)
Resolves the type of the given DomItem, when possible (e.g., when there are enough type annotations).
static void findUsagesOfNonJSIdentifiers(const DomItem &item, const QString &name, Usages &result)
bool isFieldMemberExpression(const DomItem &item)
bool isFieldMemberAccess(const DomItem &item)
static std::optional< ExpressionType > resolveSignalOrPropertyExpressionType(const QString &name, const QQmlJSScope::ConstPtr &scope, ResolveOptions options)
static void findUsagesHelper(const DomItem &item, const QString &name, Usages &result)
QQmlJSScope::ConstPtr findDefiningScopeForEnumeration(const QQmlJSScope::ConstPtr &referrerScope, const QString &nameToCheck)
QList< T > optionalToList(std::optional< T > &&optional)
static QQmlJSScope::ConstPtr propertyOwnerFrom(const QQmlJSScope::ConstPtr &type, const QString &name)
static std::optional< Location > findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
static std::optional< ExpressionType > propertyFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &propertyName, ResolveOptions options)
QList< ItemLocation > itemsFromTextLocation(const DomItem &file, int line, int character)
Find the DomItem representing the object situated in file at given line and character/column.
static std::optional< ExpressionType > resolveTypeName(const std::shared_ptr< QQmlJSTypeResolver > &resolver, const QString &name, const DomItem &item, ResolveOptions options)
Distinguishes singleton types from attached types and "regular" qml components.
static std::optional< Location > locationFromJSIdentifierDefinition(const DomItem &definitionOfItem, const QString &name)
QString qualifiersFrom(const DomItem &el)
static QList< ItemLocation > filterItemsFromTextLocation(const QList< ItemLocation > &items, qsizetype offsetInFile)
QQmlJSScope::ConstPtr findDefiningScopeForEnumerationKey(const QQmlJSScope::ConstPtr &referrerScope, const QString &nameToCheck)
static QQmlJSScope::ConstPtr expressionTypeWithDefinition(const ExpressionType &ownerType)
Usages findUsagesOf(const DomItem &item)
static std::optional< ExpressionType > resolveIdentifierExpressionType(const DomItem &item, ResolveOptions options)
std::optional< Location > findTypeDefinitionOf(const DomItem &item)
Returns the location of the type definition pointed by object.
QString findFilePathFromFileName(const QStringList &rootDirs, const QString &fileNameToSearch, const QSet< QString > &ignoredPaths)
static QQmlJSScope::ConstPtr methodOwnerFrom(const QQmlJSScope::ConstPtr &type, const QString &name)
static std::optional< ExpressionType > methodFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name, ResolveOptions options)
QQmlJSScope::ConstPtr findDefiningScopeIf(const QQmlJSScope::ConstPtr &referrerScope, Predicate &&check)
static QStringList namesOfPossibleUsages(const QString &name, const DomItem &item, const QQmlJSScope::ConstPtr &targetType)
std::optional< ErrorMessage > checkNameForRename(const DomItem &item, const QString &newName, const std::optional< ExpressionType > &targetType=std::nullopt)
static QQmlJS::SourceLocation sourceLocationOrDefault(const QQmlJS::SourceLocation &location)
std::pair< QString, QStringList > cmakeBuildCommand(const QString &path, int cmakeJobs=0)
static DomItem findJSIdentifierDefinition(const DomItem &item, const QString &name)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")