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

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