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

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