Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlsemantictokens.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <qqmlsemantictokens_p.h>
6
7#include <QtQmlLS/private/qqmllsutils_p.h>
8#include <QtQmlDom/private/qqmldomscriptelements_p.h>
9#include <QtQmlDom/private/qqmldomfieldfilter_p.h>
10
11#include <QtLanguageServer/private/qlanguageserverprotocol_p.h>
12
14
15Q_LOGGING_CATEGORY(semanticTokens, "qt.languageserver.semanticTokens")
16
17using namespace QQmlJS::AST;
18using namespace QQmlJS::Dom;
19using namespace QLspSpecification;
20using namespace HighlightingUtils;
21
23{
24 switch (highlightKind) {
82 default:
84 }
85}
86
87static int mapToProtocolDefault(QmlHighlightKind highlightKind)
88{
89 switch (highlightKind) {
143 default:
145 }
146}
147
148/*!
149\internal
150\brief Further resolves the type of a JavaScriptIdentifier
151A global object can be in the object form or in the function form.
152For example, Date can be used as a constructor function (like new Date())
153or as a object (like Date.now()).
154*/
156 const QString &name)
157{
158 // Some objects are not constructable, they are always objects.
159 static QSet<QString> noConstructorObjects = { u"Math"_s, u"JSON"_s, u"Atomics"_s, u"Reflect"_s,
160 u"console"_s };
161 // if the method name is in the list of noConstructorObjects, then it is a global object. Do not
162 // perform further checks.
163 if (noConstructorObjects.contains(name))
165 // Check if the method is called with new, then it is a constructor function
166 if (item.directParent().internalKind() == DomType::ScriptNewMemberExpression) {
168 }
169 if (DomItem containingCallExpression = item.filterUp(
170 [](DomType k, const DomItem &) { return k == DomType::ScriptCallExpression; },
171 FilterUpOptions::ReturnOuter)) {
172 // Call expression
173 // if callee is binary expression, then the rightest part is the method name
174 const auto callee = containingCallExpression.field(Fields::callee);
175 if (callee.internalKind() == DomType::ScriptBinaryExpression) {
176 const auto right = callee.field(Fields::right);
177 if (right.internalKind() == DomType::ScriptIdentifierExpression
178 && right.field(Fields::identifier).value().toString() == name) {
180 } else {
182 }
183 } else {
185 }
186 }
187 return std::nullopt;
188}
189
190static int fromQmlModifierKindToLspTokenType(QmlHighlightModifiers highlightModifier)
191{
192 using namespace QLspSpecification;
193 using namespace HighlightingUtils;
194 int modifier = 0;
195
196 if (highlightModifier.testFlag(QmlHighlightModifier::QmlPropertyDefinition))
197 addModifier(SemanticTokenModifiers::Definition, &modifier);
198
199 if (highlightModifier.testFlag(QmlHighlightModifier::QmlDefaultProperty))
200 addModifier(SemanticTokenModifiers::DefaultLibrary, &modifier);
201
202 if (highlightModifier.testFlag(QmlHighlightModifier::QmlFinalProperty))
203 addModifier(SemanticTokenModifiers::Static, &modifier);
204
205 if (highlightModifier.testFlag(QmlHighlightModifier::QmlRequiredProperty))
206 addModifier(SemanticTokenModifiers::Abstract, &modifier);
207
208 if (highlightModifier.testFlag(QmlHighlightModifier::QmlReadonlyProperty))
209 addModifier(SemanticTokenModifiers::Readonly, &modifier);
210
211 return modifier;
212}
213
215{
216 QMultiMap<QString, QString> fieldFilterAdd{};
217 QMultiMap<QString, QString> fieldFilterRemove{
218 { QString(), Fields::propertyInfos.toString() },
219 { QString(), Fields::fileLocationsTree.toString() },
220 { QString(), Fields::importScope.toString() },
221 { QString(), Fields::defaultPropertyName.toString() },
222 { QString(), Fields::get.toString() },
223 };
224 return FieldFilter{ fieldFilterAdd, fieldFilterRemove };
225}
226
227HighlightingVisitor::HighlightingVisitor(const QQmlJS::Dom::DomItem &item,
228 const std::optional<HighlightsRange> &range,
229 HighlightingMode mode)
231{
232 item.visitTree(
233 Path(),
234 [this](const Path &path, const DomItem &item, bool b) {
235 return this->visitor(path, item, b);
236 },
237 VisitOption::Default | VisitOption::NoPath, emptyChildrenVisitor, emptyChildrenVisitor,
238 highlightingFilter());
239}
240
241bool HighlightingVisitor::visitor(Path, const DomItem &item, bool)
242{
243 if (m_range.has_value()) {
244 const auto fLocs = FileLocations::treeOf(item);
245 if (!fLocs)
246 return true;
247 const auto regions = fLocs->info().regions;
248 if (!HighlightingUtils::rangeOverlapsWithSourceLocation(regions[MainRegion],
249 m_range.value()))
250 return true;
251 }
252 switch (item.internalKind()) {
253 case DomType::Comment: {
254 highlightComment(item);
255 return true;
256 }
257 case DomType::Import: {
258 highlightImport(item);
259 return true;
260 }
261 case DomType::Binding: {
262 highlightBinding(item);
263 return true;
264 }
265 case DomType::Pragma: {
266 highlightPragma(item);
267 return true;
268 }
269 case DomType::EnumDecl: {
270 highlightEnumDecl(item);
271 return true;
272 }
273 case DomType::EnumItem: {
274 highlightEnumItem(item);
275 return true;
276 }
277 case DomType::QmlObject: {
278 highlightQmlObject(item);
279 return true;
280 }
281 case DomType::QmlComponent: {
282 highlightComponent(item);
283 return true;
284 }
285 case DomType::PropertyDefinition: {
286 highlightPropertyDefinition(item);
287 return true;
288 }
289 case DomType::MethodInfo: {
290 highlightMethod(item);
291 return true;
292 }
293 case DomType::ScriptLiteral: {
294 highlightScriptLiteral(item);
295 return true;
296 }
297 case DomType::ScriptCallExpression: {
298 highlightCallExpression(item);
299 return true;
300 }
301 case DomType::ScriptIdentifierExpression: {
302 highlightIdentifier(item);
303 return true;
304 }
305 default:
306 if (item.ownerAs<ScriptExpression>())
307 highlightScriptExpressions(item);
308 return true;
309 }
310 Q_UNREACHABLE_RETURN(false);
311}
312
313void HighlightingVisitor::highlightComment(const DomItem &item)
314{
315 const auto comment = item.as<Comment>();
316 Q_ASSERT(comment);
317 const auto locs = HighlightingUtils::sourceLocationsFromMultiLineToken(
318 comment->info().comment(), comment->info().sourceLocation());
319 for (const auto &loc : locs)
320 m_highlights.addHighlight(loc, QmlHighlightKind::Comment);
321}
322
323void HighlightingVisitor::highlightImport(const DomItem &item)
324{
325 const auto fLocs = FileLocations::treeOf(item);
326 if (!fLocs)
327 return;
328 const auto regions = fLocs->info().regions;
329 const auto import = item.as<Import>();
330 Q_ASSERT(import);
331 m_highlights.addHighlight(regions[ImportTokenRegion], QmlHighlightKind::QmlKeyword);
332 if (import->uri.isModule())
333 m_highlights.addHighlight(regions[ImportUriRegion], QmlHighlightKind::QmlImportId);
334 else
335 m_highlights.addHighlight(regions[ImportUriRegion], QmlHighlightKind::String);
336 if (regions.contains(VersionRegion))
337 m_highlights.addHighlight(regions[VersionRegion], QmlHighlightKind::Number);
338 if (regions.contains(AsTokenRegion)) {
339 m_highlights.addHighlight(regions[AsTokenRegion], QmlHighlightKind::QmlKeyword);
340 m_highlights.addHighlight(regions[IdNameRegion], QmlHighlightKind::QmlNamespace);
341 }
342}
343
344void HighlightingVisitor::highlightBinding(const DomItem &item)
345{
346 const auto binding = item.as<Binding>();
347 Q_ASSERT(binding);
348 const auto fLocs = FileLocations::treeOf(item);
349 if (!fLocs) {
350 qCDebug(semanticTokens) << "Can't find the locations for" << item.internalKind();
351 return;
352 }
353 const auto regions = fLocs->info().regions;
354 // If dotted name, then defer it to be handled in ScriptIdentifierExpression
355 if (binding->name().contains("."_L1))
356 return;
357
358 if (binding->bindingType() != BindingType::Normal) {
359 m_highlights.addHighlight(regions[OnTokenRegion], QmlHighlightKind::QmlKeyword);
360 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty);
361 return;
362 }
363
364 return m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty);
365}
366
367void HighlightingVisitor::highlightPragma(const DomItem &item)
368{
369 const auto fLocs = FileLocations::treeOf(item);
370 if (!fLocs)
371 return;
372 const auto regions = fLocs->info().regions;
373 m_highlights.addHighlight(regions[PragmaKeywordRegion], QmlHighlightKind::QmlKeyword);
374 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlPragmaName );
375 const auto pragma = item.as<Pragma>();
376 for (auto i = 0; i < pragma->values.size(); ++i) {
377 DomItem value = item.field(Fields::values).index(i);
378 const auto valueRegions = FileLocations::treeOf(value)->info().regions;
379 m_highlights.addHighlight(valueRegions[PragmaValuesRegion], QmlHighlightKind::QmlPragmaValue);
380 }
381 return;
382}
383
384void HighlightingVisitor::highlightEnumDecl(const DomItem &item)
385{
386 const auto fLocs = FileLocations::treeOf(item);
387 if (!fLocs)
388 return;
389 const auto regions = fLocs->info().regions;
390 m_highlights.addHighlight(regions[EnumKeywordRegion], QmlHighlightKind::QmlKeyword);
391 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlEnumName);
392}
393
394void HighlightingVisitor::highlightEnumItem(const DomItem &item)
395{
396 const auto fLocs = FileLocations::treeOf(item);
397 if (!fLocs)
398 return;
399 const auto regions = fLocs->info().regions;
400 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlEnumMember);
401 if (regions.contains(EnumValueRegion))
402 m_highlights.addHighlight(regions[EnumValueRegion], QmlHighlightKind::Number);
403}
404
405void HighlightingVisitor::highlightQmlObject(const DomItem &item)
406{
407 const auto qmlObject = item.as<QmlObject>();
408 Q_ASSERT(qmlObject);
409 const auto fLocs = FileLocations::treeOf(item);
410 if (!fLocs)
411 return;
412 const auto regions = fLocs->info().regions;
413 // Handle ids here
414 if (!qmlObject->idStr().isEmpty()) {
415 m_highlights.addHighlight(regions[IdTokenRegion], QmlHighlightKind::QmlProperty);
416 m_highlights.addHighlight(regions[IdNameRegion], QmlHighlightKind::QmlLocalId);
417 }
418 // If dotted name, then defer it to be handled in ScriptIdentifierExpression
419 if (qmlObject->name().contains("."_L1))
420 return;
421
422 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
423}
424
425void HighlightingVisitor::highlightComponent(const DomItem &item)
426{
427 const auto fLocs = FileLocations::treeOf(item);
428 if (!fLocs)
429 return;
430 const auto regions = fLocs->info().regions;
431 const auto componentKeywordIt = regions.constFind(ComponentKeywordRegion);
432 if (componentKeywordIt == regions.constEnd())
433 return; // not an inline component, no need for highlighting
434 m_highlights.addHighlight(*componentKeywordIt, QmlHighlightKind::QmlKeyword);
435 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
436}
437
438void HighlightingVisitor::highlightPropertyDefinition(const DomItem &item)
439{
440 const auto propertyDef = item.as<PropertyDefinition>();
441 Q_ASSERT(propertyDef);
442 const auto fLocs = FileLocations::treeOf(item);
443 if (!fLocs)
444 return;
445 const auto regions = fLocs->info().regions;
446 QmlHighlightModifiers modifier = QmlHighlightModifier::QmlPropertyDefinition;
447 if (propertyDef->isDefaultMember) {
449 m_highlights.addHighlight(regions[DefaultKeywordRegion], QmlHighlightKind::QmlKeyword);
450 }
451 if (propertyDef->isFinal) {
453 m_highlights.addHighlight(regions[FinalKeywordRegion], QmlHighlightKind::QmlKeyword);
454 }
455 if (propertyDef->isRequired) {
457 m_highlights.addHighlight(regions[RequiredKeywordRegion], QmlHighlightKind::QmlKeyword);
458 }
459 if (propertyDef->isReadonly) {
461 m_highlights.addHighlight(regions[ReadonlyKeywordRegion], QmlHighlightKind::QmlKeyword);
462 }
463 m_highlights.addHighlight(regions[PropertyKeywordRegion], QmlHighlightKind::QmlKeyword);
464 if (propertyDef->isAlias())
465 m_highlights.addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlKeyword);
466 else
467 m_highlights.addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
468
469 m_highlights.addHighlight(regions[TypeModifierRegion], QmlHighlightKind::QmlTypeModifier);
470 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty,
471 modifier);
472}
473
474void HighlightingVisitor::highlightMethod(const DomItem &item)
475{
476 const auto method = item.as<MethodInfo>();
477 Q_ASSERT(method);
478 const auto fLocs = FileLocations::treeOf(item);
479 if (!fLocs)
480 return;
481 const auto regions = fLocs->info().regions;
482 switch (method->methodType) {
483 case MethodInfo::Signal: {
484 m_highlights.addHighlight(regions[SignalKeywordRegion], QmlHighlightKind::QmlKeyword);
485 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
486 break;
487 }
488 case MethodInfo::Method: {
489 m_highlights.addHighlight(regions[FunctionKeywordRegion], QmlHighlightKind::QmlKeyword);
490 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
491 m_highlights.addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
492 break;
493 }
494 default:
495 Q_UNREACHABLE();
496 }
497
498 for (auto i = 0; i < method->parameters.size(); ++i) {
499 DomItem parameter = item.field(Fields::parameters).index(i);
500 const auto paramRegions = FileLocations::treeOf(parameter)->info().regions;
501 m_highlights.addHighlight(paramRegions[IdentifierRegion],
502 QmlHighlightKind::QmlMethodParameter);
503 m_highlights.addHighlight(paramRegions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
504 }
505 return;
506}
507
508void HighlightingVisitor::highlightScriptLiteral(const DomItem &item)
509{
510 const auto literal = item.as<ScriptElements::Literal>();
511 Q_ASSERT(literal);
512 const auto fLocs = FileLocations::treeOf(item);
513 if (!fLocs)
514 return;
515 const auto regions = fLocs->info().regions;
516 if (std::holds_alternative<QString>(literal->literalValue())) {
517 const auto file = item.containingFile().as<QmlFile>();
518 if (!file)
519 return;
520 const auto &code = file->engine()->code();
521 const auto offset = regions[MainRegion].offset;
522 const auto length = regions[MainRegion].length;
523 const QStringView literalCode = QStringView{code}.mid(offset, length);
524 const auto &locs = HighlightingUtils::sourceLocationsFromMultiLineToken(
525 literalCode, regions[MainRegion]);
526 for (const auto &loc : locs)
527 m_highlights.addHighlight(loc, QmlHighlightKind::String);
528 } else if (std::holds_alternative<double>(literal->literalValue()))
529 m_highlights.addHighlight(regions[MainRegion], QmlHighlightKind::Number);
530 else if (std::holds_alternative<bool>(literal->literalValue()))
531 m_highlights.addHighlight(regions[MainRegion], QmlHighlightKind::QmlKeyword);
532 else if (std::holds_alternative<std::nullptr_t>(literal->literalValue()))
533 m_highlights.addHighlight(regions[MainRegion], QmlHighlightKind::QmlKeyword);
534 else
535 qCWarning(semanticTokens) << "Invalid literal variant";
536}
537
538void HighlightingVisitor::highlightIdentifier(const DomItem &item)
539{
540 using namespace QLspSpecification;
541 const auto id = item.as<ScriptElements::IdentifierExpression>();
542 Q_ASSERT(id);
543 const auto loc = id->mainRegionLocation();
544 // Many of the scriptIdentifiers expressions are already handled by
545 // other cases. In those cases, if the location offset is already in the list
546 // we don't need to perform expensive resolveExpressionType operation.
547 if (m_highlights.tokens().contains(loc.offset))
548 return;
549
550 // If the item is a field member base, we need to resolve the expression type
551 // If the item is a field member access, we don't need to resolve the expression type
552 // because it is already resolved in the first element.
553 if (QQmlLSUtils::isFieldMemberAccess(item))
554 highlightFieldMemberAccess(item, loc);
555 else
556 highlightBySemanticAnalysis(item, loc);
557}
558
559void HighlightingVisitor::highlightCallExpression(const DomItem &item)
560{
561 const auto highlight = [this](const DomItem &item) {
562 if (item.internalKind() == DomType::ScriptIdentifierExpression) {
563 const auto id = item.as<ScriptElements::IdentifierExpression>();
564 Q_ASSERT(id);
565 const auto loc = id->mainRegionLocation();
566 m_highlights.addHighlight(loc, QmlHighlightKind::QmlMethod);
567 }
568 };
569
570 if (item.internalKind() == DomType::ScriptCallExpression) {
571 // If the item is a call expression, we need to highlight the callee.
572 const auto callee = item.field(Fields::callee);
573 if (callee.internalKind() == DomType::ScriptIdentifierExpression) {
574 highlight(callee);
575 return;
576 } else if (callee.internalKind() == DomType::ScriptBinaryExpression) {
577 // If the callee is a binary expression, we need to highlight the right part.
578 const auto right = callee.field(Fields::right);
579 if (right.internalKind() == DomType::ScriptIdentifierExpression)
580 highlight(right);
581 return;
582 }
583 }
584}
585
586void HighlightingVisitor::highlightFieldMemberAccess(const DomItem &item,
587 QQmlJS::SourceLocation loc)
588{
589 // enum fields and qualified module identifiers are not just fields. Do semantic analysis if
590 // the identifier name is an uppercase string.
591 const auto name = item.field(Fields::identifier).value().toString();
592 if (!name.isEmpty() && name.at(0).category() == QChar::Letter_Uppercase) {
593 // maybe the identifier is an attached type or enum members, use semantic analysis to figure
594 // out.
595 return highlightBySemanticAnalysis(item, loc);
596 }
597 // Check if the name is a method
598 const auto expression =
599 QQmlLSUtils::resolveExpressionType(item, QQmlLSUtils::ResolveOptions::ResolveOwnerType);
600
601 if (!expression) {
602 m_highlights.addHighlight(loc, QmlHighlightKind::Field);
603 return;
604 }
605
606 if (expression->type == QQmlLSUtils::MethodIdentifier
607 || expression->type == QQmlLSUtils::LambdaMethodIdentifier) {
608 m_highlights.addHighlight(loc, QmlHighlightKind::QmlMethod);
609 return;
610 } else {
611 return m_highlights.addHighlight(loc, QmlHighlightKind::Field);
612 }
613}
614
615void HighlightingVisitor::highlightBySemanticAnalysis(const DomItem &item, QQmlJS::SourceLocation loc)
616{
617 const auto expression = QQmlLSUtils::resolveExpressionType(
618 item, QQmlLSUtils::ResolveOptions::ResolveOwnerType);
619
620 if (!expression) {
621 m_highlights.addHighlight(loc, QmlHighlightKind::Unknown);
622 return;
623 }
624 switch (expression->type) {
625 case QQmlLSUtils::QmlComponentIdentifier:
626 m_highlights.addHighlight(loc, QmlHighlightKind::QmlType);
627 return;
628 case QQmlLSUtils::JavaScriptIdentifier: {
630 QmlHighlightModifiers modifier = QmlHighlightModifier::None;
631 if (const auto scope = expression->semanticScope) {
632 if (const auto jsIdentifier = scope->jsIdentifier(*expression->name)) {
633 if (jsIdentifier->kind == QQmlJSScope::JavaScriptIdentifier::Parameter)
635 if (jsIdentifier->isConst) {
637 }
638 m_highlights.addHighlight(loc, tokenType, modifier);
639 return;
640 }
641 }
642 if (const auto name = expression->name) {
643 if (const auto highlightKind = resolveJsGlobalObjectKind(item, *name))
644 return m_highlights.addHighlight(loc, *highlightKind);
645 }
646 return;
647 }
648 case QQmlLSUtils::PropertyIdentifier: {
649 if (const auto scope = expression->semanticScope) {
651 if (scope == item.qmlObject().semanticScope()) {
653 } else if (scope == item.rootQmlObject(GoTo::MostLikely).semanticScope()) {
655 } else {
657 }
658 const auto property = scope->property(expression->name.value());
659 QmlHighlightModifiers modifier = QmlHighlightModifier::None;
660 if (!property.isWritable())
662 m_highlights.addHighlight(loc, tokenType, modifier);
663 }
664 return;
665 }
666 case QQmlLSUtils::PropertyChangedSignalIdentifier:
667 m_highlights.addHighlight(loc, QmlHighlightKind::QmlSignal);
668 return;
669 case QQmlLSUtils::PropertyChangedHandlerIdentifier:
670 m_highlights.addHighlight(loc, QmlHighlightKind::QmlSignalHandler);
671 return;
672 case QQmlLSUtils::SignalIdentifier:
673 m_highlights.addHighlight(loc, QmlHighlightKind::QmlSignal);
674 return;
675 case QQmlLSUtils::SignalHandlerIdentifier:
676 m_highlights.addHighlight(loc, QmlHighlightKind::QmlSignalHandler);
677 return;
678 case QQmlLSUtils::MethodIdentifier:
679 m_highlights.addHighlight(loc, QmlHighlightKind::QmlMethod);
680 return;
681 case QQmlLSUtils::QmlObjectIdIdentifier: {
682 const auto qmlfile = item.fileObject().as<QmlFile>();
683 if (!qmlfile) {
684 m_highlights.addHighlight(loc, QmlHighlightKind::Unknown);
685 return;
686 }
687 const auto resolver = qmlfile->typeResolver();
688 if (!resolver) {
689 m_highlights.addHighlight(loc, QmlHighlightKind::Unknown);
690 return;
691 }
692 const auto &objects = resolver->objectsById();
693 if (expression->name.has_value()) {
694 const auto &name = expression->name.value();
695 const auto boundName =
696 objects.id(expression->semanticScope, item.qmlObject().semanticScope());
697 if (!boundName.isEmpty() && name == boundName) {
698 // If the name is the same as the bound name, then it is a local id.
699 m_highlights.addHighlight(loc, QmlHighlightKind::QmlLocalId);
700 return;
701 } else {
702 m_highlights.addHighlight(loc, QmlHighlightKind::QmlExternalId);
703 return;
704 }
705 } else {
706 m_highlights.addHighlight(loc, QmlHighlightKind::QmlExternalId);
707 return;
708 }
709 }
710 case QQmlLSUtils::SingletonIdentifier:
711 m_highlights.addHighlight(loc, QmlHighlightKind::QmlType);
712 return;
713 case QQmlLSUtils::EnumeratorIdentifier:
714 m_highlights.addHighlight(loc, QmlHighlightKind::QmlEnumName);
715 return;
716 case QQmlLSUtils::EnumeratorValueIdentifier:
717 m_highlights.addHighlight(loc, QmlHighlightKind::QmlEnumMember);
718 return;
719 case QQmlLSUtils::AttachedTypeIdentifier:
720 case QQmlLSUtils::AttachedTypeIdentifierInBindingTarget:
721 m_highlights.addHighlight(loc, QmlHighlightKind::QmlType);
722 return;
723 case QQmlLSUtils::GroupedPropertyIdentifier:
724 m_highlights.addHighlight(loc, QmlHighlightKind::QmlProperty);
725 return;
726 case QQmlLSUtils::QualifiedModuleIdentifier:
727 m_highlights.addHighlight(loc, QmlHighlightKind::QmlNamespace);
728 return;
729 default:
730 qCWarning(semanticTokens)
731 << QString::fromLatin1("Semantic token for %1 has not been implemented yet")
732 .arg(int(expression->type));
733 }
734}
735
736void HighlightingVisitor::highlightScriptExpressions(const DomItem &item)
737{
738 const auto fLocs = FileLocations::treeOf(item);
739 if (!fLocs)
740 return;
741 const auto regions = fLocs->info().regions;
742 switch (item.internalKind()) {
743 case DomType::ScriptLiteral:
744 highlightScriptLiteral(item);
745 return;
746 case DomType::ScriptForStatement:
747 m_highlights.addHighlight(regions[ForKeywordRegion], QmlHighlightKind::QmlKeyword);
748 m_highlights.addHighlight(regions[TypeIdentifierRegion],
749 QmlHighlightKind::QmlKeyword);
750 return;
751
752 case DomType::ScriptVariableDeclaration: {
753 m_highlights.addHighlight(regions[TypeIdentifierRegion],
754 QmlHighlightKind::QmlKeyword);
755 return;
756 }
757 case DomType::ScriptReturnStatement:
758 m_highlights.addHighlight(regions[ReturnKeywordRegion], QmlHighlightKind::QmlKeyword);
759 return;
760 case DomType::ScriptCaseClause:
761 m_highlights.addHighlight(regions[CaseKeywordRegion], QmlHighlightKind::QmlKeyword);
762 return;
763 case DomType::ScriptDefaultClause:
764 m_highlights.addHighlight(regions[DefaultKeywordRegion], QmlHighlightKind::QmlKeyword);
765 return;
766 case DomType::ScriptSwitchStatement:
767 m_highlights.addHighlight(regions[SwitchKeywordRegion], QmlHighlightKind::QmlKeyword);
768 return;
769 case DomType::ScriptWhileStatement:
770 m_highlights.addHighlight(regions[WhileKeywordRegion], QmlHighlightKind::QmlKeyword);
771 return;
772 case DomType::ScriptDoWhileStatement:
773 m_highlights.addHighlight(regions[DoKeywordRegion], QmlHighlightKind::QmlKeyword);
774 m_highlights.addHighlight(regions[WhileKeywordRegion], QmlHighlightKind::QmlKeyword);
775 return;
776 case DomType::ScriptTryCatchStatement:
777 m_highlights.addHighlight(regions[TryKeywordRegion], QmlHighlightKind::QmlKeyword);
778 m_highlights.addHighlight(regions[CatchKeywordRegion], QmlHighlightKind::QmlKeyword);
779 m_highlights.addHighlight(regions[FinallyKeywordRegion], QmlHighlightKind::QmlKeyword);
780 return;
781 case DomType::ScriptForEachStatement:
782 m_highlights.addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlKeyword);
783 m_highlights.addHighlight(regions[ForKeywordRegion], QmlHighlightKind::QmlKeyword);
784 m_highlights.addHighlight(regions[InOfTokenRegion], QmlHighlightKind::QmlKeyword);
785 return;
786 case DomType::ScriptThrowStatement:
787 m_highlights.addHighlight(regions[ThrowKeywordRegion], QmlHighlightKind::QmlKeyword);
788 return;
789 case DomType::ScriptBreakStatement:
790 m_highlights.addHighlight(regions[BreakKeywordRegion], QmlHighlightKind::QmlKeyword);
791 return;
792 case DomType::ScriptContinueStatement:
793 m_highlights.addHighlight(regions[ContinueKeywordRegion], QmlHighlightKind::QmlKeyword);
794 return;
795 case DomType::ScriptIfStatement:
796 m_highlights.addHighlight(regions[IfKeywordRegion], QmlHighlightKind::QmlKeyword);
797 m_highlights.addHighlight(regions[ElseKeywordRegion], QmlHighlightKind::QmlKeyword);
798 return;
799 case DomType::ScriptLabelledStatement:
800 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::JsLabel);
801 return;
802 case DomType::ScriptConditionalExpression:
803 m_highlights.addHighlight(regions[QuestionMarkTokenRegion], QmlHighlightKind::Operator);
804 m_highlights.addHighlight(regions[ColonTokenRegion], QmlHighlightKind::Operator);
805 return;
806 case DomType::ScriptUnaryExpression:
807 case DomType::ScriptPostExpression:
808 m_highlights.addHighlight(regions[OperatorTokenRegion], QmlHighlightKind::Operator);
809 return;
810 case DomType::ScriptType:
811 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
812 m_highlights.addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
813 return;
814 case DomType::ScriptFunctionExpression: {
815 m_highlights.addHighlight(regions[FunctionKeywordRegion], QmlHighlightKind::QmlKeyword);
816 m_highlights.addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
817 return;
818 }
819 case DomType::ScriptYieldExpression:
820 m_highlights.addHighlight(regions[YieldKeywordRegion], QmlHighlightKind::QmlKeyword);
821 return;
822 case DomType::ScriptThisExpression:
823 m_highlights.addHighlight(regions[ThisKeywordRegion], QmlHighlightKind::QmlKeyword);
824 return;
825 case DomType::ScriptSuperLiteral:
826 m_highlights.addHighlight(regions[SuperKeywordRegion], QmlHighlightKind::QmlKeyword);
827 return;
828 case DomType::ScriptNewMemberExpression:
829 case DomType::ScriptNewExpression:
830 m_highlights.addHighlight(regions[NewKeywordRegion], QmlHighlightKind::QmlKeyword);
831 return;
832 case DomType::ScriptTemplateExpressionPart:
833 m_highlights.addHighlight(regions[DollarLeftBraceTokenRegion], QmlHighlightKind::Operator);
834 visitor(Path(), item.field(Fields::expression), false);
835 m_highlights.addHighlight(regions[RightBraceRegion], QmlHighlightKind::Operator);
836 return;
837 case DomType::ScriptTemplateLiteral:
838 m_highlights.addHighlight(regions[LeftBacktickTokenRegion], QmlHighlightKind::String);
839 m_highlights.addHighlight(regions[RightBacktickTokenRegion], QmlHighlightKind::String);
840 return;
841 case DomType::ScriptTemplateStringPart: {
842 // handle multiline case
843 QString code = item.field(Fields::value).value().toString();
844 const auto &locs = HighlightingUtils::sourceLocationsFromMultiLineToken(
845 code, regions[MainRegion]);
846 for (const auto &loc : locs)
847 m_highlights.addHighlight(loc, QmlHighlightKind::String);
848 return;
849 }
850 default:
851 qCDebug(semanticTokens) << "Script Expressions with kind" << item.internalKind()
852 << "not implemented";
853 }
854}
855
856/*!
857\internal
858\brief Returns multiple source locations for a given raw comment
859
860Needed by semantic highlighting of comments. LSP clients usually don't support multiline
861tokens. In QML, we can have multiline tokens like string literals and comments.
862This method generates multiple source locations of sub-elements of token split by a newline
863delimiter.
864*/
867 const QQmlJS::SourceLocation &locationInDocument)
868{
869 auto lineBreakLength = qsizetype(std::char_traits<char>::length("\n"));
870 const auto lineLengths = [&lineBreakLength](QStringView literal) {
871 std::vector<qsizetype> lineLengths;
872 qsizetype startIndex = 0;
873 qsizetype pos = literal.indexOf(u'\n');
874 while (pos != -1) {
875 // TODO: QTBUG-106813
876 // Since a document could be opened in normalized form
877 // we can't use platform dependent newline handling here.
878 // Thus, we check manually if the literal contains \r so that we split
879 // the literal at the correct offset.
880 if (pos - 1 > 0 && literal[pos - 1] == u'\r') {
881 // Handle Windows line endings
882 lineBreakLength = qsizetype(std::char_traits<char>::length("\r\n"));
883 // Move pos to the index of '\r'
884 pos = pos - 1;
885 }
886 lineLengths.push_back(pos - startIndex);
887 // Advance the lookup index, so it won't find the same index.
888 startIndex = pos + lineBreakLength;
889 pos = literal.indexOf('\n'_L1, startIndex);
890 }
891 // Push the last line
892 if (startIndex < literal.length()) {
893 lineLengths.push_back(literal.length() - startIndex);
894 }
895 return lineLengths;
896 };
897
898 QList<QQmlJS::SourceLocation> result;
899 // First token location should start from the "stringLiteral"'s
900 // location in the qml document.
901 QQmlJS::SourceLocation lineLoc = locationInDocument;
902 for (const auto lineLength : lineLengths(stringLiteral)) {
903 lineLoc.length = lineLength;
904 result.push_back(lineLoc);
905
906 // update for the next line
907 lineLoc.offset += lineLoc.length + lineBreakLength;
908 ++lineLoc.startLine;
909 lineLoc.startColumn = 1;
910 }
911 return result;
912}
913
915{
916 QList<int> result;
917 const auto highlightingTokens = highlights.tokens();
918 constexpr auto tokenEncodingLength = 5;
919 result.reserve(tokenEncodingLength * highlightingTokens.size());
920
921 int prevLine = 0;
922 int prevColumn = 0;
923
924 std::for_each(highlightingTokens.constBegin(), highlightingTokens.constEnd(), [&](const auto &token) {
925 Q_ASSERT(token.startLine >= prevLine);
926 if (token.startLine != prevLine)
927 prevColumn = 0;
928 result.emplace_back(token.startLine - prevLine);
929 result.emplace_back(token.startColumn - prevColumn);
930 result.emplace_back(token.length);
931 result.emplace_back(token.tokenType);
932 result.emplace_back(token.tokenModifier);
933 prevLine = token.startLine;
934 prevColumn = token.startColumn;
935 });
936
937 return result;
938}
939
940/*!
941\internal
942Computes the modifier value. Modifier is read as binary value in the protocol. The location
943of the bits set are interpreted as the indices of the tokenModifiers list registered by the
944server. Then, the client modifies the highlighting of the token.
945
946tokenModifiersList: ["declaration", definition, readonly, static ,,,]
947
948To set "definition" and "readonly", we need to send 0b00000110
949*/
950void HighlightingUtils::addModifier(SemanticTokenModifiers modifier, int *baseModifier)
951{
952 if (!baseModifier)
953 return;
954 *baseModifier |= (1 << int(modifier));
955}
956
957/*!
958\internal
959Check if the ranges overlap by ensuring that one range starts before the other ends
960*/
961bool HighlightingUtils::rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc,
962 const HighlightsRange &r)
963{
964 int startOffsetItem = int(loc.offset);
965 int endOffsetItem = startOffsetItem + int(loc.length);
966 return (startOffsetItem <= r.endOffset) && (r.startOffset <= endOffsetItem);
967}
968
969/*
970\internal
971Increments the resultID by one.
972*/
973void HighlightingUtils::updateResultID(QByteArray &resultID)
974{
975 int length = resultID.length();
976 for (int i = length - 1; i >= 0; --i) {
977 if (resultID[i] == '9') {
978 resultID[i] = '0';
979 } else {
980 resultID[i] = resultID[i] + 1;
981 return;
982 }
983 }
984 resultID.prepend('1');
985}
986
987/*
988\internal
989A utility method that computes the difference of two list. The first argument is the encoded token data
990of the file before edited. The second argument is the encoded token data after the file is edited. Returns
991a list of SemanticTokensEdit as expected by the protocol.
992*/
993QList<SemanticTokensEdit> HighlightingUtils::computeDiff(const QList<int> &oldData, const QList<int> &newData)
994{
995 // Find the iterators pointing the first mismatch, from the start
996 const auto [oldStart, newStart] =
997 std::mismatch(oldData.cbegin(), oldData.cend(), newData.cbegin(), newData.cend());
998
999 // Find the iterators pointing the first mismatch, from the end
1000 // but the iterators shouldn't pass over the start iterators found above.
1001 const auto [r1, r2] = std::mismatch(oldData.crbegin(), std::make_reverse_iterator(oldStart),
1002 newData.crbegin(), std::make_reverse_iterator(newStart));
1003 const auto oldEnd = r1.base();
1004 const auto newEnd = r2.base();
1005
1006 // no change
1007 if (oldStart == oldEnd && newStart == newEnd)
1008 return {};
1009
1010 SemanticTokensEdit edit;
1011 edit.start = int(std::distance(newData.cbegin(), newStart));
1012 edit.deleteCount = int(std::distance(oldStart, oldEnd));
1013
1014 if (newStart >= newData.cbegin() && newEnd <= newData.cend() && newStart < newEnd)
1015 edit.data.emplace(newStart, newEnd);
1016
1017 return { std::move(edit) };
1018}
1019
1025
1026void Highlights::addHighlight(const QQmlJS::SourceLocation &loc, QmlHighlightKind highlightKind,
1027 QmlHighlightModifiers modifierKind)
1028{
1029 int tokenType = m_mapToProtocol(highlightKind);
1030 int modifierType = fromQmlModifierKindToLspTokenType(modifierKind);
1031 return addHighlightImpl(loc, tokenType, modifierType);
1032}
1033
1034void Highlights::addHighlightImpl(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier)
1035{
1036 if (!loc.isValid()) {
1037 qCDebug(semanticTokens) << "Invalid locations: Cannot add highlight to token";
1038 return;
1039 }
1040
1041 if (loc.length == 0)
1042 return;
1043
1044 if (!m_highlights.contains(loc.offset))
1045 m_highlights.insert(loc.offset, QT_PREPEND_NAMESPACE(Token)(loc, tokenType, tokenModifier));
1046}
1047
1048Highlights HighlightingUtils::visitTokens(const QQmlJS::Dom::DomItem &item,
1049 const std::optional<HighlightsRange> &range,
1050 HighlightingMode mode)
1051{
1052 using namespace QQmlJS::Dom;
1053 HighlightingVisitor highlightDomElements(item, range, mode);
1054 return highlightDomElements.highlights();
1055}
1056
1057QList<int> HighlightingUtils::collectTokens(const QQmlJS::Dom::DomItem &item,
1058 const std::optional<HighlightsRange> &range,
1059 HighlightingMode mode)
1060{
1061 Highlights highlights = visitTokens(item, range, mode);
1062 return HighlightingUtils::encodeSemanticTokens(highlights);
1063}
1064QT_END_NAMESPACE
HighlightingVisitor(const QQmlJS::Dom::DomItem &item, const std::optional< HighlightsRange > &range, HighlightingUtils::HighlightingMode mode=HighlightingUtils::HighlightingMode::Default)
Highlights(HighlightingUtils::HighlightingMode mode=HighlightingUtils::HighlightingMode::Default)
void addHighlight(const QQmlJS::SourceLocation &loc, HighlightingUtils::QmlHighlightKind, HighlightingUtils::QmlHighlightModifiers=HighlightingUtils::QmlHighlightModifier::None)
QList< QQmlJS::SourceLocation > sourceLocationsFromMultiLineToken(QStringView code, const QQmlJS::SourceLocation &tokenLocation)
Returns multiple source locations for a given raw comment.
QList< int > encodeSemanticTokens(Highlights &highlights)
void updateResultID(QByteArray &resultID)
QList< QLspSpecification::SemanticTokensEdit > computeDiff(const QList< int > &, const QList< int > &)
QList< int > collectTokens(const QQmlJS::Dom::DomItem &item, const std::optional< HighlightsRange > &range, HighlightingMode mode=HighlightingMode::Default)
Highlights visitTokens(const QQmlJS::Dom::DomItem &item, const std::optional< HighlightsRange > &range, HighlightingMode mode=HighlightingMode::Default)
bool rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc, const HighlightsRange &r)
void addModifier(QLspSpecification::SemanticTokenModifiers modifier, int *baseModifier)
static FieldFilter highlightingFilter()
static int mapToProtocolForQtCreator(QmlHighlightKind highlightKind)
static std::optional< QmlHighlightKind > resolveJsGlobalObjectKind(const DomItem &item, const QString &name)
Further resolves the type of a JavaScriptIdentifier A global object can be in the object form or in t...
static int fromQmlModifierKindToLspTokenType(QmlHighlightModifiers highlightModifier)
static int mapToProtocolDefault(QmlHighlightKind highlightKind)