| 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 |  | 
| 12 | QT_BEGIN_NAMESPACE | 
| 13 |  | 
| 14 | Q_LOGGING_CATEGORY(semanticTokens, "qt.languageserver.semanticTokens" ) | 
| 15 |  | 
| 16 | using namespace QQmlJS::AST; | 
| 17 | using namespace QQmlJS::Dom; | 
| 18 | using namespace QLspSpecification; | 
| 19 | using namespace HighlightingUtils; | 
| 20 |  | 
| 21 | static 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 |  | 
| 83 | static 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 | 
| 145 | A global object can be in the object form or in the function form. | 
| 146 | For example, Date can be used as a constructor function (like new Date()) | 
| 147 | or as a object (like Date.now()). | 
| 148 | */ | 
| 149 | static 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 |  | 
| 184 | static 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 |  | 
| 205 | static 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 |  | 
| 218 | HighlightingVisitor::HighlightingVisitor(Highlights &highlights, | 
| 219 |                                          const std::optional<HighlightsRange> &range) | 
| 220 |     : m_highlights(highlights), m_range(range) | 
| 221 | { | 
| 222 | } | 
| 223 |  | 
| 224 | bool 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 |  | 
| 292 | void HighlightingVisitor::(const DomItem &item) | 
| 293 | { | 
| 294 |     const auto  = 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 |  | 
| 302 | void 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 |  | 
| 323 | void 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 |  | 
| 346 | void 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 |  | 
| 363 | void 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 |  | 
| 373 | void 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 |  | 
| 384 | void 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 |  | 
| 404 | void 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 |  | 
| 414 | void 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 |  | 
| 446 | void 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 |  | 
| 480 | void 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 |  | 
| 510 | void 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 |  | 
| 525 | void 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 |  | 
| 619 | void 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 |  | 
| 743 | Needed by semantic highlighting of comments. LSP clients usually don't support multiline | 
| 744 | tokens. In QML, we can have multiline tokens like string literals and comments. | 
| 745 | This method generates multiple source locations of sub-elements of token split by a newline | 
| 746 | delimiter. | 
| 747 | */ | 
| 748 | QList<QQmlJS::SourceLocation> | 
| 749 | HighlightingUtils::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 |  | 
| 797 | QList<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 | 
| 825 | Computes the modifier value. Modifier is read as binary value in the protocol. The location | 
| 826 | of the bits set are interpreted as the indices of the tokenModifiers list registered by the | 
| 827 | server. Then, the client modifies the highlighting of the token. | 
| 828 |  | 
| 829 | tokenModifiersList: ["declaration", definition, readonly, static ,,,] | 
| 830 |  | 
| 831 | To set "definition" and "readonly", we need to send 0b00000110 | 
| 832 | */ | 
| 833 | void HighlightingUtils::addModifier(SemanticTokenModifiers modifier, int *baseModifier) | 
| 834 | { | 
| 835 |     if (!baseModifier) | 
| 836 |         return; | 
| 837 |     *baseModifier |= (1 << int(modifier)); | 
| 838 | } | 
| 839 |  | 
| 840 | /*! | 
| 841 | \internal | 
| 842 | Check if the ranges overlap by ensuring that one range starts before the other ends | 
| 843 | */ | 
| 844 | bool 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 | 
| 854 | Increments the resultID by one. | 
| 855 | */ | 
| 856 | void 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 | 
| 872 | A utility method that computes the difference of two list. The first argument is the encoded token data | 
| 873 | of the file before edited. The second argument is the encoded token data after the file is edited. Returns | 
| 874 | a list of SemanticTokensEdit as expected by the protocol. | 
| 875 | */ | 
| 876 | QList<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 |  | 
| 903 | Highlights::Highlights(HighlightingMode mode) | 
| 904 |     : m_mapToProtocol(mode == HighlightingMode::QtCHighlighting ? mapToProtocolForQtCreator | 
| 905 |                                                                 : mapToProtocolDefault) | 
| 906 | { | 
| 907 | } | 
| 908 |  | 
| 909 | void 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 |  | 
| 917 | void 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 |  | 
| 931 | QList<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 |  | 
| 946 | QT_END_NAMESPACE | 
| 947 |  |