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

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