Skip to content

Commit 2c71dfa

Browse files
authored
feat: semantic highlighting support (#640)
* feat: add semantic token * fix: custom token and enhancment * fix: unit tests * fix: coverage * chore: change set * fix: no semantic for none xml view files
1 parent 0fe9a18 commit 2c71dfa

File tree

35 files changed

+1752
-292
lines changed

35 files changed

+1752
-292
lines changed

.changeset/curvy-worms-rhyme.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@ui5-language-assistant/vscode-ui5-language-assistant-bas-ext": patch
3+
"vscode-ui5-language-assistant": patch
4+
"@ui5-language-assistant/binding-parser": patch
5+
"@ui5-language-assistant/binding": patch
6+
---
7+
8+
add semantic highlighting support

packages/binding-parser/src/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export {
1010
isBindingExpression,
1111
isMetadataPath,
1212
isModel,
13-
isPropertyBindingInfo,
13+
isBindingAllowed,
14+
extractBindingSyntax,
1415
} from "./utils";
1516

1617
import {

packages/binding-parser/src/lexer/token.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const colon = createToken({
6161
});
6262
const stringValue = createToken({
6363
name: STRING_VALUE,
64-
pattern: /(?:'|"|'|")(?:.*?)(?:'|"|'|")/,
64+
pattern: /(?:'|"|'|")(\\'|\\"|.)*?(?:'|"|'|")/,
6565
});
6666

6767
const booleanValue = createToken({
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
export * as BindingParserTypes from "./binding-parser";
2+
3+
export interface ExtractBindingSyntax {
4+
startIndex: number;
5+
endIndex: number;
6+
expression: string;
7+
}

packages/binding-parser/src/utils/expression.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ExtractBindingSyntax } from "../types";
12
import type {
23
ParseResultErrors,
34
StructureValue,
@@ -92,7 +93,7 @@ export const isMetadataPath = (
9293
};
9394

9495
/**
95-
* An input is considered property binding syntax when
96+
* An input is considered as an allowed binding when
9697
*
9798
* a. is empty curly bracket e.g `{}` or `{ }`
9899
*
@@ -104,7 +105,7 @@ export const isMetadataPath = (
104105
*
105106
* e. is not OData path e.g {/path/to/...} or {path/to/...}
106107
*/
107-
export const isPropertyBindingInfo = (
108+
export const isBindingAllowed = (
108109
input: string,
109110
binding?: StructureValue,
110111
errors?: ParseResultErrors
@@ -146,3 +147,67 @@ export const isPropertyBindingInfo = (
146147
}
147148
return false;
148149
};
150+
151+
/**
152+
* Regular expression to extract binding syntax.
153+
*
154+
* Also handles escaping of '{' and '}'.
155+
*/
156+
// eslint-disable-next-line no-useless-escape
157+
const start = /(\\[\\\{\}])|(\{)/g;
158+
// eslint-disable-next-line no-useless-escape
159+
const end = /(\\[\\\{\}])|(\})/g;
160+
161+
export const extractBindingSyntax = (input: string): ExtractBindingSyntax[] => {
162+
const result: ExtractBindingSyntax[] = [];
163+
let startRegResult: RegExpExecArray | null;
164+
let endRegResult: RegExpExecArray | null;
165+
// resetting
166+
start.lastIndex = 0;
167+
let startIndex = 0;
168+
let lastIndex = 0;
169+
let endIndex = 0;
170+
const text = input;
171+
if (text.trim() === "") {
172+
return [{ startIndex, endIndex, expression: input }];
173+
}
174+
while ((startRegResult = start.exec(input)) !== null) {
175+
// scape special chars
176+
if (startRegResult[1]) {
177+
continue;
178+
}
179+
const startInput = input.slice(startRegResult.index);
180+
// collect all closing bracket(s)
181+
end.lastIndex = 0;
182+
while ((endRegResult = end.exec(startInput)) !== null) {
183+
// scape special chars
184+
if (endRegResult[1]) {
185+
break;
186+
}
187+
lastIndex = endRegResult.index;
188+
}
189+
if (lastIndex === startRegResult.index) {
190+
// missing closing bracket
191+
const expression = startInput.slice(0, input.length);
192+
result.push({
193+
startIndex: startRegResult.index,
194+
endIndex: input.length,
195+
expression,
196+
});
197+
input = startInput.slice(input.length);
198+
} else {
199+
const expression = startInput.slice(0, lastIndex + 1);
200+
startIndex = endIndex + startRegResult.index;
201+
endIndex = startIndex + lastIndex + 1;
202+
result.push({
203+
startIndex,
204+
endIndex,
205+
expression,
206+
});
207+
input = startInput.slice(lastIndex + 1);
208+
// resetting
209+
start.lastIndex = 0;
210+
}
211+
}
212+
return result;
213+
};

packages/binding-parser/src/utils/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export {
1010
isBindingExpression,
1111
isMetadataPath,
1212
isModel,
13-
isPropertyBindingInfo,
13+
isBindingAllowed,
14+
extractBindingSyntax,
1415
} from "./expression";
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"bindings": [
3+
{
4+
"leftCurly": {
5+
"type": "left-curly",
6+
"text": "{",
7+
"range": "[(0,0)..(0,1)]"
8+
},
9+
"elements": [
10+
{
11+
"key": {
12+
"type": "key",
13+
"text": "$filter",
14+
"range": "[(1,4)..(1,11)]",
15+
"originalText": "$filter"
16+
},
17+
"colon": {
18+
"type": "colon",
19+
"text": ":",
20+
"range": "[(1,12)..(1,13)]"
21+
},
22+
"value": {
23+
"type": "string-value",
24+
"text": "\"TravelStatus_code eq \\\"O\\\" and IsActiveEntity eq false or SiblingEntity/IsActiveEntity eq null\"",
25+
"range": "[(1,14)..(1,110)]"
26+
},
27+
"range": "[(1,4)..(1,110)]",
28+
"type": "structure-element"
29+
}
30+
],
31+
"rightCurly": {
32+
"type": "right-curly",
33+
"text": "}",
34+
"range": "[(2,0)..(2,1)]"
35+
},
36+
"range": "[(0,0)..(2,1)]",
37+
"commas": [],
38+
"type": "structure-value"
39+
}
40+
],
41+
"spaces": [
42+
{
43+
"type": "white-space",
44+
"text": "\n ",
45+
"range": "[(0,1)..(1,4)]"
46+
},
47+
{
48+
"type": "white-space",
49+
"text": " ",
50+
"range": "[(1,11)..(1,12)]"
51+
},
52+
{
53+
"type": "white-space",
54+
"text": " ",
55+
"range": "[(1,13)..(1,14)]"
56+
},
57+
{
58+
"type": "white-space",
59+
"text": "\n",
60+
"range": "[(1,110)..(1,111)]"
61+
}
62+
],
63+
"type": "template"
64+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"name": "template",
3+
"children": {
4+
"object": [
5+
{
6+
"name": "object",
7+
"children": {
8+
"left-curly": [
9+
{
10+
"image": "{",
11+
"startColumn": 1,
12+
"endColumn": 1,
13+
"tokenTypeName": "left-curly"
14+
}
15+
],
16+
"object-item": [
17+
{
18+
"name": "object-item",
19+
"children": {
20+
"key": [
21+
{
22+
"image": "$filter",
23+
"startColumn": 5,
24+
"endColumn": 11,
25+
"tokenTypeName": "key"
26+
}
27+
],
28+
"colon": [
29+
{
30+
"image": ":",
31+
"startColumn": 13,
32+
"endColumn": 13,
33+
"tokenTypeName": "colon"
34+
}
35+
],
36+
"value": [
37+
{
38+
"name": "value",
39+
"children": {
40+
"string-value": [
41+
{
42+
"image": "\"TravelStatus_code eq \\\"O\\\" and IsActiveEntity eq false or SiblingEntity/IsActiveEntity eq null\"",
43+
"startColumn": 15,
44+
"endColumn": 110,
45+
"tokenTypeName": "string-value"
46+
}
47+
]
48+
},
49+
"location": {
50+
"startColumn": 15,
51+
"endColumn": 110
52+
}
53+
}
54+
]
55+
},
56+
"location": {
57+
"startColumn": 5,
58+
"endColumn": 110
59+
}
60+
}
61+
],
62+
"right-curly": [
63+
{
64+
"image": "}",
65+
"startColumn": 1,
66+
"endColumn": 1,
67+
"tokenTypeName": "right-curly"
68+
}
69+
]
70+
},
71+
"location": {
72+
"startColumn": 1,
73+
"endColumn": 1
74+
}
75+
}
76+
]
77+
},
78+
"location": {
79+
"startColumn": 1,
80+
"endColumn": 1
81+
}
82+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
$filter : "TravelStatus_code eq \"O\" and IsActiveEntity eq false or SiblingEntity/IsActiveEntity eq null"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)