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 default:
716 qCDebug(semanticTokens)
717 << "Script Expressions with kind" << item.internalKind() << "not implemented";
718 return;
719 }
720 Q_UNREACHABLE_RETURN();
721}
722
723/*!
724\internal
725\brief Returns multiple source locations for a given raw comment
726
727Needed by semantic highlighting of comments. LSP clients usually don't support multiline
728tokens. In QML, we can have multiline tokens like string literals and comments.
729This method generates multiple source locations of sub-elements of token split by a newline
730delimiter.
731*/
732QList<QQmlJS::SourceLocation>
733HighlightingUtils::sourceLocationsFromMultiLineToken(QStringView stringLiteral,
734 const QQmlJS::SourceLocation &locationInDocument)
735{
736 auto lineBreakLength = qsizetype(std::char_traits<char>::length(s: "\n"));
737 const auto lineLengths = [&lineBreakLength](QStringView literal) {
738 std::vector<qsizetype> lineLengths;
739 qsizetype startIndex = 0;
740 qsizetype pos = literal.indexOf(c: u'\n');
741 while (pos != -1) {
742 // TODO: QTBUG-106813
743 // Since a document could be opened in normalized form
744 // we can't use platform dependent newline handling here.
745 // Thus, we check manually if the literal contains \r so that we split
746 // the literal at the correct offset.
747 if (pos - 1 > 0 && literal[pos - 1] == u'\r') {
748 // Handle Windows line endings
749 lineBreakLength = qsizetype(std::char_traits<char>::length(s: "\r\n"));
750 // Move pos to the index of '\r'
751 pos = pos - 1;
752 }
753 lineLengths.push_back(x: pos - startIndex);
754 // Advance the lookup index, so it won't find the same index.
755 startIndex = pos + lineBreakLength;
756 pos = literal.indexOf(c: '\n'_L1, from: startIndex);
757 }
758 // Push the last line
759 if (startIndex < literal.length()) {
760 lineLengths.push_back(x: literal.length() - startIndex);
761 }
762 return lineLengths;
763 };
764
765 QList<QQmlJS::SourceLocation> result;
766 // First token location should start from the "stringLiteral"'s
767 // location in the qml document.
768 QQmlJS::SourceLocation lineLoc = locationInDocument;
769 for (const auto lineLength : lineLengths(stringLiteral)) {
770 lineLoc.length = lineLength;
771 result.push_back(t: lineLoc);
772
773 // update for the next line
774 lineLoc.offset += lineLoc.length + lineBreakLength;
775 ++lineLoc.startLine;
776 lineLoc.startColumn = 1;
777 }
778 return result;
779}
780
781QList<int> HighlightingUtils::encodeSemanticTokens(Highlights &highlights)
782{
783 QList<int> result;
784 const auto highlightingTokens = highlights.highlights();
785 constexpr auto tokenEncodingLength = 5;
786 result.reserve(asize: tokenEncodingLength * highlightingTokens.size());
787
788 int prevLine = 0;
789 int prevColumn = 0;
790
791 std::for_each(first: highlightingTokens.constBegin(), last: highlightingTokens.constEnd(), f: [&](const auto &token) {
792 Q_ASSERT(token.startLine >= prevLine);
793 if (token.startLine != prevLine)
794 prevColumn = 0;
795 result.emplace_back(token.startLine - prevLine);
796 result.emplace_back(token.startColumn - prevColumn);
797 result.emplace_back(token.length);
798 result.emplace_back(token.tokenType);
799 result.emplace_back(token.tokenModifier);
800 prevLine = token.startLine;
801 prevColumn = token.startColumn;
802 });
803
804 return result;
805}
806
807/*!
808\internal
809Computes the modifier value. Modifier is read as binary value in the protocol. The location
810of the bits set are interpreted as the indices of the tokenModifiers list registered by the
811server. Then, the client modifies the highlighting of the token.
812
813tokenModifiersList: ["declaration", definition, readonly, static ,,,]
814
815To set "definition" and "readonly", we need to send 0b00000110
816*/
817void HighlightingUtils::addModifier(SemanticTokenModifiers modifier, int *baseModifier)
818{
819 if (!baseModifier)
820 return;
821 *baseModifier |= (1 << int(modifier));
822}
823
824/*!
825\internal
826Check if the ranges overlap by ensuring that one range starts before the other ends
827*/
828bool HighlightingUtils::rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc,
829 const HighlightsRange &r)
830{
831 int startOffsetItem = int(loc.offset);
832 int endOffsetItem = startOffsetItem + int(loc.length);
833 return (startOffsetItem <= r.endOffset) && (r.startOffset <= endOffsetItem);
834}
835
836/*
837\internal
838Increments the resultID by one.
839*/
840void HighlightingUtils::updateResultID(QByteArray &resultID)
841{
842 int length = resultID.length();
843 for (int i = length - 1; i >= 0; --i) {
844 if (resultID[i] == '9') {
845 resultID[i] = '0';
846 } else {
847 resultID[i] = resultID[i] + 1;
848 return;
849 }
850 }
851 resultID.prepend(c: '1');
852}
853
854/*
855\internal
856A utility method that computes the difference of two list. The first argument is the encoded token data
857of the file before edited. The second argument is the encoded token data after the file is edited. Returns
858a list of SemanticTokensEdit as expected by the protocol.
859*/
860QList<SemanticTokensEdit> HighlightingUtils::computeDiff(const QList<int> &oldData, const QList<int> &newData)
861{
862 // Find the iterators pointing the first mismatch, from the start
863 const auto [oldStart, newStart] =
864 std::mismatch(first1: oldData.cbegin(), last1: oldData.cend(), first2: newData.cbegin(), last2: newData.cend());
865
866 // Find the iterators pointing the first mismatch, from the end
867 // but the iterators shouldn't pass over the start iterators found above.
868 const auto [r1, r2] = std::mismatch(first1: oldData.crbegin(), last1: std::make_reverse_iterator(i: oldStart),
869 first2: newData.crbegin(), last2: std::make_reverse_iterator(i: newStart));
870 const auto oldEnd = r1.base();
871 const auto newEnd = r2.base();
872
873 // no change
874 if (oldStart == oldEnd && newStart == newEnd)
875 return {};
876
877 SemanticTokensEdit edit;
878 edit.start = int(std::distance(first: newData.cbegin(), last: newStart));
879 edit.deleteCount = int(std::distance(first: oldStart, last: oldEnd));
880
881 if (newStart >= newData.cbegin() && newEnd <= newData.cend() && newStart < newEnd)
882 edit.data.emplace(args: newStart, args: newEnd);
883
884 return { std::move(edit) };
885}
886
887Highlights::Highlights(HighlightingMode mode)
888 : m_mapToProtocol(mode == HighlightingMode::QtCHighlighting ? mapToProtocolForQtCreator
889 : mapToProtocolDefault)
890{
891}
892
893void Highlights::addHighlight(const QQmlJS::SourceLocation &loc, QmlHighlightKind highlightKind,
894 QmlHighlightModifiers modifierKind)
895{
896 int tokenType = m_mapToProtocol(highlightKind);
897 int modifierType = fromQmlModifierKindToLspTokenType(highlightModifier: modifierKind);
898 return addHighlightImpl(loc, tokenType, tokenModifier: modifierType);
899}
900
901void Highlights::addHighlightImpl(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier)
902{
903 if (!loc.isValid()) {
904 qCDebug(semanticTokens) << "Invalid locations: Cannot add highlight to token";
905 return;
906 }
907
908 if (loc.length == 0)
909 return;
910
911 if (!m_highlights.contains(key: loc.offset))
912 m_highlights.insert(key: loc.offset, QT_PREPEND_NAMESPACE(Token)(loc, tokenType, tokenModifier));
913}
914
915QList<int> HighlightingUtils::collectTokens(const QQmlJS::Dom::DomItem &item,
916 const std::optional<HighlightsRange> &range,
917 HighlightingMode mode)
918{
919 using namespace QQmlJS::Dom;
920 Highlights highlights(mode);
921 HighlightingVisitor highlightDomElements(highlights, range);
922 // In QmlFile level, visitTree visits even FileLocations tree which takes quite a time to
923 // finish. HighlightingFilter is added to prevent unnecessary visits.
924 item.visitTree(basePath: Path(), visitor: highlightDomElements, options: VisitOption::Default, openingVisitor: emptyChildrenVisitor,
925 closingVisitor: emptyChildrenVisitor, filter: highlightingFilter());
926
927 return HighlightingUtils::encodeSemanticTokens(highlights);
928}
929
930QT_END_NAMESPACE
931

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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