@fc-components/monaco-editor 0.2.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/expr/__tests__/__mocks__/monaco-editor.d.ts +28 -0
- package/dist/expr/completion/getCompletionProvider.d.ts +4 -0
- package/dist/expr/expr.d.ts +83 -0
- package/dist/expr/index.d.ts +3 -0
- package/dist/expr/parser/index.d.ts +3 -0
- package/dist/expr/parser/lexer.d.ts +27 -0
- package/dist/expr/parser/parser.d.ts +66 -0
- package/dist/expr/parser/types.d.ts +32 -0
- package/dist/expr/types.d.ts +17 -0
- package/dist/expr/validation.d.ts +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/monaco-editor.cjs.development.js +1987 -3
- package/dist/monaco-editor.cjs.development.js.map +1 -1
- package/dist/monaco-editor.cjs.production.min.js +1 -1
- package/dist/monaco-editor.cjs.production.min.js.map +1 -1
- package/dist/monaco-editor.esm.js +1991 -8
- package/dist/monaco-editor.esm.js.map +1 -1
- package/dist/sql/format.d.ts +1 -0
- package/dist/sql/index.d.ts +9 -2
- package/package.json +6 -1
- package/src/expr/__tests__/__mocks__/monaco-editor.ts +34 -0
- package/src/expr/__tests__/expr.test.tsx +339 -0
- package/src/expr/completion/getCompletionProvider.ts +133 -0
- package/src/expr/expr.ts +229 -0
- package/src/expr/index.tsx +322 -0
- package/src/expr/parser/index.ts +3 -0
- package/src/expr/parser/lexer.ts +377 -0
- package/src/expr/parser/parser.ts +581 -0
- package/src/expr/parser/types.ts +77 -0
- package/src/expr/types.ts +17 -0
- package/src/expr/validation.ts +209 -0
- package/src/index.tsx +2 -0
- package/src/sql/format.ts +13 -0
- package/src/sql/index.tsx +91 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatSql(sql: string): string;
|
package/dist/sql/index.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ interface SqlEditorProps {
|
|
|
16
16
|
onBlur?: (value: string) => void;
|
|
17
17
|
onFocus?: (value: string) => void;
|
|
18
18
|
editorDidMount?: (editor: monacoTypes.editor.IStandaloneCodeEditor) => void;
|
|
19
|
+
/** Whether to show the format button. Default: false */
|
|
20
|
+
enableFormat?: boolean;
|
|
21
|
+
/** Custom format button renderer */
|
|
22
|
+
renderFormatButton?: (handleFormat: () => void) => React.ReactNode;
|
|
19
23
|
}
|
|
20
|
-
export
|
|
21
|
-
|
|
24
|
+
export interface SqlEditorHandle {
|
|
25
|
+
format: () => void;
|
|
26
|
+
}
|
|
27
|
+
declare const SqlEditor: React.ForwardRefExoticComponent<SqlEditorProps & React.RefAttributes<SqlEditorHandle>>;
|
|
28
|
+
export default SqlEditor;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.2
|
|
6
|
+
"version": "0.3.2",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/monaco-editor.esm.js",
|
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
"react": ">=16"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@babel/preset-react": "^7.28.5",
|
|
33
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
32
34
|
"@size-limit/preset-small-lib": "^11.2.0",
|
|
35
|
+
"@types/jest": "^30.0.0",
|
|
33
36
|
"@types/lodash": "4.x",
|
|
34
37
|
"@types/pluralize": "^0.0.33",
|
|
35
38
|
"@types/react": "^17.0.0",
|
|
@@ -39,6 +42,7 @@
|
|
|
39
42
|
"react": "^17.0.0",
|
|
40
43
|
"react-dom": "^17.0.0",
|
|
41
44
|
"size-limit": "^11.2.0",
|
|
45
|
+
"ts-jest": "^29.2.6",
|
|
42
46
|
"tsdx": "^0.14.1",
|
|
43
47
|
"tslib": "^2.8.1",
|
|
44
48
|
"typescript": "^5.8.3"
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
"monaco-promql": "^1.7.4",
|
|
55
59
|
"pluralize": "^8.0.0",
|
|
56
60
|
"react-monaco-editor": "^0.58.0",
|
|
61
|
+
"sql-formatter": "^15.8.0",
|
|
57
62
|
"uuidv4": "^6.2.13"
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const MarkerSeverity = {
|
|
2
|
+
Warning: 4,
|
|
3
|
+
Error: 8,
|
|
4
|
+
Info: 2,
|
|
5
|
+
Hint: 1,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const editor = {
|
|
9
|
+
setModelMarkers: () => {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const languages = {
|
|
13
|
+
CompletionItemKind: {
|
|
14
|
+
Keyword: 14,
|
|
15
|
+
Function: 9,
|
|
16
|
+
},
|
|
17
|
+
CompletionItemInsertTextRule: {
|
|
18
|
+
InsertAsSnippet: 1,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const KeyMod = {
|
|
23
|
+
Shift: 1,
|
|
24
|
+
CtrlCmd: 2,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const KeyCode = {
|
|
28
|
+
Enter: 3,
|
|
29
|
+
KeyF: 4,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Selection = function () {};
|
|
33
|
+
|
|
34
|
+
export const Range = function () {};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { language, languageConfiguration } from '../expr';
|
|
2
|
+
import { validateExpr } from '../validation';
|
|
3
|
+
import { getExprCompletionProvider } from '../completion/getCompletionProvider';
|
|
4
|
+
|
|
5
|
+
describe('expr language definition', () => {
|
|
6
|
+
it('should have correct language id prefix', () => {
|
|
7
|
+
expect(language.tokenPostfix).toBe('.expr');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should define keywords', () => {
|
|
11
|
+
expect(language.keywords).toContain('let');
|
|
12
|
+
expect(language.keywords).toContain('true');
|
|
13
|
+
expect(language.keywords).toContain('false');
|
|
14
|
+
expect(language.keywords).toContain('nil');
|
|
15
|
+
expect(language.keywords).toContain('in');
|
|
16
|
+
expect(language.keywords).toContain('not');
|
|
17
|
+
expect(language.keywords).toContain('and');
|
|
18
|
+
expect(language.keywords).toContain('or');
|
|
19
|
+
expect(language.keywords).toContain('if');
|
|
20
|
+
expect(language.keywords).toContain('else');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should define builtin functions', () => {
|
|
24
|
+
expect(language.builtinFunctions).toContain('len');
|
|
25
|
+
expect(language.builtinFunctions).toContain('now');
|
|
26
|
+
expect(language.builtinFunctions).toContain('filter');
|
|
27
|
+
expect(language.builtinFunctions).toContain('map');
|
|
28
|
+
expect(language.builtinFunctions).toContain('all');
|
|
29
|
+
expect(language.builtinFunctions).toContain('type');
|
|
30
|
+
expect(language.builtinFunctions).toContain('bitand');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should define operators', () => {
|
|
34
|
+
expect(language.operators).toContain('+');
|
|
35
|
+
expect(language.operators).toContain('-');
|
|
36
|
+
expect(language.operators).toContain('*');
|
|
37
|
+
expect(language.operators).toContain('**');
|
|
38
|
+
expect(language.operators).toContain('..');
|
|
39
|
+
expect(language.operators).toContain('|');
|
|
40
|
+
expect(language.operators).toContain('?.');
|
|
41
|
+
expect(language.operators).toContain('??');
|
|
42
|
+
expect(language.operators).toContain('matches');
|
|
43
|
+
expect(language.operators).toContain('in');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should have comment configuration', () => {
|
|
47
|
+
expect(languageConfiguration.comments.lineComment).toBe('//');
|
|
48
|
+
expect(languageConfiguration.comments.blockComment).toEqual(['/*', '*/']);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should have bracket configuration', () => {
|
|
52
|
+
expect(languageConfiguration.brackets).toContainEqual(['{', '}']);
|
|
53
|
+
expect(languageConfiguration.brackets).toContainEqual(['[', ']']);
|
|
54
|
+
expect(languageConfiguration.brackets).toContainEqual(['(', ')']);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should have auto closing pairs', () => {
|
|
58
|
+
expect(languageConfiguration.autoClosingPairs).toContainEqual({ open: '"', close: '"' });
|
|
59
|
+
expect(languageConfiguration.autoClosingPairs).toContainEqual({ open: "'", close: "'" });
|
|
60
|
+
expect(languageConfiguration.autoClosingPairs).toContainEqual({ open: '`', close: '`' });
|
|
61
|
+
expect(languageConfiguration.autoClosingPairs).toContainEqual({ open: '(', close: ')' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should have tokenizer with root section', () => {
|
|
65
|
+
expect(language.tokenizer).toHaveProperty('root');
|
|
66
|
+
expect(Array.isArray(language.tokenizer.root)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should have all required tokenizer states', () => {
|
|
70
|
+
const states = Object.keys(language.tokenizer);
|
|
71
|
+
expect(states).toContain('root');
|
|
72
|
+
expect(states).toContain('whitespace');
|
|
73
|
+
expect(states).toContain('comments');
|
|
74
|
+
expect(states).toContain('comment');
|
|
75
|
+
expect(states).toContain('numbers');
|
|
76
|
+
expect(states).toContain('strings');
|
|
77
|
+
expect(states).toContain('string_double');
|
|
78
|
+
expect(states).toContain('string_single');
|
|
79
|
+
expect(states).toContain('string_backtick');
|
|
80
|
+
expect(states).toContain('bytes');
|
|
81
|
+
expect(states).toContain('bytes_double');
|
|
82
|
+
expect(states).toContain('bytes_single');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('expr validation', () => {
|
|
87
|
+
it('should return empty markers for empty string', () => {
|
|
88
|
+
const markers = validateExpr('');
|
|
89
|
+
expect(markers).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return empty markers for valid expression', () => {
|
|
93
|
+
const markers = validateExpr('let x = 42; x * 2');
|
|
94
|
+
expect(markers).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should detect unclosed single quote', () => {
|
|
98
|
+
const markers = validateExpr("let x = 'hello");
|
|
99
|
+
const quoteMarkers = markers.filter((m) => (m.message || '').includes('quote'));
|
|
100
|
+
expect(quoteMarkers.length).toBeGreaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should detect unclosed double quote', () => {
|
|
104
|
+
const markers = validateExpr('let x = "hello');
|
|
105
|
+
const quoteMarkers = markers.filter((m) => (m.message || '').includes('quote'));
|
|
106
|
+
expect(quoteMarkers.length).toBeGreaterThan(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should detect unclosed backtick', () => {
|
|
110
|
+
const markers = validateExpr('let x = `hello');
|
|
111
|
+
const quoteMarkers = markers.filter((m) => (m.message || '').includes('quote') || (m.message || '').includes('backtick'));
|
|
112
|
+
expect(quoteMarkers.length).toBeGreaterThan(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should detect unmatched opening parenthesis', () => {
|
|
116
|
+
const markers = validateExpr('let x = (1 + 2');
|
|
117
|
+
const parenMarkers = markers.filter((m) => (m.message || '').includes('parenthesis') || (m.message || '').includes('end of expression') || (m.message || '').includes('")"'));
|
|
118
|
+
expect(parenMarkers.length).toBeGreaterThan(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should detect mismatch', () => {
|
|
122
|
+
const markers = validateExpr('let x = 1 + 2)');
|
|
123
|
+
expect(markers.length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should detect unmatched opening bracket', () => {
|
|
127
|
+
const markers = validateExpr('let x = [1, 2');
|
|
128
|
+
const bracketMarkers = markers.filter((m) => (m.message || '').includes('bracket') || (m.message || '').includes('end of expression') || (m.message || '').includes('"]"'));
|
|
129
|
+
expect(bracketMarkers.length).toBeGreaterThan(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should detect unmatched closing bracket', () => {
|
|
133
|
+
const markers = validateExpr('let x = 1, 2]');
|
|
134
|
+
expect(markers.length).toBeGreaterThan(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should detect unmatched opening curly brace', () => {
|
|
138
|
+
const markers = validateExpr('let x = {a: 1');
|
|
139
|
+
expect(markers.length).toBeGreaterThan(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should detect unmatched closing curly brace', () => {
|
|
143
|
+
const markers = validateExpr('let x = a: 1}');
|
|
144
|
+
expect(markers.length).toBeGreaterThan(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should not flag valid closed quotes', () => {
|
|
148
|
+
const markers = validateExpr('let name = "hello"');
|
|
149
|
+
const quoteMarkers = markers.filter((m) => (m.message || '').includes('quote'));
|
|
150
|
+
expect(quoteMarkers).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should not flag valid closed parentheses', () => {
|
|
154
|
+
const markers = validateExpr('let x = (1 + 2) * 3');
|
|
155
|
+
expect(markers).toHaveLength(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle block comments', () => {
|
|
159
|
+
const markers = validateExpr('let x = 1 /* this is a comment */ + 2');
|
|
160
|
+
expect(markers).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle multi-line block comments', () => {
|
|
164
|
+
const markers = validateExpr('/* start\nmiddle\nend */ let x = 1');
|
|
165
|
+
expect(markers).toHaveLength(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should handle balanced quotes', () => {
|
|
169
|
+
const markers = validateExpr(`let name = "hello" + 'world'`);
|
|
170
|
+
const quoteMarkers = markers.filter((m) => (m.message || '').includes('quote'));
|
|
171
|
+
expect(quoteMarkers).toHaveLength(0);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('expr parser syntax validation', () => {
|
|
176
|
+
it('should accept valid expressions', () => {
|
|
177
|
+
const validExprs = [
|
|
178
|
+
'42',
|
|
179
|
+
'"hello"',
|
|
180
|
+
'true',
|
|
181
|
+
'nil',
|
|
182
|
+
'a + b',
|
|
183
|
+
'a * b + c',
|
|
184
|
+
'a > 5',
|
|
185
|
+
'a || b && c',
|
|
186
|
+
'user.Name',
|
|
187
|
+
'user?.Name',
|
|
188
|
+
'arr[0]',
|
|
189
|
+
'arr[1:3]',
|
|
190
|
+
'foo(1, 2)',
|
|
191
|
+
'len(arr)',
|
|
192
|
+
'filter(arr, {# > 2})',
|
|
193
|
+
'[1, 2, 3]',
|
|
194
|
+
'{a: 1, b: 2}',
|
|
195
|
+
'let x = 42; x * 2',
|
|
196
|
+
'a ? b : c',
|
|
197
|
+
'a ?: c',
|
|
198
|
+
'a ?? b',
|
|
199
|
+
'a | upper()',
|
|
200
|
+
'a matches "foo"',
|
|
201
|
+
'a contains "bar"',
|
|
202
|
+
'now()',
|
|
203
|
+
'a + b; c + d',
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const expr of validExprs) {
|
|
207
|
+
const markers = validateExpr(expr);
|
|
208
|
+
const syntaxErrors = markers.filter((m) => m.severity === 8);
|
|
209
|
+
expect(syntaxErrors.map((e) => `${expr}: ${e.message}`)).toEqual([]);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should report unexpected token', () => {
|
|
214
|
+
const markers = validateExpr('42 @ 10');
|
|
215
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
216
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
217
|
+
expect(errors[0].message).toContain('unexpected token');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should report unexpected end of expression', () => {
|
|
221
|
+
const markers = validateExpr('foo(');
|
|
222
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
223
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should report expected identifier after let', () => {
|
|
227
|
+
const markers = validateExpr('let 42 = 10');
|
|
228
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
229
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
230
|
+
expect(errors[0].message).toContain('variable name');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should report expected = after variable name', () => {
|
|
234
|
+
const markers = validateExpr('let x 10');
|
|
235
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
236
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
237
|
+
expect(errors[0].message).toContain('expected "="');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should report missing map value after colon', () => {
|
|
241
|
+
const markers = validateExpr('{a:}');
|
|
242
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
243
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should report unexpected token in array', () => {
|
|
247
|
+
const markers = validateExpr('[1 2]');
|
|
248
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
249
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should report missing closing parenthesis', () => {
|
|
253
|
+
const markers = validateExpr('(1 + 2');
|
|
254
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
255
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should report missing property after dot', () => {
|
|
259
|
+
const markers = validateExpr('user.');
|
|
260
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
261
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should report missing identifier after pipe', () => {
|
|
265
|
+
const markers = validateExpr('a |');
|
|
266
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
267
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle complex valid expressions', () => {
|
|
271
|
+
const markers = validateExpr('filter(users, {.Age > 18 && .Name startsWith "J"})');
|
|
272
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
273
|
+
expect(errors).toHaveLength(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should validate expression with pipe and builtin', () => {
|
|
277
|
+
const markers = validateExpr('user.Name | upper() | split(" ")');
|
|
278
|
+
const errors = markers.filter((m) => m.severity === 8);
|
|
279
|
+
expect(errors).toHaveLength(0);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('expr completion provider', () => {
|
|
284
|
+
it('should return a completion provider', () => {
|
|
285
|
+
const provider = getExprCompletionProvider();
|
|
286
|
+
expect(provider).toBeDefined();
|
|
287
|
+
expect(typeof provider.provideCompletionItems).toBe('function');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should provide keywords in suggestions', async () => {
|
|
291
|
+
const provider = getExprCompletionProvider();
|
|
292
|
+
const mockModel: any = {
|
|
293
|
+
getWordUntilPosition: () => ({ startColumn: 1, endColumn: 1, word: '' }),
|
|
294
|
+
getValue: () => '',
|
|
295
|
+
getLineContent: () => '',
|
|
296
|
+
};
|
|
297
|
+
const mockPosition: any = { lineNumber: 1, column: 1 };
|
|
298
|
+
const emptyContext: any = {};
|
|
299
|
+
const emptyToken: any = {};
|
|
300
|
+
|
|
301
|
+
const result = await provider.provideCompletionItems(mockModel, mockPosition, emptyContext, emptyToken);
|
|
302
|
+
const suggestions = (result && result.suggestions) || [];
|
|
303
|
+
|
|
304
|
+
const keywordLabels = suggestions.filter((s: any) => s.kind === 14).map((s: any) => s.label);
|
|
305
|
+
|
|
306
|
+
expect(keywordLabels).toContain('let');
|
|
307
|
+
expect(keywordLabels).toContain('true');
|
|
308
|
+
expect(keywordLabels).toContain('false');
|
|
309
|
+
expect(keywordLabels).toContain('nil');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should provide functions in suggestions', async () => {
|
|
313
|
+
const provider = getExprCompletionProvider();
|
|
314
|
+
const mockModel: any = {
|
|
315
|
+
getWordUntilPosition: () => ({ startColumn: 1, endColumn: 1, word: '' }),
|
|
316
|
+
getValue: () => '',
|
|
317
|
+
getLineContent: () => '',
|
|
318
|
+
};
|
|
319
|
+
const mockPosition: any = { lineNumber: 1, column: 1 };
|
|
320
|
+
const emptyContext: any = {};
|
|
321
|
+
const emptyToken: any = {};
|
|
322
|
+
|
|
323
|
+
const result = await provider.provideCompletionItems(mockModel, mockPosition, emptyContext, emptyToken);
|
|
324
|
+
const suggestions = (result && result.suggestions) || [];
|
|
325
|
+
|
|
326
|
+
const functionLabels = suggestions
|
|
327
|
+
.filter(function (s) {
|
|
328
|
+
return s.kind === 9;
|
|
329
|
+
})
|
|
330
|
+
.map(function (s) {
|
|
331
|
+
return s.label;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(functionLabels).toContain('len');
|
|
335
|
+
expect(functionLabels).toContain('filter');
|
|
336
|
+
expect(functionLabels).toContain('map');
|
|
337
|
+
expect(functionLabels).toContain('now');
|
|
338
|
+
});
|
|
339
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
|
|
3
|
+
const EXPR_KEYWORDS = ['let', 'true', 'false', 'nil', 'in', 'not', 'and', 'or', 'if', 'else'];
|
|
4
|
+
|
|
5
|
+
const EXPR_FUNCTIONS: { name: string; signature: string; description: string; category: string }[] = [
|
|
6
|
+
// String functions
|
|
7
|
+
{ name: 'trim', signature: 'trim(str[, chars])', description: 'Removes white spaces from both ends of a string', category: 'string' },
|
|
8
|
+
{ name: 'trimPrefix', signature: 'trimPrefix(str, prefix)', description: 'Removes the specified prefix from the string', category: 'string' },
|
|
9
|
+
{ name: 'trimSuffix', signature: 'trimSuffix(str, suffix)', description: 'Removes the specified suffix from the string', category: 'string' },
|
|
10
|
+
{ name: 'upper', signature: 'upper(str)', description: 'Converts all characters to uppercase', category: 'string' },
|
|
11
|
+
{ name: 'lower', signature: 'lower(str)', description: 'Converts all characters to lowercase', category: 'string' },
|
|
12
|
+
{ name: 'split', signature: 'split(str, delimiter[, n])', description: 'Splits a string at each instance of the delimiter', category: 'string' },
|
|
13
|
+
{ name: 'splitAfter', signature: 'splitAfter(str, delimiter[, n])', description: 'Splits a string after each instance of the delimiter', category: 'string' },
|
|
14
|
+
{ name: 'replace', signature: 'replace(str, old, new)', description: 'Replaces all occurrences of old with new', category: 'string' },
|
|
15
|
+
{ name: 'repeat', signature: 'repeat(str, n)', description: 'Repeats a string n times', category: 'string' },
|
|
16
|
+
{ name: 'indexOf', signature: 'indexOf(str, substring)', description: 'Returns the index of the first occurrence of substring', category: 'string' },
|
|
17
|
+
{ name: 'lastIndexOf', signature: 'lastIndexOf(str, substring)', description: 'Returns the index of the last occurrence of substring', category: 'string' },
|
|
18
|
+
{ name: 'hasPrefix', signature: 'hasPrefix(str, prefix)', description: 'Returns true if string starts with the given prefix', category: 'string' },
|
|
19
|
+
{ name: 'hasSuffix', signature: 'hasSuffix(str, suffix)', description: 'Returns true if string ends with the given suffix', category: 'string' },
|
|
20
|
+
{ name: 'contains', signature: 'contains(str, substr)', description: 'Returns true if string contains the given substring', category: 'string' },
|
|
21
|
+
{ name: 'startsWith', signature: 'startsWith(str, prefix)', description: 'Returns true if string starts with the given prefix', category: 'string' },
|
|
22
|
+
{ name: 'endsWith', signature: 'endsWith(str, suffix)', description: 'Returns true if string ends with the given suffix', category: 'string' },
|
|
23
|
+
|
|
24
|
+
// Date functions
|
|
25
|
+
{ name: 'now', signature: 'now()', description: 'Returns the current date as a time.Time value', category: 'date' },
|
|
26
|
+
{ name: 'duration', signature: 'duration(str)', description: 'Returns time.Duration value of the given string', category: 'date' },
|
|
27
|
+
{ name: 'date', signature: 'date(str[, format[, timezone]])', description: 'Converts the given string into a date representation', category: 'date' },
|
|
28
|
+
{ name: 'timezone', signature: 'timezone(str)', description: 'Returns the timezone of the given string', category: 'date' },
|
|
29
|
+
|
|
30
|
+
// Number functions
|
|
31
|
+
{ name: 'max', signature: 'max(n1, n2)', description: 'Returns the maximum of two numbers', category: 'number' },
|
|
32
|
+
{ name: 'min', signature: 'min(n1, n2)', description: 'Returns the minimum of two numbers', category: 'number' },
|
|
33
|
+
{ name: 'abs', signature: 'abs(n)', description: 'Returns the absolute value of a number', category: 'number' },
|
|
34
|
+
{ name: 'ceil', signature: 'ceil(n)', description: 'Returns the least integer value greater than or equal to x', category: 'number' },
|
|
35
|
+
{ name: 'floor', signature: 'floor(n)', description: 'Returns the greatest integer value less than or equal to x', category: 'number' },
|
|
36
|
+
{ name: 'round', signature: 'round(n)', description: 'Returns the nearest integer, rounding half away from zero', category: 'number' },
|
|
37
|
+
|
|
38
|
+
// Array functions
|
|
39
|
+
{ name: 'all', signature: 'all(array, predicate)', description: 'Returns true if all elements satisfy the predicate', category: 'array' },
|
|
40
|
+
{ name: 'any', signature: 'any(array, predicate)', description: 'Returns true if any elements satisfy the predicate', category: 'array' },
|
|
41
|
+
{ name: 'one', signature: 'one(array, predicate)', description: 'Returns true if exactly one element satisfies the predicate', category: 'array' },
|
|
42
|
+
{ name: 'none', signature: 'none(array, predicate)', description: 'Returns true if no elements satisfy the predicate', category: 'array' },
|
|
43
|
+
{ name: 'map', signature: 'map(array, predicate)', description: 'Returns new array by applying the predicate to each element', category: 'array' },
|
|
44
|
+
{ name: 'filter', signature: 'filter(array, predicate)', description: 'Returns new array by filtering elements by predicate', category: 'array' },
|
|
45
|
+
{ name: 'find', signature: 'find(array, predicate)', description: 'Finds the first element satisfying the predicate', category: 'array' },
|
|
46
|
+
{ name: 'findIndex', signature: 'findIndex(array, predicate)', description: 'Finds the index of the first element satisfying the predicate', category: 'array' },
|
|
47
|
+
{ name: 'findLast', signature: 'findLast(array, predicate)', description: 'Finds the last element satisfying the predicate', category: 'array' },
|
|
48
|
+
{ name: 'findLastIndex', signature: 'findLastIndex(array, predicate)', description: 'Finds the index of the last element satisfying the predicate', category: 'array' },
|
|
49
|
+
{ name: 'groupBy', signature: 'groupBy(array, predicate)', description: 'Groups elements by the result of the predicate', category: 'array' },
|
|
50
|
+
{ name: 'count', signature: 'count(array[, predicate])', description: 'Returns the number of elements satisfying the predicate', category: 'array' },
|
|
51
|
+
{ name: 'concat', signature: 'concat(array1, array2[, ...])', description: 'Concatenates two or more arrays', category: 'array' },
|
|
52
|
+
{ name: 'flatten', signature: 'flatten(array)', description: 'Flattens an array into one-dimensional array', category: 'array' },
|
|
53
|
+
{ name: 'uniq', signature: 'uniq(array)', description: 'Removes duplicates from an array', category: 'array' },
|
|
54
|
+
{ name: 'join', signature: 'join(array[, delimiter])', description: 'Joins an array of strings into a single string', category: 'array' },
|
|
55
|
+
{ name: 'reduce', signature: 'reduce(array, predicate[, initial])', description: 'Reduces an array to a single value', category: 'array' },
|
|
56
|
+
{ name: 'sum', signature: 'sum(array[, predicate])', description: 'Returns the sum of all numbers in an array', category: 'array' },
|
|
57
|
+
{ name: 'mean', signature: 'mean(array)', description: 'Returns the average of all numbers in an array', category: 'array' },
|
|
58
|
+
{ name: 'median', signature: 'median(array)', description: 'Returns the median of all numbers in an array', category: 'array' },
|
|
59
|
+
{ name: 'first', signature: 'first(array)', description: 'Returns the first element from an array', category: 'array' },
|
|
60
|
+
{ name: 'last', signature: 'last(array)', description: 'Returns the last element from an array', category: 'array' },
|
|
61
|
+
{ name: 'take', signature: 'take(array, n)', description: 'Returns the first n elements from an array', category: 'array' },
|
|
62
|
+
{ name: 'reverse', signature: 'reverse(array)', description: 'Returns a new reversed copy of the array', category: 'array' },
|
|
63
|
+
{ name: 'sort', signature: 'sort(array[, order])', description: 'Sorts an array in ascending or descending order', category: 'array' },
|
|
64
|
+
{ name: 'sortBy', signature: 'sortBy(array[, predicate, order])', description: 'Sorts an array by the result of the predicate', category: 'array' },
|
|
65
|
+
|
|
66
|
+
// Map functions
|
|
67
|
+
{ name: 'keys', signature: 'keys(map)', description: 'Returns an array containing the keys of the map', category: 'map' },
|
|
68
|
+
{ name: 'values', signature: 'values(map)', description: 'Returns an array containing the values of the map', category: 'map' },
|
|
69
|
+
|
|
70
|
+
// Type conversion functions
|
|
71
|
+
{ name: 'type', signature: 'type(v)', description: 'Returns the type of the given value', category: 'conversion' },
|
|
72
|
+
{ name: 'int', signature: 'int(v)', description: 'Returns the integer value of a number or string', category: 'conversion' },
|
|
73
|
+
{ name: 'float', signature: 'float(v)', description: 'Returns the float value of a number or string', category: 'conversion' },
|
|
74
|
+
{ name: 'string', signature: 'string(v)', description: 'Converts a value to its string representation', category: 'conversion' },
|
|
75
|
+
{ name: 'toJSON', signature: 'toJSON(v)', description: 'Converts a value to JSON string representation', category: 'conversion' },
|
|
76
|
+
{ name: 'fromJSON', signature: 'fromJSON(v)', description: 'Parses a JSON string to a value', category: 'conversion' },
|
|
77
|
+
{ name: 'toBase64', signature: 'toBase64(v)', description: 'Encodes a string into Base64 format', category: 'conversion' },
|
|
78
|
+
{ name: 'fromBase64', signature: 'fromBase64(v)', description: 'Decodes a Base64 encoded string', category: 'conversion' },
|
|
79
|
+
{ name: 'toPairs', signature: 'toPairs(map)', description: 'Converts a map to an array of key-value pairs', category: 'conversion' },
|
|
80
|
+
{ name: 'fromPairs', signature: 'fromPairs(array)', description: 'Converts an array of key-value pairs to a map', category: 'conversion' },
|
|
81
|
+
|
|
82
|
+
// Miscellaneous functions
|
|
83
|
+
{ name: 'len', signature: 'len(v)', description: 'Returns the length of an array, map, or string', category: 'misc' },
|
|
84
|
+
{ name: 'get', signature: 'get(v, index)', description: 'Retrieves element at the specified index from an array or map', category: 'misc' },
|
|
85
|
+
|
|
86
|
+
// Bitwise functions
|
|
87
|
+
{ name: 'bitand', signature: 'bitand(int, int)', description: 'Returns the bitwise AND of two integers', category: 'bitwise' },
|
|
88
|
+
{ name: 'bitor', signature: 'bitor(int, int)', description: 'Returns the bitwise OR of two integers', category: 'bitwise' },
|
|
89
|
+
{ name: 'bitxor', signature: 'bitxor(int, int)', description: 'Returns the bitwise XOR of two integers', category: 'bitwise' },
|
|
90
|
+
{ name: 'bitnand', signature: 'bitnand(int, int)', description: 'Returns the bitwise AND NOT of two integers', category: 'bitwise' },
|
|
91
|
+
{ name: 'bitnot', signature: 'bitnot(int)', description: 'Returns the bitwise NOT of an integer', category: 'bitwise' },
|
|
92
|
+
{ name: 'bitshl', signature: 'bitshl(int, int)', description: 'Returns the Left Shift of an integer', category: 'bitwise' },
|
|
93
|
+
{ name: 'bitshr', signature: 'bitshr(int, int)', description: 'Returns the Right Shift of an integer', category: 'bitwise' },
|
|
94
|
+
{ name: 'bitushr', signature: 'bitushr(int, int)', description: 'Returns the unsigned Right Shift of an integer', category: 'bitwise' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export const getExprCompletionProvider = () => {
|
|
98
|
+
return {
|
|
99
|
+
provideCompletionItems(
|
|
100
|
+
model: monaco.editor.ITextModel,
|
|
101
|
+
position: monaco.Position,
|
|
102
|
+
_context: monaco.languages.CompletionContext,
|
|
103
|
+
_token: monaco.CancellationToken,
|
|
104
|
+
): monaco.languages.ProviderResult<monaco.languages.CompletionList> {
|
|
105
|
+
const word = model.getWordUntilPosition(position);
|
|
106
|
+
const range = new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
|
|
107
|
+
|
|
108
|
+
const suggestions: monaco.languages.CompletionItem[] = [
|
|
109
|
+
...EXPR_KEYWORDS.map((keyword) => ({
|
|
110
|
+
label: keyword,
|
|
111
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
112
|
+
insertText: keyword,
|
|
113
|
+
range: range,
|
|
114
|
+
sortText: '1' + keyword,
|
|
115
|
+
})),
|
|
116
|
+
...EXPR_FUNCTIONS.map((func) => ({
|
|
117
|
+
label: func.name,
|
|
118
|
+
kind: monaco.languages.CompletionItemKind.Function,
|
|
119
|
+
insertText: func.name,
|
|
120
|
+
detail: func.signature,
|
|
121
|
+
documentation: func.description + ` (${func.category})`,
|
|
122
|
+
range: range,
|
|
123
|
+
sortText: '2' + func.name,
|
|
124
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
125
|
+
})),
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
suggestions: suggestions,
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
};
|