| 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 | #ifndef QQMLSEMANTICTOKENS_P_H |
| 5 | #define QQMLSEMANTICTOKENS_P_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | // |
| 17 | |
| 18 | #include <QtLanguageServer/private/qlanguageserverspec_p.h> |
| 19 | #include <QtQmlDom/private/qqmldomitem_p.h> |
| 20 | #include <QtCore/qlist.h> |
| 21 | #include <QtCore/qmap.h> |
| 22 | |
| 23 | QT_BEGIN_NAMESPACE |
| 24 | |
| 25 | Q_DECLARE_LOGGING_CATEGORY(semanticTokens) |
| 26 | |
| 27 | namespace HighlightingUtils { |
| 28 | Q_NAMESPACE |
| 29 | |
| 30 | // Protocol agnostic highlighting kinds |
| 31 | // Use this enum while visiting dom tree to define the highlighting kinds for the semantic tokens |
| 32 | // Then map it to the protocol specific token types and modifiers |
| 33 | // This can be as much as detailed as needed |
| 34 | enum class QmlHighlightKind { |
| 35 | QmlKeyword, // Qml keyword |
| 36 | QmlType, // Qml type name |
| 37 | QmlImportId, // Qml import module name |
| 38 | QmlNamespace, // Qml module namespace, i.e import QtQuick as Namespace |
| 39 | QmlLocalId, // Object id within the same file |
| 40 | QmlExternalId, // Object id defined in another file. [UNUSED FOR NOW] |
| 41 | QmlProperty, // Qml property. For now used for all kind of properties |
| 42 | QmlScopeObjectProperty, // Qml property defined in the current scope |
| 43 | QmlRootObjectProperty, // Qml property defined in the parent scopes |
| 44 | QmlExternalObjectProperty, // Qml property defined in the root object of another file |
| 45 | QmlMethod, |
| 46 | QmlMethodParameter, |
| 47 | QmlSignal, |
| 48 | QmlSignalHandler, |
| 49 | QmlEnumName, // Enum type name |
| 50 | QmlEnumMember, // Enum field names |
| 51 | QmlPragmaName, // Qml pragma name |
| 52 | QmlPragmaValue, // Qml pragma value |
| 53 | QmlTypeModifier, // list<QtObject>, list is the modifier, QtObject is the type |
| 54 | JsImport, // Js imported name |
| 55 | JsGlobalVar, // Js global variable or objects |
| 56 | JsGlobalMethod, // Js global method |
| 57 | JsScopeVar, // Js variable defined in the current scope |
| 58 | JsLabel, // js label |
| 59 | Number, |
| 60 | String, |
| 61 | , |
| 62 | Operator, |
| 63 | Field, // Used for the field names in the property chains, |
| 64 | Unknown, // Used for the unknown tokens |
| 65 | }; |
| 66 | |
| 67 | enum class QmlHighlightModifier { |
| 68 | None = 0, |
| 69 | QmlPropertyDefinition = 1 << 0, |
| 70 | QmlDefaultProperty = 1 << 1, |
| 71 | QmlFinalProperty = 1 << 2, |
| 72 | QmlRequiredProperty = 1 << 3, |
| 73 | QmlReadonlyProperty = 1 << 4, |
| 74 | }; |
| 75 | Q_DECLARE_FLAGS(QmlHighlightModifiers, QmlHighlightModifier) |
| 76 | Q_DECLARE_OPERATORS_FOR_FLAGS(QmlHighlightModifiers) |
| 77 | |
| 78 | enum class HighlightingMode { Default, QtCHighlighting }; |
| 79 | |
| 80 | // Protocol specific token types |
| 81 | // The values in this enum are converted to relevant strings and sent to the client as server |
| 82 | // capabilities The convention is that the first letter in the enum value is decapitalized and the |
| 83 | // rest is unchanged i.e Namespace -> "namespace" This is handled in enumToByteArray() helper |
| 84 | // function. |
| 85 | enum class SemanticTokenProtocolTypes { |
| 86 | // Subset of the QLspSpefication::SemanticTokenTypes enum |
| 87 | // We register only the token types used in the qml semantic highlighting |
| 88 | Namespace, |
| 89 | Type, |
| 90 | Enum, |
| 91 | Parameter, |
| 92 | Variable, |
| 93 | Property, |
| 94 | EnumMember, |
| 95 | Method, |
| 96 | Keyword, |
| 97 | , |
| 98 | String, |
| 99 | Number, |
| 100 | Regexp, |
| 101 | Operator, |
| 102 | Decorator, |
| 103 | |
| 104 | // Additional token types for the extended semantic highlighting |
| 105 | QmlLocalId, // object id within the same file |
| 106 | QmlExternalId, // object id defined in another file |
| 107 | QmlRootObjectProperty, // qml property defined in the parent scopes |
| 108 | QmlScopeObjectProperty, // qml property defined in the current scope |
| 109 | QmlExternalObjectProperty, // qml property defined in the root object of another file |
| 110 | JsScopeVar, // js variable defined in the current file |
| 111 | JsImportVar, // js import name that is imported in the qml file |
| 112 | JsGlobalVar, // js global variables |
| 113 | QmlStateName, // name of a qml state |
| 114 | Field, // used for the field names in the property chains |
| 115 | Unknown, // used for the unknown contexts |
| 116 | }; |
| 117 | Q_ENUM_NS(SemanticTokenProtocolTypes) |
| 118 | |
| 119 | } // namespace HighlightingUtils |
| 120 | |
| 121 | // Represents a semantic highlighting token |
| 122 | // startLine and startColumn are 0-based as in LSP spec. |
| 123 | struct Token |
| 124 | { |
| 125 | Token() = default; |
| 126 | Token(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier = 0) |
| 127 | : offset(loc.offset), |
| 128 | length(loc.length), |
| 129 | startLine(loc.startLine - 1), |
| 130 | startColumn(loc.startColumn - 1), |
| 131 | tokenType(tokenType), |
| 132 | tokenModifier(tokenModifier) |
| 133 | { |
| 134 | } |
| 135 | |
| 136 | inline friend bool operator<(const Token &lhs, const Token &rhs) |
| 137 | { |
| 138 | return lhs.offset < rhs.offset; |
| 139 | } |
| 140 | |
| 141 | inline friend bool operator==(const Token &lhs, const Token &rhs) |
| 142 | { |
| 143 | return lhs.offset == rhs.offset && lhs.length == rhs.length |
| 144 | && lhs.startLine == rhs.startLine && lhs.startColumn == rhs.startColumn |
| 145 | && lhs.tokenType == rhs.tokenType && lhs.tokenModifier == rhs.tokenModifier; |
| 146 | } |
| 147 | |
| 148 | int offset; |
| 149 | int length; |
| 150 | int startLine; |
| 151 | int startColumn; |
| 152 | int tokenType; |
| 153 | int tokenModifier; |
| 154 | }; |
| 155 | |
| 156 | using HighlightsContainer = QMap<int, QT_PREPEND_NAMESPACE(Token)>; |
| 157 | |
| 158 | /*! |
| 159 | \internal |
| 160 | Offsets start from zero. |
| 161 | */ |
| 162 | struct HighlightsRange |
| 163 | { |
| 164 | int startOffset; |
| 165 | int endOffset; |
| 166 | }; |
| 167 | |
| 168 | class Highlights |
| 169 | { |
| 170 | public: |
| 171 | using QmlHighlightKindToLspKind = int (*)(HighlightingUtils::QmlHighlightKind); |
| 172 | Highlights(HighlightingUtils::HighlightingMode mode = HighlightingUtils::HighlightingMode::Default); |
| 173 | void addHighlight(const QQmlJS::SourceLocation &loc, HighlightingUtils::QmlHighlightKind, |
| 174 | HighlightingUtils::QmlHighlightModifiers = |
| 175 | HighlightingUtils::QmlHighlightModifier::None); |
| 176 | HighlightsContainer &tokens() { return m_highlights; } |
| 177 | const HighlightsContainer &tokens() const { return m_highlights; } |
| 178 | |
| 179 | private: |
| 180 | void addHighlightImpl(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier = 0); |
| 181 | HighlightsContainer m_highlights; |
| 182 | QmlHighlightKindToLspKind m_mapToProtocol; |
| 183 | }; |
| 184 | |
| 185 | namespace HighlightingUtils |
| 186 | { |
| 187 | QList<int> encodeSemanticTokens(Highlights &highlights); |
| 188 | QList<QQmlJS::SourceLocation> |
| 189 | sourceLocationsFromMultiLineToken(QStringView code, |
| 190 | const QQmlJS::SourceLocation &tokenLocation); |
| 191 | void addModifier(QLspSpecification::SemanticTokenModifiers modifier, int *baseModifier); |
| 192 | bool rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc, const HighlightsRange &r); |
| 193 | QList<QLspSpecification::SemanticTokensEdit> computeDiff(const QList<int> &, const QList<int> &); |
| 194 | void updateResultID(QByteArray &resultID); |
| 195 | QList<int> collectTokens(const QQmlJS::Dom::DomItem &item, |
| 196 | const std::optional<HighlightsRange> &range, |
| 197 | HighlightingMode mode = HighlightingMode::Default); |
| 198 | Highlights visitTokens(const QQmlJS::Dom::DomItem &item, |
| 199 | const std::optional<HighlightsRange> &range, |
| 200 | HighlightingMode mode = HighlightingMode::Default); |
| 201 | } // namespace HighlightingUtils |
| 202 | |
| 203 | class HighlightingVisitor |
| 204 | { |
| 205 | public: |
| 206 | HighlightingVisitor(const QQmlJS::Dom::DomItem &item, |
| 207 | const std::optional<HighlightsRange> &range, |
| 208 | HighlightingUtils::HighlightingMode mode = |
| 209 | HighlightingUtils::HighlightingMode::Default); |
| 210 | const Highlights &hightights() const { return m_highlights; } |
| 211 | Highlights &highlights() { return m_highlights; } |
| 212 | |
| 213 | private: |
| 214 | bool visitor(QQmlJS::Dom::Path, const QQmlJS::Dom::DomItem &item, bool); |
| 215 | void (const QQmlJS::Dom::DomItem &item); |
| 216 | void highlightImport(const QQmlJS::Dom::DomItem &item); |
| 217 | void highlightBinding(const QQmlJS::Dom::DomItem &item); |
| 218 | void highlightPragma(const QQmlJS::Dom::DomItem &item); |
| 219 | void highlightEnumItem(const QQmlJS::Dom::DomItem &item); |
| 220 | void highlightEnumDecl(const QQmlJS::Dom::DomItem &item); |
| 221 | void highlightQmlObject(const QQmlJS::Dom::DomItem &item); |
| 222 | void highlightComponent(const QQmlJS::Dom::DomItem &item); |
| 223 | void highlightPropertyDefinition(const QQmlJS::Dom::DomItem &item); |
| 224 | void highlightMethod(const QQmlJS::Dom::DomItem &item); |
| 225 | void highlightScriptLiteral(const QQmlJS::Dom::DomItem &item); |
| 226 | void highlightIdentifier(const QQmlJS::Dom::DomItem &item); |
| 227 | void highlightBySemanticAnalysis(const QQmlJS::Dom::DomItem &item, QQmlJS::SourceLocation loc); |
| 228 | void highlightScriptExpressions(const QQmlJS::Dom::DomItem &item); |
| 229 | void highlightCallExpression(const QQmlJS::Dom::DomItem &item); |
| 230 | void highlightFieldMemberAccess(const QQmlJS::Dom::DomItem &item, QQmlJS::SourceLocation loc); |
| 231 | |
| 232 | private: |
| 233 | Highlights m_highlights; |
| 234 | std::optional<HighlightsRange> m_range; |
| 235 | }; |
| 236 | |
| 237 | QT_END_NAMESPACE |
| 238 | |
| 239 | #endif // QQMLSEMANTICTOKENS_P_H |
| 240 | |