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
394DomItem baseObject(const DomItem &object)
395{
396 DomItem prototypes;
397 DomItem qmlObject = object.qmlObject();
398 // object is (or is inside) an inline component definition
399 if (object.internalKind() == DomType::QmlComponent || !qmlObject) {
400 prototypes = object.component()
401 .field(Fields::objects)
402 .index(0)
403 .field(QQmlJS::Dom::Fields::prototypes);
404 } else {
405 // object is (or is inside) a QmlObject
406 prototypes = qmlObject.field(QQmlJS::Dom::Fields::prototypes);
407 }
408 switch (prototypes.indexes()) {
409 case 0:
410 return {};
411 case 1:
412 break;
413 default:
414 qDebug() << "Multiple prototypes found for " << object.name() << ", taking the first one.";
415 break;
416 }
417 QQmlJS::Dom::DomItem base = prototypes.index(0).proceedToScope();
418 return base;
419}
420
421static std::optional<Location> locationFromDomItem(const DomItem &item, FileLocationRegion region)
422{
423 auto tree = FileLocations::treeOf(item);
424 // tree is null for C++ defined types, for example
425 if (!tree)
426 return {};
427
428 QQmlJS::SourceLocation sourceLocation = FileLocations::region(tree, region);
429 if (!sourceLocation.isValid() && region != QQmlJS::Dom::MainRegion)
430 sourceLocation = FileLocations::region(tree, QQmlJS::Dom::MainRegion);
431
432 return Location::tryFrom(item.canonicalFilePath(), sourceLocation, item);
433}
434
435/*!
436 \internal
437 \brief Returns the location of the type definition pointed by object.
438
439 For a \c PropertyDefinition, return the location of the type of the property.
440 For a \c Binding, return the bound item's type location if an QmlObject is bound, and otherwise
441 the type of the property.
442 For a \c QmlObject, return the location of the QmlObject's base.
443 For an \c Id, return the location of the object to which the id resolves.
444 For a \c Methodparameter, return the location of the type of the parameter.
445 Otherwise, return std::nullopt.
446 */
447std::optional<Location> findTypeDefinitionOf(const DomItem &object)
448{
449 DomItem typeDefinition;
450
451 switch (object.internalKind()) {
452 case QQmlJS::Dom::DomType::QmlComponent:
453 typeDefinition = object.field(Fields::objects).index(0);
454 break;
455 case QQmlJS::Dom::DomType::QmlObject:
456 typeDefinition = baseObject(object);
457 break;
458 case QQmlJS::Dom::DomType::Binding: {
459 auto binding = object.as<Binding>();
460 Q_ASSERT(binding);
461
462 // try to grab the type from the bound object
463 if (binding->valueKind() == BindingValueKind::Object) {
464 typeDefinition = baseObject(object.field(Fields::value));
465 break;
466 } else {
467 // use the type of the property it is bound on for scriptexpression etc.
468 DomItem propertyDefinition;
469 const QString bindingName = binding->name();
470 object.containingObject().visitLookup(
471 bindingName,
472 [&propertyDefinition](const DomItem &item) {
473 if (item.internalKind() == QQmlJS::Dom::DomType::PropertyDefinition) {
474 propertyDefinition = item;
475 return false;
476 }
477 return true;
478 },
479 LookupType::PropertyDef);
480 typeDefinition = propertyDefinition.field(Fields::type).proceedToScope();
481 break;
482 }
483 Q_UNREACHABLE();
484 }
485 case QQmlJS::Dom::DomType::Id:
486 typeDefinition = object.field(Fields::referredObject).proceedToScope();
487 break;
488 case QQmlJS::Dom::DomType::PropertyDefinition:
489 case QQmlJS::Dom::DomType::MethodParameter:
490 case QQmlJS::Dom::DomType::MethodInfo:
491 typeDefinition = object.field(Fields::type).proceedToScope();
492 break;
493 case QQmlJS::Dom::DomType::ScriptIdentifierExpression: {
494 if (DomItem type = object.filterUp(
495 [](DomType k, const DomItem &) { return k == DomType::ScriptType; },
496 FilterUpOptions::ReturnOuter)) {
497
498 const QString name = fieldMemberExpressionBits(type.field(Fields::typeName)).join(u'.');
499 switch (type.directParent().internalKind()) {
500 case DomType::QmlObject:
501 // is the type name of a QmlObject, like Item in `Item {...}`
502 typeDefinition = baseObject(type.directParent());
503 break;
504 case DomType::QmlComponent:
505 typeDefinition = type.directParent();
506 return locationFromDomItem(typeDefinition, FileLocationRegion::IdentifierRegion);
507 break;
508 default:
509 // is a type annotation, like Item in `function f(x: Item) { ... }`
510 typeDefinition = object.path(Paths::lookupTypePath(name));
511 if (typeDefinition.internalKind() == DomType::Export) {
512 typeDefinition = typeDefinition.field(Fields::type).get();
513 }
514 }
515 break;
516 }
517 if (DomItem id = object.filterUp(
518 [](DomType k, const DomItem &) { return k == DomType::Id; },
519 FilterUpOptions::ReturnOuter)) {
520
521 typeDefinition = id.field(Fields::referredObject).proceedToScope();
522 break;
523 }
524
525 auto scope = resolveExpressionType(
526 object, ResolveOptions::ResolveActualTypeForFieldMemberExpression);
527 if (!scope || !scope->semanticScope)
528 return {};
529
530 if (scope->type == QmlObjectIdIdentifier) {
531 return Location::tryFrom(scope->semanticScope->filePath(),
532 scope->semanticScope->sourceLocation(), object);
533 }
534
535 typeDefinition = sourceLocationToDomItem(object.containingFile(),
536 scope->semanticScope->sourceLocation());
537 return locationFromDomItem(typeDefinition.component(),
538 FileLocationRegion::IdentifierRegion);
539 }
540 default:
541 qDebug() << "QQmlLSUtils::findTypeDefinitionOf: Found unimplemented Type"
542 << object.internalKindStr();
543 return {};
544 }
545
546 return locationFromDomItem(typeDefinition, FileLocationRegion::MainRegion);
547}
548
549static bool findDefinitionFromItem(const DomItem &item, const QString &name)
550{
551 if (const QQmlJSScope::ConstPtr &scope = item.semanticScope()) {
552 qCDebug(QQmlLSUtilsLog) << "Searching for definition in" << item.internalKindStr();
553 if (auto jsIdentifier = scope->ownJSIdentifier(name)) {
554 qCDebug(QQmlLSUtilsLog) << "Found scope" << scope->baseTypeName();
555 return true;
556 }
557 }
558 return false;
559}
560
561static DomItem findJSIdentifierDefinition(const DomItem &item, const QString &name)
562{
563 DomItem definitionOfItem;
564 item.visitUp([&name, &definitionOfItem](const DomItem &i) {
565 if (findDefinitionFromItem(i, name)) {
566 definitionOfItem = i;
567 return false;
568 }
569 // early exit: no JS definitions/usages outside the ScriptExpression DOM element.
570 if (i.internalKind() == DomType::ScriptExpression)
571 return false;
572 return true;
573 });
574
575 if (definitionOfItem)
576 return definitionOfItem;
577
578 // special case: somebody asks for usages of a function parameter from its definition
579 // function parameters are defined in the method's scope
580 if (DomItem res = item.filterUp([](DomType k, const DomItem &) { return k == DomType::MethodInfo; },
581 FilterUpOptions::ReturnOuter)) {
582 DomItem candidate = res.field(Fields::body).field(Fields::scriptElement);
583 if (findDefinitionFromItem(candidate, name)) {
584 return candidate;
585 }
586 }
587
588 // lambda function parameters are defined in the FunctionExpression scope
589 if (DomItem res = item.filterUp(
590 [](DomType k, const DomItem &) { return k == DomType::ScriptFunctionExpression; },
591 FilterUpOptions::ReturnOuter)) {
592 if (findDefinitionFromItem(res, name)) {
593 return res;
594 }
595 }
596
597 return definitionOfItem;
598}
599
600/*!
601\internal
602Represents a signal, signal handler, property, property changed signal or a property changed
603handler.
604 */
606{
607 /*!
608 \internal The name of the signal or property, independent of whether this is a changed signal
609 or handler.
610 */
613};
614
615/*!
616\internal
617\brief Find out if \c{name} is a signal, signal handler, property, property changed signal, or a
618property changed handler in the given QQmlJSScope.
619
620Heuristic to find if name is a property, property changed signal, .... because those can appear
621under different names, for example \c{mySignal} and \c{onMySignal} for a signal.
622This will give incorrect results as soon as properties/signals/methods are called \c{onMySignal},
623\c{on<some already existing property>Changed}, ..., but the good news is that the engine also
624will act weird in these cases (e.g. one cannot bind to a property called like an already existing
625signal or a property changed handler).
626For future reference: you can always add additional checks to check the existence of those buggy
627properties/signals/methods by looking if they exist in the QQmlJSScope.
628*/
629static std::optional<SignalOrProperty> resolveNameInQmlScope(const QString &name,
630 const QQmlJSScope::ConstPtr &owner)
631{
632 if (owner->hasProperty(name)) {
633 return SignalOrProperty{ name, PropertyIdentifier };
634 }
635
636 if (const auto propertyName = QQmlSignalNames::changedHandlerNameToPropertyName(name)) {
637 if (owner->hasProperty(*propertyName)) {
638 const QString signalName = *QQmlSignalNames::changedHandlerNameToSignalName(name);
639 const QQmlJSMetaMethod signal = owner->methods(signalName).front();
640 // PropertyChangedHandlers don't have parameters: treat all other as regular signal
641 // handlers. Even if they appear in the notify of the property.
642 if (signal.parameterNames().size() == 0)
643 return SignalOrProperty{ *propertyName, PropertyChangedHandlerIdentifier };
644 else
645 return SignalOrProperty{ signalName, SignalHandlerIdentifier };
646 }
647 }
648
649 if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(name)) {
650 if (auto methods = owner->methods(*signalName); !methods.isEmpty()) {
651 if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) {
652 return SignalOrProperty{ *signalName, SignalHandlerIdentifier };
653 }
654 }
655 }
656
657 if (const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(name)) {
658 if (owner->hasProperty(*propertyName)) {
659 return SignalOrProperty{ *propertyName, PropertyChangedSignalIdentifier };
660 }
661 }
662
663 if (auto methods = owner->methods(name); !methods.isEmpty()) {
664 if (methods.front().methodType() == QQmlJSMetaMethodType::Signal) {
665 return SignalOrProperty{ name, SignalIdentifier };
666 }
667 return SignalOrProperty{ name, MethodIdentifier };
668 }
669 return std::nullopt;
670}
671
672/*!
673\internal
674Returns a list of names, that when belonging to the same targetType, should be considered equal.
675This is used to find signal handlers as usages of their corresponding signals, for example.
676*/
677static QStringList namesOfPossibleUsages(const QString &name,
678 const DomItem &item,
679 const QQmlJSScope::ConstPtr &targetType)
680{
681 QStringList namesToCheck = { name };
682 if (item.internalKind() == DomType::EnumItem || item.internalKind() == DomType::EnumDecl)
683 return namesToCheck;
684
685 auto namings = resolveNameInQmlScope(name, targetType);
686 if (!namings)
687 return namesToCheck;
688 switch (namings->type) {
689 case PropertyIdentifier: {
690 // for a property, also find bindings to its onPropertyChanged handler + propertyChanged
691 // signal
692 const QString propertyChangedHandler =
693 QQmlSignalNames::propertyNameToChangedHandlerName(namings->name);
694 namesToCheck.append(propertyChangedHandler);
695
696 const QString propertyChangedSignal =
697 QQmlSignalNames::propertyNameToChangedSignalName(namings->name);
698 namesToCheck.append(propertyChangedSignal);
699 break;
700 }
702 // for a property changed handler, also find the usages of its property + propertyChanged
703 // signal
704 namesToCheck.append(namings->name);
705 namesToCheck.append(QQmlSignalNames::propertyNameToChangedSignalName(namings->name));
706 break;
707 }
709 // for a property changed signal, also find the usages of its property + onPropertyChanged
710 // handlers
711 namesToCheck.append(namings->name);
712 namesToCheck.append(QQmlSignalNames::propertyNameToChangedHandlerName(namings->name));
713 break;
714 }
715 case SignalIdentifier: {
716 // for a signal, also find bindings to its onSignalHandler.
717 namesToCheck.append(QQmlSignalNames::signalNameToHandlerName(namings->name));
718 break;
719 }
721 // for a signal handler, also find the usages of the signal it handles
722 namesToCheck.append(namings->name);
723 break;
724 }
725 default: {
726 break;
727 }
728 }
729 return namesToCheck;
730}
731
732template<typename Predicate>
734 const QQmlJSScope::ConstPtr &referrerScope, Predicate &&check)
735{
736 QQmlJSScope::ConstPtr result;
737 QQmlJSUtils::searchBaseAndExtensionTypes(
738 referrerScope, [&](const QQmlJSScope::ConstPtr &scope) {
739 if (check(scope)) {
740 result = scope;
741 return true;
742 }
743 return false;
744 });
745
746 return result;
747}
748
749/*!
750\internal
751\brief Finds the scope where a property is first defined.
752
753Starts looking for the name starting from the given scope and traverse through base and
754extension types.
755*/
756QQmlJSScope::ConstPtr findDefiningScopeForProperty(const QQmlJSScope::ConstPtr &referrerScope,
757 const QString &nameToCheck)
758{
759 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
760 return scope->hasOwnProperty(nameToCheck);
761 });
762}
763
764/*!
765\internal
766See also findDefiningScopeForProperty().
767
768Special case: you can also bind to a signal handler.
769*/
770QQmlJSScope::ConstPtr findDefiningScopeForBinding(const QQmlJSScope::ConstPtr &referrerScope,
771 const QString &nameToCheck)
772{
773 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
774 return scope->hasOwnProperty(nameToCheck) || scope->hasOwnMethod(nameToCheck);
775 });
776}
777
778/*!
779\internal
780See also findDefiningScopeForProperty().
781*/
782QQmlJSScope::ConstPtr findDefiningScopeForMethod(const QQmlJSScope::ConstPtr &referrerScope,
783 const QString &nameToCheck)
784{
785 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
786 return scope->hasOwnMethod(nameToCheck);
787 });
788}
789
790/*!
791\internal
792See also findDefiningScopeForProperty().
793*/
794QQmlJSScope::ConstPtr findDefiningScopeForEnumeration(const QQmlJSScope::ConstPtr &referrerScope,
795 const QString &nameToCheck)
796{
797 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
798 return scope->hasOwnEnumeration(nameToCheck);
799 });
800}
801
802/*!
803\internal
804See also findDefiningScopeForProperty().
805*/
806QQmlJSScope::ConstPtr findDefiningScopeForEnumerationKey(const QQmlJSScope::ConstPtr &referrerScope,
807 const QString &nameToCheck)
808{
809 return findDefiningScopeIf(referrerScope, [&nameToCheck](const QQmlJSScope::ConstPtr &scope) {
810 return scope->hasOwnEnumerationKey(nameToCheck);
811 });
812}
813
814/*!
815 Filter away the parts of the Dom not needed for find usages, by following the profiler's
816 information.
817 1. "propertyInfos" tries to require all inherited properties of some QmlObject. That is super
818 slow (profiler says it eats 90% of the time needed by `tst_qmlls_utils findUsages`!) and is not
819 needed for usages.
820 2. "get" tries to resolve references, like base types saved in prototypes for example, and is not
821 needed to find usages. Profiler says it eats 70% of the time needed by `tst_qmlls_utils
822 findUsages`.
823 3. "defaultPropertyName" also recurses through base types and is not needed to find usages.
824*/
826{
827 FieldFilter filter{ {},
828 {
829 { QString(), Fields::propertyInfos.toString() },
830 { QString(), Fields::defaultPropertyName.toString() },
831 { QString(), Fields::get.toString() },
832 } };
833 return filter;
834};
835
836static void findUsagesOfNonJSIdentifiers(const DomItem &item, const QString &name, Usages &result)
837{
838 const auto expressionType = resolveExpressionType(item, ResolveOwnerType);
839 if (!expressionType)
840 return;
841
842 // for Qml file components: add their filename as an usage for the renaming operation
843 if (expressionType->type == QmlComponentIdentifier
844 && !expressionType->semanticScope->isInlineComponent()) {
845 result.appendFilenameUsage(expressionType->semanticScope->filePath());
846 }
847
848 const QStringList namesToCheck = namesOfPossibleUsages(name, item, expressionType->semanticScope);
849
850 const auto addLocationIfTypeMatchesTarget =
851 [&result, &expressionType, &item](const DomItem &toBeResolved, FileLocationRegion subRegion) {
852 const auto currentType =
853 resolveExpressionType(toBeResolved, ResolveOptions::ResolveOwnerType);
854 if (!currentType)
855 return;
856
857 const QQmlJSScope::ConstPtr target = expressionType->semanticScope;
858 const QQmlJSScope::ConstPtr current = currentType->semanticScope;
859 if (target == current) {
860 auto tree = FileLocations::treeOf(toBeResolved);
861 QQmlJS::SourceLocation sourceLocation;
862
863 sourceLocation = FileLocations::region(tree, subRegion);
864 if (!sourceLocation.isValid())
865 return;
866
867 if (auto location = Location::tryFrom(toBeResolved.canonicalFilePath(),
868 sourceLocation, item)) {
869 result.appendUsage(*location);
870 }
871 }
872 };
873
874 auto findUsages = [&addLocationIfTypeMatchesTarget, &name,
875 &namesToCheck](Path, const DomItem &current, bool) -> bool {
876 bool continueForChildren = true;
877 if (auto scope = current.semanticScope()) {
878 // is the current property shadowed by some JS identifier? ignore current + its children
879 if (scope->ownJSIdentifier(name)) {
880 return false;
881 }
882 }
883 switch (current.internalKind()) {
884 case DomType::QmlObject:
885 case DomType::Binding:
886 case DomType::MethodInfo:
887 case DomType::PropertyDefinition: {
888 const QString propertyName = current.field(Fields::name).value().toString();
889 if (namesToCheck.contains(propertyName))
890 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
891 return continueForChildren;
892 }
893 case DomType::ScriptIdentifierExpression: {
894 const QString identifierName = current.field(Fields::identifier).value().toString();
895 if (namesToCheck.contains(identifierName))
896 addLocationIfTypeMatchesTarget(current, MainRegion);
897 return continueForChildren;
898 }
899 case DomType::ScriptLiteral: {
900 const QString literal = current.field(Fields::value).value().toString();
901 if (namesToCheck.contains(literal))
902 addLocationIfTypeMatchesTarget(current, MainRegion);
903 return continueForChildren;
904 }
905 case DomType::EnumItem: {
906 // Only look for the first enum defined. The inner enums
907 // have no way to be accessed.
908 const auto parentPath = current.containingObject().pathFromOwner();
909 const auto index = parentPath.last().headIndex();
910 if (index != 0)
911 return continueForChildren;
912 const QString enumValue = current.field(Fields::name).value().toString();
913 if (namesToCheck.contains(enumValue))
914 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
915 return continueForChildren;
916 }
917 case DomType::EnumDecl: {
918 // Only look for the first enum defined. The inner enums
919 // have no way to be accessed.
920 const auto parentPath = current.pathFromOwner();
921 const auto index = parentPath.last().headIndex();
922 if (index != 0)
923 return continueForChildren;
924 const QString enumValue = current.field(Fields::name).value().toString();
925 if (namesToCheck.contains(enumValue))
926 addLocationIfTypeMatchesTarget(current, IdentifierRegion);
927 return continueForChildren;
928 }
929 default:
930 return continueForChildren;
931 };
932
933 Q_UNREACHABLE_RETURN(continueForChildren);
934 };
935
936 const DomItem qmlFiles = item.top().field(Fields::qmlFileWithPath);
937 const auto filter = filterForFindUsages();
938 for (const QString &file : qmlFiles.keys()) {
939 const DomItem currentFileComponents =
940 qmlFiles.key(file).field(Fields::currentItem).field(Fields::components);
941 currentFileComponents.visitTree(Path(), emptyChildrenVisitor,
942 VisitOption::Recurse | VisitOption::VisitSelf, findUsages,
943 emptyChildrenVisitor, filter);
944 }
945}
946
947static std::optional<Location> locationFromJSIdentifierDefinition(const DomItem &definitionOfItem,
948 const QString &name)
949{
950 Q_ASSERT_X(!definitionOfItem.semanticScope().isNull()
951 && definitionOfItem.semanticScope()->ownJSIdentifier(name).has_value(),
952 "QQmlLSUtils::locationFromJSIdentifierDefinition",
953 "JS definition does not actually define the JS identifier. "
954 "Did you obtain definitionOfItem from findJSIdentifierDefinition() ?");
955 const QQmlJS::SourceLocation location =
956 definitionOfItem.semanticScope()->ownJSIdentifier(name).value().location;
957
958 return Location::tryFrom(definitionOfItem.canonicalFilePath(), location, definitionOfItem);
959}
960
961static void findUsagesHelper(const DomItem &item, const QString &name, Usages &result)
962{
963 qCDebug(QQmlLSUtilsLog) << "Looking for JS identifier with name" << name;
964 DomItem definitionOfItem = findJSIdentifierDefinition(item, name);
965
966 // if there is no definition found: check if name was a property or an id instead
967 if (!definitionOfItem) {
968 qCDebug(QQmlLSUtilsLog) << "No defining JS-Scope found!";
969 findUsagesOfNonJSIdentifiers(item, name, result);
970 return;
971 }
972
973 definitionOfItem.visitTree(
974 Path(), emptyChildrenVisitor, VisitOption::VisitAdopted | VisitOption::Recurse,
975 [&name, &result](Path, const DomItem &item, bool) -> bool {
976 qCDebug(QQmlLSUtilsLog) << "Visiting a " << item.internalKindStr();
977 if (item.internalKind() == DomType::ScriptIdentifierExpression
978 && item.field(Fields::identifier).value().toString() == name) {
979 // add this usage
980 auto fileLocation = FileLocations::treeOf(item);
981 if (!fileLocation) {
982 qCWarning(QQmlLSUtilsLog) << "Failed finding filelocation of found usage";
983 return true;
984 }
985 const QQmlJS::SourceLocation sourceLocation = fileLocation->info().fullRegion;
986 const QString fileName = item.canonicalFilePath();
987 if (auto location = Location::tryFrom(fileName, sourceLocation, item))
988 result.appendUsage(*location);
989 return true;
990 }
991 QQmlJSScope::ConstPtr scope = item.semanticScope();
992 // Do not visit children if current JS identifier has been redefined.
993 if (scope && scope != item.directParent().semanticScope()
994 && scope->ownJSIdentifier(name)) {
995 // Note that if the current semantic scope == parent's semantic scope, then no
996 // redefinition actually took place. This happens for example in
997 // FunctionExpressions, where the body's semantic scope is the
998 // FunctionExpression's semantic scope.
999 return false;
1000 }
1001 return true;
1002 },
1003 emptyChildrenVisitor, filterForFindUsages());
1004
1005 if (const auto definition = locationFromJSIdentifierDefinition(definitionOfItem, name))
1006 result.appendUsage(*definition);
1007}
1008
1009Usages findUsagesOf(const DomItem &item)
1010{
1011 Usages result;
1012
1013 switch (item.internalKind()) {
1014 case DomType::ScriptIdentifierExpression: {
1015 const QString name = item.field(Fields::identifier).value().toString();
1016 findUsagesHelper(item, name, result);
1017 break;
1018 }
1019 case DomType::ScriptVariableDeclarationEntry: {
1020 const QString name = item.field(Fields::identifier).value().toString();
1021 findUsagesHelper(item, name, result);
1022 break;
1023 }
1024 case DomType::EnumDecl:
1025 case DomType::EnumItem:
1026 case DomType::QmlObject:
1027 case DomType::PropertyDefinition:
1028 case DomType::Binding:
1029 case DomType::MethodInfo: {
1030 const QString name = item.field(Fields::name).value().toString();
1031 findUsagesHelper(item, name, result);
1032 break;
1033 }
1034 case DomType::QmlComponent: {
1035 QString name = item.field(Fields::name).value().toString();
1036
1037 // get rid of extra qualifiers
1038 if (const auto dotIndex = name.indexOf(u'.'); dotIndex != -1)
1039 name = name.sliced(dotIndex + 1);
1040 findUsagesHelper(item, name, result);
1041 break;
1042 }
1043 default:
1044 qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
1045 << "was not implemented for QQmlLSUtils::findUsagesOf";
1046 return result;
1047 }
1048
1049 result.sort();
1050
1051 if (QQmlLSUtilsLog().isDebugEnabled()) {
1052 qCDebug(QQmlLSUtilsLog) << "Found following usages in files:";
1053 for (auto r : result.usagesInFile()) {
1054 qCDebug(QQmlLSUtilsLog) << r.filename() << " @ " << r.sourceLocation().startLine << ":"
1055 << r.sourceLocation().startColumn << " with length "
1056 << r.sourceLocation().length;
1057 }
1058 qCDebug(QQmlLSUtilsLog) << "And following usages in file names:"
1059 << result.usagesInFilename();
1060 }
1061
1062 return result;
1063}
1064
1065static std::optional<IdentifierType> hasMethodOrSignal(const QQmlJSScope::ConstPtr &scope,
1066 const QString &name)
1067{
1068 auto methods = scope->methods(name);
1069 if (methods.isEmpty())
1070 return {};
1071
1072 const bool isSignal = methods.front().methodType() == QQmlJSMetaMethodType::Signal;
1073 IdentifierType type =
1075 return type;
1076}
1077
1078/*!
1079\internal
1080Searches for a method by traversing the parent scopes.
1081
1082We assume here that it is possible to call methods from parent scope to simplify things, as the
1083linting module already warns about calling methods from parent scopes.
1084
1085Note: in QML, one can only call methods from the current scope, and from the QML file root scope.
1086Everything else needs a qualifier.
1087*/
1088static std::optional<ExpressionType>
1089methodFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name,
1090 ResolveOptions options)
1091{
1092 for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) {
1093 if (auto type = hasMethodOrSignal(current, name)) {
1094 switch (options) {
1095 case ResolveOwnerType:
1096 return ExpressionType{ name, findDefiningScopeForMethod(current, name), *type };
1098 // QQmlJSScopes were not implemented for methods yet, but JS functions have methods
1099 // and properties see
1100 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
1101 // for the list of properties/methods of functions. Therefore return a null scope.
1102 // see also code below for non-qualified method access
1103 return ExpressionType{ name, {}, *type };
1104 }
1105 }
1106
1107 if (const auto signalName = QQmlSignalNames::handlerNameToSignalName(name)) {
1108 if (auto type = hasMethodOrSignal(current, *signalName)) {
1109 switch (options) {
1110 case ResolveOwnerType:
1111 return ExpressionType{ name, findDefiningScopeForMethod(current, *signalName),
1112 SignalHandlerIdentifier };
1114 // Properties and methods of JS methods are not supported yet
1115 return ExpressionType{ name, {}, SignalHandlerIdentifier };
1116 }
1117 }
1118 }
1119 }
1120 return {};
1121}
1122
1123
1124/*!
1125\internal
1126See comment on methodFromReferrerScope: the same applies to properties.
1127*/
1128static std::optional<ExpressionType>
1129propertyFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &propertyName,
1130 ResolveOptions options)
1131{
1132 for (QQmlJSScope::ConstPtr current = referrerScope; current; current = current->parentScope()) {
1133 const auto resolved = resolveNameInQmlScope(propertyName, current);
1134 if (!resolved)
1135 continue;
1136
1137 if (auto property = current->property(resolved->name); property.isValid()) {
1138 switch (options) {
1139 case ResolveOwnerType:
1140 return ExpressionType{ propertyName,
1141 findDefiningScopeForProperty(current, propertyName),
1142 resolved->type };
1144 return ExpressionType{ propertyName, property.type(), resolved->type };
1145 }
1146 }
1147 }
1148 return {};
1149}
1150
1151/*!
1152\internal
1153See comment on methodFromReferrerScope: the same applies to property bindings.
1154
1155If resolver is not null then it is used to resolve the id with which a generalized grouped
1156properties starts.
1157*/
1158static std::optional<ExpressionType>
1159propertyBindingFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name,
1160 ResolveOptions options, QQmlJSTypeResolver *resolverForIds)
1161{
1162 const auto bindings = referrerScope->propertyBindings(name);
1163 if (bindings.isEmpty())
1164 return {};
1165
1166 const auto binding = bindings.begin();
1167 const auto bindingType = binding->bindingType();
1168 const bool bindingIsAttached = bindingType == QQmlSA::BindingType::AttachedProperty;
1169 if (!bindingIsAttached && bindingType != QQmlSA::BindingType::GroupProperty)
1170 return {};
1171
1172 // Generalized grouped properties, like Bindings or PropertyChanges, for example, have bindings
1173 // starting in an id (like `someId.someProperty: ...`).
1174 // If `someid` is not a property and is a deferred name, then it should be an id.
1175 if (!bindingIsAttached && !referrerScope->hasProperty(name)
1176 && referrerScope->isNameDeferred(name)) {
1177 if (!resolverForIds)
1178 return {};
1179
1180 return ExpressionType {
1181 name,
1182 resolverForIds->typeForId(referrerScope, name, AssumeComponentsAreBound),
1183 QmlObjectIdIdentifier
1184 };
1185 }
1186
1187 const auto typeIdentifier =
1189
1190 const auto getScope = [bindingIsAttached, binding]() -> QQmlJSScope::ConstPtr {
1191 if (bindingIsAttached)
1192 return binding->attachedType();
1193
1194 return binding->groupType();
1195 };
1196
1197 switch (options) {
1198 case ResolveOwnerType: {
1199 return ExpressionType{ name,
1200 // note: always return the type of the attached type as the owner.
1201 // Find usages on "Keys.", for example, should yield all usages of
1202 // the "Keys" attached property.
1203 bindingIsAttached
1204 ? getScope()
1205 : findDefiningScopeForProperty(referrerScope, name),
1206 typeIdentifier };
1207 }
1209 return ExpressionType{ name, getScope(), typeIdentifier };
1210 }
1211 Q_UNREACHABLE_RETURN({});
1212}
1213
1214/*! \internal
1215 Finds the scope within the special elements like Connections,
1216 PropertyChanges, Bindings or AnchorChanges.
1217*/
1219 const QQmlJSScope::ConstPtr &scope, const DomItem &item)
1220{
1221 if (!scope)
1222 return {};
1223
1224 const QSet<QString> specialItems = {u"QQmlConnections"_s,
1225 u"QQuickPropertyChanges"_s,
1226 u"QQmlBind"_s,
1227 u"QQuickAnchorChanges"_s};
1228
1229 const auto special = QQmlJSUtils::searchBaseAndExtensionTypes(
1230 scope, [&specialItems](const QQmlJSScope::ConstPtr &visitedScope) {
1231 const auto typeName = visitedScope->internalName();
1232 if (specialItems.contains(typeName))
1233 return true;
1234 return false;
1235 });
1236
1237 if (!special)
1238 return {};
1239
1240 // Perform target name search if there is binding to property "target"
1241 QString targetName;
1242 if (scope->hasOwnPropertyBindings(u"target"_s)) {
1243 // TODO: propagate the whole binding.
1244 // We can figure out the meaning of target in more cases.
1245
1246 DomItem current = item.qmlObject();
1247 auto target = current.bindings().key(u"target"_s).index(0);
1248 if (target) {
1249 targetName = target.field(Fields::value)
1250 .field(Fields::scriptElement)
1251 .field(Fields::identifier)
1252 .value()
1253 .toString();
1254 }
1255 }
1256
1257 if (!targetName.isEmpty()) {
1258 // target: someId
1259 auto resolver = item.containingFile().ownerAs<QmlFile>()->typeResolver();
1260 if (!resolver)
1261 return {};
1262
1263 // Note: It does not have to be an ID. It can be a property.
1264 return resolver->scopedType(scope, targetName);
1265 }
1266
1267 if (item.internalKind() == DomType::Binding &&
1268 item.field(Fields::bindingType).value().toInteger() == int(BindingType::OnBinding)) {
1269 // Binding on sth : {} syntax
1270 // Target scope is the current scope
1271 return scope;
1272 }
1273 return scope->parentScope();
1274}
1275
1276/*!
1277\internal
1278\brief Distinguishes singleton types from attached types and "regular" qml components.
1279 */
1280static std::optional<ExpressionType>
1281resolveTypeName(const std::shared_ptr<QQmlJSTypeResolver> &resolver, const QString &name,
1282 const DomItem &item, ResolveOptions options)
1283{
1284 const auto scope = resolver->typeForName(name);
1285 if (!scope)
1286 return {};
1287
1288 if (scope->isSingleton())
1290
1291 // A type not followed by a field member expression is just a type. Otherwise, it could either
1292 // be a type or an attached type!
1293 if (!isFieldMemberBase(item))
1294 return ExpressionType{ name, scope, QmlComponentIdentifier };
1295
1296 // take the right hand side and unwrap in case its a nested fieldmemberexpression
1297 const DomItem rightHandSide = [&item]() {
1298 const DomItem candidate = item.directParent().field(Fields::right);
1299 // case B(a,b) for the right hand side of `a` in `a.b`
1300 if (candidate != item)
1301 return candidate;
1302 // case B(B(a,b),c) for the right hand side of `b` in `a.b.c`
1303 return item.directParent().directParent().field(Fields::right);
1304 }();
1305
1306 if (rightHandSide.internalKind() != DomType::ScriptIdentifierExpression)
1307 return ExpressionType{ name, scope, QmlComponentIdentifier };
1308
1309 const QString fieldMemberAccessName = rightHandSide.value().toString();
1310 if (fieldMemberAccessName.isEmpty() || !fieldMemberAccessName.front().isLower())
1311 return ExpressionType{ name, scope, QmlComponentIdentifier };
1312
1313 if (scope->attachedType()) {
1314 return ExpressionType{ name, options == ResolveOwnerType ? scope : scope->attachedType(),
1316 }
1317 return {};
1318}
1319
1320static std::optional<ExpressionType> resolveFieldMemberExpressionType(const DomItem &item,
1321 ResolveOptions options)
1322{
1323 const QString name = item.field(Fields::identifier).value().toString();
1324 DomItem parent = item.directParent();
1325 auto owner = resolveExpressionType(parent.field(Fields::left),
1326 ResolveOptions::ResolveActualTypeForFieldMemberExpression);
1327 if (!owner)
1328 return {};
1329
1330 if (!owner->semanticScope) {
1331 // JS objects can get new members and methods during runtime and therefore has no
1332 // qqmljsscopes. Therefore, just label everything inside a JavaScriptIdentifier as being
1333 // another JavaScriptIdentifier.
1334 if (owner->type == JavaScriptIdentifier) {
1335 return ExpressionType{ name, {}, JavaScriptIdentifier };
1336 } else if (owner->type == QualifiedModuleIdentifier) {
1337 const QmlFile *qmlFile = item.fileObject().as<QmlFile>();
1338 if (!qmlFile)
1339 return {};
1340 if (auto scope = resolveTypeName(
1341 qmlFile->typeResolver(), u"%1.%2"_s.arg(*owner->name, name), item,
1342 options)) {
1343 // remove the qualified module name from the type name
1344 scope->name = name;
1345 return scope;
1346 }
1347 }
1348 return {};
1349 }
1350
1351 if (auto scope = methodFromReferrerScope(owner->semanticScope, name, options))
1352 return *scope;
1353
1354 if (auto scope = propertyBindingFromReferrerScope(owner->semanticScope, name, options, nullptr))
1355 return *scope;
1356
1357 if (auto scope = propertyFromReferrerScope(owner->semanticScope, name, options))
1358 return *scope;
1359
1360 if (owner->type == QmlComponentIdentifier || owner->type == EnumeratorIdentifier) {
1361 // Check if name is a enum value <TypeName>.<EnumValue> or ...<EnumName>.<EnumValue>
1362 // Enumerations should live under the root element scope of the file that defines the enum,
1363 // therefore use the DomItem to find the root element of the qml file instead of directly
1364 // using owner->semanticScope.
1365 if (const auto scope = owner->semanticScope->isComposite()
1366 ? item.goToFile(owner->semanticScope->filePath())
1367 .rootQmlObject(GoTo::MostLikely)
1368 .semanticScope()
1369 : owner->semanticScope) {
1370 if (scope->hasEnumerationKey(name)) {
1371 return ExpressionType{ name, scope, EnumeratorValueIdentifier };
1372 }
1373 // Or it is a enum name <TypeName>.<EnumName>.<EnumValue>
1374 else if (scope->hasEnumeration(name)) {
1375 return ExpressionType{ name, scope, EnumeratorIdentifier };
1376 }
1377 }
1378
1379 // check inline components <TypeName>.<InlineComponentName>
1380 for (auto it = owner->semanticScope->childScopesBegin(),
1381 end = owner->semanticScope->childScopesEnd();
1382 it != end; ++it) {
1383 if ((*it)->inlineComponentName() == name) {
1384 return ExpressionType{ name, *it, QmlComponentIdentifier };
1385 }
1386 }
1387 return {};
1388 }
1389
1390 qCDebug(QQmlLSUtilsLog) << "Could not find identifier expression for" << item.internalKindStr();
1391 return owner;
1392}
1393
1394/*!
1395\internal
1396Resolves the expression type of a binding for signal handlers, like the function expression
1397\c{(x) => ...} in
1398
1399\qml
1400onHelloSignal: (x) => ...
1401\endqml
1402
1403would be resolved to the \c{onHelloSignal} expression type, for example.
1404*/
1405static std::optional<ExpressionType> resolveBindingIfSignalHandler(const DomItem &functionExpression)
1406{
1407 if (functionExpression.internalKind() != DomType::ScriptFunctionExpression)
1408 return {};
1409
1410 const DomItem parent = functionExpression.directParent();
1411 if (parent.internalKind() != DomType::ScriptExpression)
1412 return {};
1413
1414 const DomItem grandParent = parent.directParent();
1415 if (grandParent.internalKind() != DomType::Binding)
1416 return {};
1417
1418 auto bindingType = resolveExpressionType(grandParent, ResolveOwnerType);
1419 return bindingType;
1420}
1421
1422/*!
1423\internal
1424In a signal handler
1425
1426\qml
1427 onSomeSignal: (x, y, z) => ....
1428\endqml
1429
1430the parameters \c x, \c y and \c z are not allowed to have type annotations: instead, their type is
1431defined by the signal definition itself.
1432
1433This code detects signal handler parameters and resolves their type using the signal's definition.
1434*/
1435static std::optional<ExpressionType>
1436resolveSignalHandlerParameterType(const DomItem &parameterDefinition, const QString &name,
1437 ResolveOptions options)
1438{
1439 const std::optional<QQmlJSScope::JavaScriptIdentifier> jsIdentifier =
1440 parameterDefinition.semanticScope()->jsIdentifier(name);
1441 if (!jsIdentifier || jsIdentifier->kind != QQmlJSScope::JavaScriptIdentifier::Parameter)
1442 return {};
1443
1444 const DomItem handlerFunctionExpression =
1445 parameterDefinition.internalKind() == DomType::ScriptBlockStatement
1446 ? parameterDefinition.directParent()
1447 : parameterDefinition;
1448
1449 const std::optional<ExpressionType> bindingType =
1450 resolveBindingIfSignalHandler(handlerFunctionExpression);
1451 if (!bindingType)
1452 return {};
1453
1454 if (bindingType->type == PropertyChangedHandlerIdentifier)
1455 return ExpressionType{};
1456
1457 if (bindingType->type != SignalHandlerIdentifier)
1458 return {};
1459
1460 const DomItem parameters = handlerFunctionExpression[Fields::parameters];
1461 const int indexOfParameter = [&parameters, &name]() {
1462 for (int i = 0; i < parameters.indexes(); ++i) {
1463 if (parameters[i][Fields::identifier].value().toString() == name)
1464 return i;
1465 }
1466 Q_ASSERT_X(false, "resolveSignalHandlerParameter",
1467 "can't find JS identifier with Parameter kind in the parameters");
1468 Q_UNREACHABLE_RETURN(-1);
1469 }();
1470
1471 const std::optional<QString> signalName =
1472 QQmlSignalNames::handlerNameToSignalName(*bindingType->name);
1473 Q_ASSERT_X(signalName.has_value(), "resolveSignalHandlerParameterType",
1474 "handlerNameToSignalName failed on a SignalHandler");
1475
1476 const QQmlJSMetaMethod signalDefinition =
1477 bindingType->semanticScope->methods(*signalName).front();
1478 const QList<QQmlJSMetaParameter> parameterList = signalDefinition.parameters();
1479
1480 // not a signal handler parameter after all
1481 if (parameterList.size() <= indexOfParameter)
1482 return {};
1483
1484 // now we can return an ExpressionType, even if the indexOfParameter calculation result is only
1485 // needed to check whether this is a signal handler parameter or not.
1486 if (options == ResolveOwnerType)
1487 return ExpressionType{ name, bindingType->semanticScope, JavaScriptIdentifier };
1488 else {
1489 const QQmlJSScope::ConstPtr parameterType = parameterList[indexOfParameter].type();
1490 return ExpressionType{ name, parameterType, JavaScriptIdentifier };
1491 }
1492}
1493
1494static std::optional<ExpressionType> resolveIdentifierExpressionType(const DomItem &item,
1495 ResolveOptions options)
1496{
1497 if (isFieldMemberAccess(item) || isFieldMemberExpression(item)) {
1498 return resolveFieldMemberExpressionType(item, options);
1499 }
1500
1501 const QString name = item.field(Fields::identifier).value().toString();
1502
1503 if (const DomItem definitionOfItem = findJSIdentifierDefinition(item, name)) {
1504 Q_ASSERT_X(!definitionOfItem.semanticScope().isNull()
1505 && definitionOfItem.semanticScope()->ownJSIdentifier(name),
1506 "QQmlLSUtils::findDefinitionOf",
1507 "JS definition does not actually define the JS identifer. "
1508 "It should be empty.");
1509 if (auto parameter = resolveSignalHandlerParameterType(definitionOfItem, name, options))
1510 return parameter;
1511
1512 const auto scope = definitionOfItem.semanticScope();
1513 return ExpressionType{ name,
1514 options == ResolveOwnerType
1515 ? scope
1516 : QQmlJSScope::ConstPtr(
1517 scope->ownJSIdentifier(name)->scope.toStrongRef()),
1518 IdentifierType::JavaScriptIdentifier };
1519 }
1520
1521 const auto referrerScope = item.nearestSemanticScope();
1522 if (!referrerScope)
1523 return {};
1524
1525 // check if its a method
1526 if (auto scope = methodFromReferrerScope(referrerScope, name, options))
1527 return scope;
1528
1529 const auto qmlFile = item.containingFile().ownerAs<QmlFile>();
1530 if (!qmlFile)
1531 return {};
1532
1533 const auto resolver = qmlFile->typeResolver();
1534 if (!resolver)
1535 return {};
1536
1537 // check if its found as a property binding
1538 if (const auto scope = propertyBindingFromReferrerScope(
1539 referrerScope, name, options, resolver.get())) {
1540 return *scope;
1541 }
1542
1543 // check if its an (unqualified) property
1544 if (const auto scope = propertyFromReferrerScope(referrerScope, name, options))
1545 return *scope;
1546
1547 if (resolver->seenModuleQualifiers().contains(name))
1548 return ExpressionType{ name, {}, QualifiedModuleIdentifier };
1549
1550 if (const auto scope = resolveTypeName(resolver, name, item, options))
1551 return scope;
1552
1553 // check if its an id
1554 if (const QQmlJSScope::ConstPtr fromId
1555 = resolver->typeForId(referrerScope, name, AssumeComponentsAreBound)) {
1556 return ExpressionType{ name, fromId, QmlObjectIdIdentifier };
1557 }
1558
1559 const QQmlJSScope::ConstPtr jsGlobal = resolver->jsGlobalObject();
1560 // check if its a JS global method
1561 if (auto scope = methodFromReferrerScope(jsGlobal, name, options)) {
1562 scope->type = JavaScriptIdentifier;
1563 return scope;
1564 }
1565
1566 // check if its an JS global property
1567 if (auto scope = propertyFromReferrerScope(jsGlobal, name, options)) {
1568 scope->type = JavaScriptIdentifier;
1569 return scope;
1570 }
1571
1572 return {};
1573}
1574
1575static std::optional<ExpressionType>
1576resolveSignalOrPropertyExpressionType(const QString &name, const QQmlJSScope::ConstPtr &scope,
1577 ResolveOptions options)
1578{
1579 const auto signalOrProperty = resolveNameInQmlScope(name, scope);
1580 if (!signalOrProperty)
1581 return {};
1582
1583 switch (signalOrProperty->type) {
1584 case PropertyIdentifier:
1585 switch (options) {
1586 case ResolveOwnerType:
1587 return ExpressionType{ name, findDefiningScopeForProperty(scope, name),
1588 signalOrProperty->type };
1590 return ExpressionType{ name, scope->property(name).type(), signalOrProperty->type };
1591 }
1592 Q_UNREACHABLE_RETURN({});
1594 switch (options) {
1595 case ResolveOwnerType:
1596 return ExpressionType{ name,
1597 findDefiningScopeForProperty(scope, signalOrProperty->name),
1598 signalOrProperty->type };
1600 // Properties and methods are not implemented on methods.
1601 Q_UNREACHABLE_RETURN({});
1602 }
1603 Q_UNREACHABLE_RETURN({});
1606 case SignalIdentifier:
1607 case MethodIdentifier:
1608 switch (options) {
1609 case ResolveOwnerType: {
1610 return ExpressionType{ name, findDefiningScopeForMethod(scope, signalOrProperty->name),
1611 signalOrProperty->type };
1612 }
1614 // Properties and methods are not implemented on methods.
1615 Q_UNREACHABLE_RETURN({});
1616 }
1617 Q_UNREACHABLE_RETURN({});
1618 default:
1619 Q_UNREACHABLE_RETURN({});
1620 }
1621}
1622
1623/*!
1624 \internal
1625 \brief
1626 Resolves the type of the given DomItem, when possible (e.g., when there are enough type
1627 annotations).
1628
1629 Might return an ExpressionType without(!) semantic scope when no type information is available, for
1630 example resolving the type of x in someJSObject.x (where `let someJSObject = { x: 42 };`) then
1631 the name and type of x is known but no semantic scope can be obtained.
1632*/
1633std::optional<ExpressionType> resolveExpressionType(const QQmlJS::Dom::DomItem &item,
1634 ResolveOptions options)
1635{
1636 switch (item.internalKind()) {
1637 case DomType::ScriptIdentifierExpression: {
1638 return resolveIdentifierExpressionType(item, options);
1639 }
1640 case DomType::PropertyDefinition: {
1641 auto propertyDefinition = item.as<PropertyDefinition>();
1642 if (propertyDefinition && propertyDefinition->semanticScope()) {
1643 const auto &scope = propertyDefinition->semanticScope();
1644 switch (options) {
1645 case ResolveOwnerType:
1646 return ExpressionType{ propertyDefinition->name, scope, PropertyIdentifier };
1648 // There should not be any PropertyDefinition inside a FieldMemberExpression.
1649 Q_UNREACHABLE_RETURN({});
1650 }
1651 Q_UNREACHABLE_RETURN({});
1652 }
1653 return {};
1654 }
1655 case DomType::Binding: {
1656 auto binding = item.as<Binding>();
1657 if (binding) {
1658 std::optional<QQmlJSScope::ConstPtr> owner = item.qmlObject().semanticScope();
1659 if (!owner)
1660 return {};
1661 const QString name = binding->name();
1662
1663 if (name == u"id")
1664 return ExpressionType{ name, owner.value(), QmlObjectIdIdentifier };
1665
1666 if (const QQmlJSScope::ConstPtr targetScope
1667 = findScopeOfSpecialItems(owner.value(), item)) {
1668 const auto signalOrProperty = resolveNameInQmlScope(name, targetScope);
1669 if (!signalOrProperty)
1670 return {};
1671 switch (options) {
1672 case ResolveOwnerType:
1673 return ExpressionType{
1674 name, findDefiningScopeForBinding(targetScope, signalOrProperty->name),
1675 signalOrProperty->type
1676 };
1678 // Bindings can't be inside of FieldMemberExpressions.
1679 Q_UNREACHABLE_RETURN({});
1680 }
1681 }
1682 if (auto result = resolveSignalOrPropertyExpressionType(name, owner.value(), options)) {
1683 return result;
1684 }
1685 qDebug(QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"
1686 "type of a Binding.";
1687 }
1688
1689 return {};
1690 }
1691 case DomType::QmlObject: {
1692 auto object = item.as<QmlObject>();
1693 if (!object)
1694 return {};
1695 if (auto scope = object->semanticScope()) {
1696 const auto name = item.name();
1697 const bool isComponent = name.front().isUpper();
1698 if (isComponent)
1699 scope = scope->baseType();
1700 const IdentifierType type =
1702 switch (options) {
1703 case ResolveOwnerType:
1704 return ExpressionType{ name, scope, type };
1706 return ExpressionType{ name, scope, type };
1707 }
1708 }
1709 return {};
1710 }
1711 case DomType::QmlComponent: {
1712 auto component = item.as<QmlComponent>();
1713 if (!component)
1714 return {};
1715 const auto scope = component->semanticScope();
1716 if (!scope)
1717 return {};
1718
1719 QString name = item.name();
1720 if (auto dotIndex = name.indexOf(u'.'); dotIndex != -1)
1721 name = name.sliced(dotIndex + 1);
1722 switch (options) {
1723 case ResolveOwnerType:
1724 return ExpressionType{ name, scope, QmlComponentIdentifier };
1726 return ExpressionType{ name, scope, QmlComponentIdentifier };
1727 }
1728 Q_UNREACHABLE_RETURN({});
1729 }
1730 case DomType::ScriptFunctionExpression: {
1731 return ExpressionType{ {}, item.semanticScope(), LambdaMethodIdentifier };
1732 }
1733 case DomType::MethodInfo: {
1734 const auto object = item.as<MethodInfo>();
1735 if (object && object->semanticScope()) {
1736 std::optional<QQmlJSScope::ConstPtr> scope = object->semanticScope();
1737 if (!scope)
1738 return {};
1739
1740 if (const QQmlJSScope::ConstPtr targetScope =
1741 findScopeOfSpecialItems(scope.value()->parentScope(), item)) {
1742 const auto signalOrProperty = resolveNameInQmlScope(object->name, targetScope);
1743 if (!signalOrProperty)
1744 return {};
1745
1746 switch (options) {
1747 case ResolveOwnerType:
1748 return ExpressionType{ object->name,
1749 findDefiningScopeForMethod(targetScope,
1750 signalOrProperty->name),
1751 signalOrProperty->type };
1753 // not supported for methods
1754 return {};
1755 }
1756 }
1757
1758 // in case scope is the semantic scope for the function bodies: grab the owner's scope
1759 // this happens for all methods but not for signals (they do not have a body)
1760 if (QQmlSA::isFunctionScope(scope.value()->scopeType()))
1761 scope = scope.value()->parentScope();
1762
1763 if (const auto result = resolveSignalOrPropertyExpressionType(
1764 object->name, scope.value(), options)) {
1765 return result;
1766 }
1767 qDebug(QQmlLSUtilsLog) << "QQmlLSUtils::resolveExpressionType() could not resolve the"
1768 "type of a MethodInfo.";
1769 }
1770
1771 return {};
1772 }
1773 case DomType::ScriptBinaryExpression: {
1774 if (isFieldMemberExpression(item)) {
1775 return resolveExpressionType(item.field(Fields::right), options);
1776 }
1777 return {};
1778 }
1779 case DomType::ScriptLiteral: {
1780 /* special case
1781 Binding { target: someId; property: "someProperty"}
1782 */
1783 const auto scope = item.qmlObject().semanticScope();
1784 const auto name = item.field(Fields::value).value().toString();
1785 if (const QQmlJSScope::ConstPtr targetScope = findScopeOfSpecialItems(scope, item)) {
1786 const auto signalOrProperty = resolveNameInQmlScope(name, targetScope);
1787 if (!signalOrProperty)
1788 return {};
1789 switch (options) {
1790 case ResolveOwnerType:
1791 return ExpressionType{
1792 name, findDefiningScopeForProperty(targetScope, signalOrProperty->name),
1793 signalOrProperty->type
1794 };
1796 // ScriptLiteral's can't be inside of FieldMemberExpression's, especially when they
1797 // are inside a special item.
1798 Q_UNREACHABLE_RETURN({});
1799 }
1800 }
1801 return {};
1802 }
1803 case DomType::EnumItem: {
1804 const QString enumValue = item.field(Fields::name).value().toString();
1805 const QQmlJSScope::ConstPtr referrerScope
1806 = item.rootQmlObject(GoTo::MostLikely).semanticScope();
1807 if (!referrerScope->hasEnumerationKey(enumValue))
1808 return {};
1809 switch (options) {
1810 // special case: use the owner's scope here, as enums do not have their own
1811 // QQmlJSScope.
1812 case ResolveActualTypeForFieldMemberExpression:
1813 case ResolveOwnerType:
1814 return ExpressionType {
1815 enumValue,
1816 findDefiningScopeForEnumerationKey(referrerScope, enumValue),
1817 EnumeratorValueIdentifier
1818 };
1819 }
1820 Q_UNREACHABLE_RETURN({});
1821 }
1822 case DomType::EnumDecl: {
1823 const QString enumName = item.field(Fields::name).value().toString();
1824 const QQmlJSScope::ConstPtr referrerScope
1825 = item.rootQmlObject(GoTo::MostLikely).semanticScope();
1826 if (!referrerScope->hasEnumeration(enumName))
1827 return {};
1828 switch (options) {
1829 // special case: use the owner's scope here, as enums do not have their own QQmlJSScope.
1830 case ResolveActualTypeForFieldMemberExpression:
1831 case ResolveOwnerType:
1832 return ExpressionType {
1833 enumName,
1834 findDefiningScopeForEnumeration(referrerScope, enumName),
1835 EnumeratorIdentifier
1836 };
1837 }
1838
1839 Q_UNREACHABLE_RETURN({});
1840 }
1841 case DomType::Import: {
1842 // we currently only support qualified module identifiers
1843 if (!item[Fields::importId])
1844 return {};
1845 return ExpressionType{ item[Fields::importId].value().toString(),
1846 {},
1847 QualifiedModuleIdentifier };
1848 }
1849 case DomType::ScriptNewMemberExpression: {
1850 const auto name = item.field(Fields::base).value().toString();
1851 return ExpressionType{ name, {}, JavaScriptIdentifier };
1852 }
1853 case DomType::ScriptCallExpression: {
1854 const DomItem callee = item.field(Fields::callee);
1855 const auto calleeExpressionType = resolveExpressionType(callee, ResolveOwnerType);
1856
1857 if (!calleeExpressionType || !calleeExpressionType->semanticScope
1858 || !calleeExpressionType->name || calleeExpressionType->type != MethodIdentifier) {
1859 return {};
1860 }
1861
1862 const auto methods =
1863 calleeExpressionType->semanticScope->methods(*calleeExpressionType->name);
1864 if (methods.isEmpty())
1865 return {};
1866
1867 const auto returnType = methods.front().returnType();
1868 return ExpressionType{ {}, returnType, NotAnIdentifier };
1869 }
1870 default: {
1871 qCDebug(QQmlLSUtilsLog) << "Type" << item.internalKindStr()
1872 << "is unimplemented in QQmlLSUtils::resolveExpressionType";
1873 return {};
1874 }
1875 }
1876 Q_UNREACHABLE();
1877}
1878
1879DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocation &location)
1880{
1881 // QQmlJS::SourceLocation starts counting at 1 but the utils and the LSP start at 0.
1882 auto items = QQmlLSUtils::itemsFromTextLocation(file, location.startLine - 1,
1883 location.startColumn - 1);
1884 switch (items.size()) {
1885 case 0:
1886 return {};
1887 case 1:
1888 return items.front().domItem;
1889 case 2: {
1890 // special case: because location points to the beginning of the type definition,
1891 // itemsFromTextLocation might also return the type on its left, in case it is directly
1892 // adjacent to it. In this case always take the right (=with the higher column-number)
1893 // item.
1894 auto &first = items.front();
1895 auto &second = items.back();
1896 Q_ASSERT_X(first.fileLocation->info().fullRegion.startLine
1897 == second.fileLocation->info().fullRegion.startLine,
1898 "QQmlLSUtils::findTypeDefinitionOf(DomItem)",
1899 "QQmlLSUtils::itemsFromTextLocation returned non-adjacent items.");
1900 if (first.fileLocation->info().fullRegion.startColumn
1901 > second.fileLocation->info().fullRegion.startColumn)
1902 return first.domItem;
1903 else
1904 return second.domItem;
1905 break;
1906 }
1907 default:
1908 qDebug() << "Found multiple candidates for type of scriptidentifierexpression";
1909 break;
1910 }
1911 return {};
1912}
1913
1914static std::optional<Location>
1915findEnumDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
1916{
1917 const DomItem enumeration = [&file, &location, &name]() -> DomItem {
1918 const DomItem enumerations = QQmlLSUtils::sourceLocationToDomItem(file, location)
1919 .qmlObject()
1920 .component()
1921 .field(Fields::enumerations);
1922 const QSet<QString> enumerationNames = enumerations.keys();
1923 for (const QString &enumName : enumerationNames) {
1924 const DomItem currentKey = enumerations.key(enumName).index(0);
1925 if (enumName == name)
1926 return currentKey;
1927 const DomItem values = currentKey.field(Fields::values);
1928 for (int i = 0, end = values.size(); i < end; ++i) {
1929 const DomItem currentValue = values.index(i);
1930 if (currentValue.field(Fields::name).value().toStringView() == name)
1931 return currentValue;
1932 }
1933 }
1934 return {};
1935 }();
1936
1937 auto fileLocation = FileLocations::treeOf(enumeration);
1938
1939 if (!fileLocation)
1940 return {};
1941
1942 auto regions = fileLocation->info().regions;
1943
1944 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1945 return Location::tryFrom(enumeration.canonicalFilePath(), *it, file);
1946 }
1947
1948 return {};
1949}
1950
1951static std::optional<Location>
1952findMethodDefinitionOf(const DomItem &file, QQmlJS::SourceLocation location, const QString &name)
1953{
1954 DomItem owner = QQmlLSUtils::sourceLocationToDomItem(file, location).qmlObject();
1955 DomItem method = owner.field(Fields::methods).key(name).index(0);
1956 auto fileLocation = FileLocations::treeOf(method);
1957 if (!fileLocation)
1958 return {};
1959
1960 auto regions = fileLocation->info().regions;
1961
1962 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1963 return Location::tryFrom(method.canonicalFilePath(), *it, file);
1964 }
1965
1966 return {};
1967}
1968
1969static std::optional<Location>
1970findPropertyDefinitionOf(const DomItem &file, QQmlJS::SourceLocation propertyDefinitionLocation,
1971 const QString &name)
1972{
1973 DomItem propertyOwner =
1974 QQmlLSUtils::sourceLocationToDomItem(file, propertyDefinitionLocation).qmlObject();
1975 DomItem propertyDefinition = propertyOwner.field(Fields::propertyDefs).key(name).index(0);
1976 auto fileLocation = FileLocations::treeOf(propertyDefinition);
1977 if (!fileLocation)
1978 return {};
1979
1980 auto regions = fileLocation->info().regions;
1981
1982 if (auto it = regions.constFind(IdentifierRegion); it != regions.constEnd()) {
1983 return Location::tryFrom(propertyDefinition.canonicalFilePath(), *it, file);
1984 }
1985
1986 return {};
1987}
1988
1989static QQmlJS::SourceLocation sourceLocationOrDefault(const QQmlJS::SourceLocation &location)
1990{
1991 return location.startLine == 0 ? QQmlJS::SourceLocation{ 0, 0, 1, 1 } : location;
1992}
1993
1994static std::optional<Location> createCppTypeLocation(const QQmlJSScope::ConstPtr &type,
1995 const QStringList &headerLocations,
1996 const QQmlJS::SourceLocation &location)
1997{
1998 const QString filePath = findFilePathFromFileName(headerLocations, type->filePath(), {});
1999 if (filePath.isEmpty()) {
2000 qCWarning(QQmlLSUtilsLog) << "Couldn't find the C++ file '%1'."_L1.arg(type->filePath());
2001 return {};
2002 }
2003
2004 const TextPosition endPosition{ static_cast<int>(location.startLine) + 1, 1 };
2005 return Location{ filePath, location, endPosition };
2006}
2007
2008static std::optional<Location> findDefinitionOfType(const QQmlJSScope::ConstPtr &scope,
2009 const DomItem &item,
2010 const QStringList &headerDirectories)
2011{
2012 if (!scope)
2013 return {};
2014 if (scope->isComposite())
2015 if (const auto result = Location::tryFrom(scope->filePath(), scope->sourceLocation(), item))
2016 return result;
2017 return createCppTypeLocation(scope, headerDirectories,
2018 sourceLocationOrDefault(scope->sourceLocation()));
2019}
2020
2021std::optional<Location> findDefinitionOf(const DomItem &item, const QStringList &headerDirectories)
2022{
2023 auto resolvedExpression = resolveExpressionType(item, ResolveOptions::ResolveOwnerType);
2024
2025 if (!resolvedExpression || !resolvedExpression->name
2026 || (!resolvedExpression->semanticScope
2027 && resolvedExpression->type != QualifiedModuleIdentifier)) {
2028 qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf: Type could not be resolved.";
2029 return {};
2030 }
2031
2032 switch (resolvedExpression->type) {
2033 case JavaScriptIdentifier: {
2034 const auto jsIdentifier =
2035 resolvedExpression->semanticScope->ownJSIdentifier(*resolvedExpression->name);
2036 if (!jsIdentifier)
2037 return {};
2038
2039 return Location::tryFrom(resolvedExpression->semanticScope->filePath(),
2040 jsIdentifier->location, item);
2041 }
2042
2044 case PropertyIdentifier: {
2045 if (!resolvedExpression->semanticScope->isComposite()) {
2046 return createCppTypeLocation(
2047 resolvedExpression->semanticScope, headerDirectories,
2048 resolvedExpression->semanticScope->property(*resolvedExpression->name)
2049 .sourceLocation());
2050 }
2051 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2052 const QQmlJS::SourceLocation ownerLocation =
2053 resolvedExpression->semanticScope->sourceLocation();
2054 return findPropertyDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name);
2055 }
2058 case SignalIdentifier:
2060 case MethodIdentifier: {
2061 if (!resolvedExpression->semanticScope->isComposite()) {
2062 const auto methods =
2063 resolvedExpression->semanticScope->methods(*resolvedExpression->name);
2064 if (methods.isEmpty())
2065 return {};
2066 return createCppTypeLocation(resolvedExpression->semanticScope, headerDirectories,
2067 methods.front().sourceLocation());
2068 }
2069 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2070 const QQmlJS::SourceLocation ownerLocation =
2071 resolvedExpression->semanticScope->sourceLocation();
2072 return findMethodDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name);
2073 }
2074 case QmlObjectIdIdentifier: {
2075 DomItem qmlObject = QQmlLSUtils::sourceLocationToDomItem(
2076 item.containingFile(), resolvedExpression->semanticScope->sourceLocation());
2077 // in the Dom, the id is saved in a QMultiHash inside the Component of an QmlObject.
2078 const DomItem domId = qmlObject.component()
2079 .field(Fields::ids)
2080 .key(*resolvedExpression->name)
2081 .index(0)
2082 .field(Fields::value);
2083 if (!domId) {
2084 qCDebug(QQmlLSUtilsLog)
2085 << "QmlComponent in Dom structure has no id, was it misconstructed?";
2086 return {};
2087 }
2088
2089 return Location::tryFrom(domId.canonicalFilePath(),
2090 FileLocations::treeOf(domId)->info().fullRegion, domId);
2091 }
2092 case AttachedTypeIdentifier:
2093 return findDefinitionOfType(resolvedExpression->semanticScope->attachedType(), item,
2094 headerDirectories);
2095 case AttachedTypeIdentifierInBindingTarget:
2096 return findDefinitionOfType(resolvedExpression->semanticScope->baseType(), item,
2097 headerDirectories);
2098 case QmlComponentIdentifier:
2099 case SingletonIdentifier:
2100 return findDefinitionOfType(resolvedExpression->semanticScope, item, headerDirectories);
2102 const DomItem imports = item.fileObject().field(Fields::imports);
2103 for (int i = 0; i < imports.indexes(); ++i) {
2104 if (imports[i][Fields::importId].value().toString() == resolvedExpression->name) {
2105 const auto fileLocations = FileLocations::treeOf(imports[i]);
2106 if (!fileLocations)
2107 continue;
2108
2109 return Location::tryFrom(item.canonicalFilePath(), fileLocations->info().regions[IdNameRegion], item);
2110 }
2111 }
2112 return {};
2113 }
2116 if (!resolvedExpression->semanticScope->isComposite()) {
2117 const auto enumerations = resolvedExpression->semanticScope->enumerations();
2118 for (const auto &enumeration : enumerations) {
2119 if (enumeration.hasKey(*resolvedExpression->name)
2120 || enumeration.name() == *resolvedExpression->name) {
2121 return createCppTypeLocation(
2122 resolvedExpression->semanticScope, headerDirectories,
2123 sourceLocationOrDefault(QQmlJS::SourceLocation::fromQSizeType(
2124 0, 0, enumeration.lineNumber(), 1)));
2125 }
2126 }
2127 return {};
2128 }
2129 const DomItem ownerFile = item.goToFile(resolvedExpression->semanticScope->filePath());
2130 const QQmlJS::SourceLocation ownerLocation =
2131 resolvedExpression->semanticScope->sourceLocation();
2132 return findEnumDefinitionOf(ownerFile, ownerLocation, *resolvedExpression->name);
2133 }
2134
2135 case LambdaMethodIdentifier:
2136 case NotAnIdentifier:
2137 qCDebug(QQmlLSUtilsLog) << "QQmlLSUtils::findDefinitionOf was not implemented for type"
2138 << resolvedExpression->type;
2139 return {};
2140 }
2141 Q_UNREACHABLE_RETURN({});
2142}
2143
2144static QQmlJSScope::ConstPtr propertyOwnerFrom(const QQmlJSScope::ConstPtr &type,
2145 const QString &name)
2146{
2147 Q_ASSERT(!name.isEmpty());
2148 Q_ASSERT(type);
2149
2150 QQmlJSScope::ConstPtr typeWithDefinition = type;
2151 while (typeWithDefinition && !typeWithDefinition->hasOwnProperty(name))
2152 typeWithDefinition = typeWithDefinition->baseType();
2153
2154 if (!typeWithDefinition) {
2155 qCDebug(QQmlLSUtilsLog)
2156 << "QQmlLSUtils::checkNameForRename cannot find property definition,"
2157 " ignoring.";
2158 }
2159
2160 return typeWithDefinition;
2161}
2162
2163static QQmlJSScope::ConstPtr methodOwnerFrom(const QQmlJSScope::ConstPtr &type,
2164 const QString &name)
2165{
2166 Q_ASSERT(!name.isEmpty());
2167 Q_ASSERT(type);
2168
2169 QQmlJSScope::ConstPtr typeWithDefinition = type;
2170 while (typeWithDefinition && !typeWithDefinition->hasOwnMethod(name))
2171 typeWithDefinition = typeWithDefinition->baseType();
2172
2173 if (!typeWithDefinition) {
2174 qCDebug(QQmlLSUtilsLog)
2175 << "QQmlLSUtils::checkNameForRename cannot find method definition,"
2176 " ignoring.";
2177 }
2178
2179 return typeWithDefinition;
2180}
2181
2183{
2184 switch (ownerType.type) {
2185 case PropertyIdentifier:
2186 return propertyOwnerFrom(ownerType.semanticScope, *ownerType.name);
2188 const auto propertyName =
2189 QQmlSignalNames::changedHandlerNameToPropertyName(*ownerType.name);
2190 return propertyOwnerFrom(ownerType.semanticScope, *propertyName);
2191 break;
2192 }
2194 const auto propertyName = QQmlSignalNames::changedSignalNameToPropertyName(*ownerType.name);
2195 return propertyOwnerFrom(ownerType.semanticScope, *propertyName);
2196 }
2197 case MethodIdentifier:
2198 case SignalIdentifier:
2199 return methodOwnerFrom(ownerType.semanticScope, *ownerType.name);
2201 const auto signalName = QQmlSignalNames::handlerNameToSignalName(*ownerType.name);
2202 return methodOwnerFrom(ownerType.semanticScope, *signalName);
2203 }
2215 case NotAnIdentifier:
2216 return ownerType.semanticScope;
2217 }
2218 return {};
2219}
2220
2221std::optional<ErrorMessage> checkNameForRename(const DomItem &item, const QString &dirtyNewName,
2222 const std::optional<ExpressionType> &ownerType)
2223{
2224 if (!ownerType) {
2225 if (const auto resolved = resolveExpressionType(item, ResolveOwnerType))
2226 return checkNameForRename(item, dirtyNewName, resolved);
2227 }
2228
2229 // general checks for ECMAscript identifiers
2230 if (!isValidEcmaScriptIdentifier(dirtyNewName))
2231 return ErrorMessage{ 0, u"Invalid EcmaScript identifier!"_s };
2232
2233 const auto userSemanticScope = item.nearestSemanticScope();
2234
2235 if (!ownerType || !userSemanticScope) {
2236 return ErrorMessage{ 0, u"Requested item cannot be renamed."_s };
2237 }
2238
2239 // type specific checks
2240 switch (ownerType->type) {
2242 if (!QQmlSignalNames::isChangedSignalName(dirtyNewName)) {
2243 return ErrorMessage{ 0, u"Invalid name for a property changed signal."_s };
2244 }
2245 break;
2246 }
2248 if (!QQmlSignalNames::isChangedHandlerName(dirtyNewName)) {
2249 return ErrorMessage{ 0, u"Invalid name for a property changed handler identifier."_s };
2250 }
2251 break;
2252 }
2254 if (!QQmlSignalNames::isHandlerName(dirtyNewName)) {
2255 return ErrorMessage{ 0, u"Invalid name for a signal handler identifier."_s };
2256 }
2257 break;
2258 }
2259 // TODO: any other specificities?
2261 if (dirtyNewName.front().isLetter() && !dirtyNewName.front().isLower()) {
2262 return ErrorMessage{ 0, u"Object id names cannot start with an upper case letter."_s };
2263 }
2264 break;
2266 case PropertyIdentifier:
2267 case SignalIdentifier:
2268 case MethodIdentifier:
2269 default:
2270 break;
2271 };
2272
2273 auto typeWithDefinition = expressionTypeWithDefinition(*ownerType);
2274
2275 if (!typeWithDefinition) {
2276 return ErrorMessage{
2277 0,
2278 u"Renaming has not been implemented for the requested item."_s,
2279 };
2280 }
2281
2282 // is it not defined in QML?
2283 if (!typeWithDefinition->isComposite()) {
2284 return ErrorMessage{ 0, u"Cannot rename items defined in non-QML files."_s };
2285 }
2286
2287 // is it defined in the current module?
2288 const QString moduleOfDefinition = ownerType->semanticScope->moduleName();
2289 const QString moduleOfCurrentItem = userSemanticScope->moduleName();
2290 if (moduleOfDefinition != moduleOfCurrentItem) {
2291 return ErrorMessage{
2292 0,
2293 u"Cannot rename items defined in the \"%1\" module from a usage in the \"%2\" module."_s
2294 .arg(moduleOfDefinition, moduleOfCurrentItem),
2295 };
2296 }
2297
2298 return {};
2299}
2300
2301static std::optional<QString> oldNameFrom(const DomItem &item)
2302{
2303 switch (item.internalKind()) {
2304 case DomType::ScriptIdentifierExpression: {
2305 return item.field(Fields::identifier).value().toString();
2306 }
2307 case DomType::ScriptVariableDeclarationEntry: {
2308 return item.field(Fields::identifier).value().toString();
2309 }
2310 case DomType::PropertyDefinition:
2311 case DomType::Binding:
2312 case DomType::MethodInfo: {
2313 return item.field(Fields::name).value().toString();
2314 }
2315 default:
2316 qCDebug(QQmlLSUtilsLog) << item.internalKindStr()
2317 << "was not implemented for QQmlLSUtils::renameUsagesOf";
2318 return std::nullopt;
2319 }
2320 Q_UNREACHABLE_RETURN(std::nullopt);
2321}
2322
2323static std::optional<QString> newNameFrom(const QString &dirtyNewName, IdentifierType alternative)
2324{
2325 // When renaming signal/property changed handlers and property changed signals:
2326 // Get the actual corresponding signal name (for signal handlers) or property name (for
2327 // property changed signal + handlers) that will be used for the renaming.
2328 switch (alternative) {
2330 return QQmlSignalNames::handlerNameToSignalName(dirtyNewName);
2331 }
2333 return QQmlSignalNames::changedHandlerNameToPropertyName(dirtyNewName);
2334 }
2336 return QQmlSignalNames::changedSignalNameToPropertyName(dirtyNewName);
2337 }
2338 case SignalIdentifier:
2339 case PropertyIdentifier:
2340 default:
2341 return std::nullopt;
2342 }
2343 Q_UNREACHABLE_RETURN(std::nullopt);
2344}
2345
2346/*!
2347\internal
2348\brief Rename the appearance of item to newName.
2349
2350Special cases:
2351\list
2352 \li Renaming a property changed signal or property changed handler does the same as renaming
2353 the underlying property, except that newName gets
2354 \list
2355 \li its "on"-prefix and "Changed"-suffix chopped of if item is a property changed handlers
2356 \li its "Changed"-suffix chopped of if item is a property changed signals
2357 \endlist
2358 \li Renaming a signal handler does the same as renaming a signal, but the "on"-prefix in newName
2359 is chopped of.
2360
2361 All of the chopping operations are done using the static helpers from QQmlSignalNames.
2362\endlist
2363*/
2364RenameUsages renameUsagesOf(const DomItem &item, const QString &dirtyNewName,
2365 const std::optional<ExpressionType> &targetType)
2366{
2367 RenameUsages result;
2368 const Usages locations = findUsagesOf(item);
2369 if (locations.isEmpty())
2370 return result;
2371
2372 auto oldName = oldNameFrom(item);
2373 if (!oldName)
2374 return result;
2375
2376 QQmlJSScope::ConstPtr semanticScope;
2377 if (targetType) {
2378 semanticScope = targetType->semanticScope;
2379 } else if (const auto resolved =
2380 QQmlLSUtils::resolveExpressionType(item, ResolveOptions::ResolveOwnerType)) {
2381 semanticScope = resolved->semanticScope;
2382 } else {
2383 return result;
2384 }
2385
2386 QString newName;
2387 if (const auto resolved = resolveNameInQmlScope(*oldName, semanticScope)) {
2388 newName = newNameFrom(dirtyNewName, resolved->type).value_or(dirtyNewName);
2389 oldName = resolved->name;
2390 } else {
2391 newName = dirtyNewName;
2392 }
2393
2394 const qsizetype oldNameLength = oldName->length();
2395 const qsizetype oldHandlerNameLength =
2396 QQmlSignalNames::signalNameToHandlerName(*oldName).length();
2397 const qsizetype oldChangedSignalNameLength =
2398 QQmlSignalNames::propertyNameToChangedSignalName(*oldName).length();
2399 const qsizetype oldChangedHandlerNameLength =
2400 QQmlSignalNames::propertyNameToChangedHandlerName(*oldName).length();
2401
2402 const QString newHandlerName = QQmlSignalNames::signalNameToHandlerName(newName);
2403 const QString newChangedSignalName = QQmlSignalNames::propertyNameToChangedSignalName(newName);
2404 const QString newChangedHandlerName =
2405 QQmlSignalNames::propertyNameToChangedHandlerName(newName);
2406
2407 // set the new name at the found usages, but add "on"-prefix and "Changed"-suffix if needed
2408 for (const auto &location : locations.usagesInFile()) {
2409 const qsizetype currentLength = location.sourceLocation().length;
2410 Edit edit;
2411 edit.location = location;
2412 if (oldNameLength == currentLength) {
2413 // normal case, nothing to do
2414 edit.replacement = newName;
2415
2416 } else if (oldHandlerNameLength == currentLength) {
2417 // signal handler location
2418 edit.replacement = newHandlerName;
2419
2420 } else if (oldChangedSignalNameLength == currentLength) {
2421 // property changed signal location
2422 edit.replacement = newChangedSignalName;
2423
2424 } else if (oldChangedHandlerNameLength == currentLength) {
2425 // property changed handler location
2426 edit.replacement = newChangedHandlerName;
2427
2428 } else {
2429 qCDebug(QQmlLSUtilsLog) << "Found usage with wrong identifier length, ignoring...";
2430 continue;
2431 }
2432 result.appendRename(edit);
2433 }
2434
2435 for (const auto &filename : locations.usagesInFilename()) {
2436 // assumption: we only rename files ending in .qml or .ui.qml in qmlls
2437 QString extension;
2438 if (filename.endsWith(u".ui.qml"_s))
2439 extension = u".ui.qml"_s;
2440 else if (filename.endsWith(u".qml"_s))
2441 extension = u".qml"_s;
2442 else
2443 continue;
2444
2445 QFileInfo info(filename);
2446 // do not rename the file if it has a custom type name in the qmldir
2447 if (!info.isFile() || info.baseName() != oldName)
2448 continue;
2449
2450 const QString newFilename =
2451 QDir::cleanPath(filename + "/.."_L1) + '/'_L1 + newName + extension;
2452 result.appendRename({ filename, newFilename });
2453 }
2454
2455 return result;
2456}
2457
2458std::optional<Location> Location::tryFrom(const QString &fileName,
2459 const QQmlJS::SourceLocation &sourceLocation,
2460 const QQmlJS::Dom::DomItem &someItem)
2461{
2462 auto qmlFile = someItem.goToFile(fileName).ownerAs<QQmlJS::Dom::QmlFile>();
2463 if (!qmlFile) {
2464 qDebug() << "Could not find file" << fileName << "in the dom!";
2465 return {};
2466 }
2467 return Location{ fileName, sourceLocation,
2468 textRowAndColumnFrom(qmlFile->code(), sourceLocation.end()) };
2469}
2470
2471Location Location::from(const QString &fileName, const QQmlJS::SourceLocation &sourceLocation, const QString &code)
2472{
2473 return Location{ fileName, sourceLocation, textRowAndColumnFrom(code, sourceLocation.end()) };
2474}
2475
2476Location Location::from(const QString &fileName, const QString &code, qsizetype startLine,
2477 qsizetype startCharacter, qsizetype length)
2478{
2479 const auto offset = QQmlLSUtils::textOffsetFrom(code, startLine - 1, startCharacter - 1);
2480 return from(fileName,
2481 QQmlJS::SourceLocation::fromQSizeType(offset, length, startLine, startCharacter),
2482 code);
2483}
2484
2485Edit Edit::from(const QString &fileName, const QString &code, qsizetype startLine,
2486 qsizetype startCharacter, qsizetype length, const QString &newName)
2487{
2488 Edit rename;
2489 rename.location = Location::from(fileName, code, startLine, startCharacter, length);
2490 rename.replacement = newName;
2491 return rename;
2492}
2493
2494bool isValidEcmaScriptIdentifier(QStringView identifier)
2495{
2496 QQmlJS::Lexer lexer(nullptr);
2497 lexer.setCode(identifier.toString(), 0);
2498 const int token = lexer.lex();
2499 if (token != static_cast<int>(QQmlJS::Lexer::T_IDENTIFIER))
2500 return false;
2501 // make sure there is nothing following the lexed identifier
2502 const int eofToken = lexer.lex();
2503 return eofToken == static_cast<int>(QQmlJS::Lexer::EOF_SYMBOL);
2504}
2505
2506
2507/*!
2508\internal
2509Returns the name of the cmake program along with the arguments needed to build the
2510qmltyperegistration. This command generates the .qmltypes, qmldir and .qrc files required for qmlls
2511to provide correct information on C++ defined QML elements.
2512
2513We assume here that CMake is available in the path. This should be the case for linux and macOS by
2514default.
2515For windows, having CMake in the path is not too unrealistic, for example,
2516https://doc.qt.io/qt-6/windows-building.html#step-2-install-build-requirements claims that you need
2517to have CMake in your path to build Qt. So a developer machine running qmlls has a high chance of
2518having CMake in their path, if CMake is installed and used.
2519*/
2521{
2522 const std::pair<QString, QStringList> result{
2523 u"cmake"_s, { u"--build"_s, path, u"-t"_s, u"all_qmltyperegistrations"_s }
2524 };
2525 return result;
2526}
2527
2529{
2530 std::sort(m_usagesInFile.begin(), m_usagesInFile.end());
2531 std::sort(m_usagesInFilename.begin(), m_usagesInFilename.end());
2532}
2533
2534bool Usages::isEmpty() const
2535{
2536 return m_usagesInFilename.isEmpty() && m_usagesInFile.isEmpty();
2537}
2538
2539Usages::Usages(const QList<Location> &usageInFile, const QList<QString> &usageInFilename)
2541{
2542 std::sort(m_usagesInFile.begin(), m_usagesInFile.end());
2543 std::sort(m_usagesInFilename.begin(), m_usagesInFilename.end());
2544}
2545
2546RenameUsages::RenameUsages(const QList<Edit> &renamesInFile,
2547 const QList<FileRename> &renamesInFilename)
2549{
2550 std::sort(m_renamesInFile.begin(), m_renamesInFile.end());
2551 std::sort(m_renamesInFilename.begin(), m_renamesInFilename.end());
2552}
2553
2555static QStringList findFilePathsFromFileNamesImpl(const QStringList &rootDirs,
2556 const QStringList &fileNamesToSearch,
2557 SearchOption option,
2558 const QSet<QString> &ignoredFilePaths)
2559{
2560 if (fileNamesToSearch.isEmpty() || rootDirs.isEmpty())
2561 return {};
2562
2563 const qint64 maxFilesToSearch =
2564 qEnvironmentVariableIntegerValue("QMLLS_MAX_FILES_TO_SEARCH")
2565 .value_or(20'000); // 20'000 entries need around one second on my machine
2566 qint64 visitedItems = 0;
2567
2568 QStringList result;
2569 std::queue<QString> toVisit;
2570 for (const QString &rootDir : rootDirs)
2571 toVisit.push(rootDir);
2572
2573 while (!toVisit.empty()) {
2574 const QString current = toVisit.front();
2575 toVisit.pop();
2576
2577 for (const auto &entry : QDirListing{ current, QDirListing::IteratorFlag::ExcludeOther }) {
2578 // timeout to avoid qmlls from recursing too much, in case rootDir is the filesystem
2579 // root for example
2580 if (++visitedItems > maxFilesToSearch) {
2581 qInfo(QQmlLSUtilsLog).noquote().nospace()
2582 << "Aborting search for \"" << fileNamesToSearch.join("\", \""_L1)
2583 << "\" inside \"" << rootDirs.join("\", \""_L1)
2584 << "\" after reaching QMLLS_MAX_FILES_TO_SEARCH (currently set to "
2585 << maxFilesToSearch
2586 << "). Set the environment variable \"QMLLS_MAX_FILES_TO_SEARCH\" to a "
2587 "higher value to spend more time on searching.";
2588 return result;
2589 }
2590
2591 if (entry.isDir()) {
2592 toVisit.push(entry.filePath());
2593 continue;
2594 }
2595 Q_ASSERT(entry.isFile());
2596 if (!fileNamesToSearch.contains(entry.fileName()))
2597 continue;
2598
2599 if (ignoredFilePaths.contains(entry.absoluteFilePath()))
2600 continue;
2601
2602 if (option == FindFirst)
2603 return { entry.absoluteFilePath() };
2604
2605 result << entry.absoluteFilePath();
2606 }
2607 }
2608
2609 return result;
2610}
2611
2612QStringList findFilePathsFromFileNames(const QString &rootDir, const QStringList &fileNamesToSearch,
2613 const QSet<QString> &ignoredFilePaths)
2614{
2615 return findFilePathsFromFileNamesImpl({ rootDir }, fileNamesToSearch, FindAll,
2616 ignoredFilePaths);
2617}
2618
2619QString findFilePathFromFileName(const QStringList &rootDirs, const QString &fileNameToSearch,
2620 const QSet<QString> &ignoredFilePaths)
2621{
2622 const QStringList result = findFilePathsFromFileNamesImpl(rootDirs, { fileNameToSearch },
2623 FindFirst, ignoredFilePaths);
2624 return result.isEmpty() ? QString{} : result.front();
2625}
2626
2627} // namespace QQmlLSUtils
2628
2629QT_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 QQmlJSScope::ConstPtr &type, const QStringList &headerLocations, const QQmlJS::SourceLocation &location)
static std::optional< ExpressionType > resolveFieldMemberExpressionType(const DomItem &item, ResolveOptions options)
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< Location > locationFromDomItem(const DomItem &item, FileLocationRegion region)
static std::optional< ExpressionType > propertyBindingFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QString &name, ResolveOptions options, QQmlJSTypeResolver *resolverForIds)
std::optional< Location > findDefinitionOf(const DomItem &item, const QStringList &headerDirectories)
DomItem baseObject(const DomItem &qmlObject)
QByteArray lspUriToQmlUrl(const QByteArray &uri)
DomItem sourceLocationToDomItem(const DomItem &file, const QQmlJS::SourceLocation &location)
static std::optional< Location > findDefinitionOfType(const QQmlJSScope::ConstPtr &scope, const DomItem &item, const QStringList &headerDirectories)
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).
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)
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)
std::pair< QString, QStringList > cmakeBuildCommand(const QString &path)
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)
static DomItem findJSIdentifierDefinition(const DomItem &item, const QString &name)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")