@valtzu/codemirror-lang-el 1.1.0 → 1.3.0
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/CHANGELOG.md +13 -0
- package/README.md +11 -2
- package/dist/index.cjs +59 -21
- package/dist/index.js +59 -21
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
1.3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
* Lint left- and right-side arguments of `contains`, `starts with`, `ends with` and `matches` operators
|
|
8
|
+
* Add eslint to make contributing/review easier
|
|
9
|
+
* Add type checking for left-side argument in `in` expression
|
|
10
|
+
|
|
11
|
+
1.2
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
* Add support for typed arrays
|
|
15
|
+
* Add Codex CLI and Docker Compose integration
|
|
16
|
+
|
|
4
17
|
1.1
|
|
5
18
|
---
|
|
6
19
|
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ If you're using Bootstrap UI, check the [Web Component](https://github.com/valtz
|
|
|
42
42
|
|
|
43
43
|
```html
|
|
44
44
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
45
|
-
<script type="module" src="https://esm.sh/symfony-expression-editor
|
|
45
|
+
<script type="module" src="https://esm.sh/symfony-expression-editor"></script>
|
|
46
46
|
<textarea class="form-control" is="expression-editor" rows="1">'foobar' starts with 'foo'</textarea>
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -72,7 +72,7 @@ See [CONFIGURATION.md](CONFIGURATION.md)
|
|
|
72
72
|
|
|
73
73
|
### Example
|
|
74
74
|
|
|
75
|
-
[Live demo](https://jsfiddle.net/
|
|
75
|
+
[Live demo](https://jsfiddle.net/32ybaetz/)
|
|
76
76
|
|
|
77
77
|
```html
|
|
78
78
|
<div id="editor"></div>
|
|
@@ -118,6 +118,15 @@ See [CONFIGURATION.md](CONFIGURATION.md)
|
|
|
118
118
|
</script>
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### Using OpenAI codex
|
|
122
|
+
|
|
123
|
+
Create `.env` file with
|
|
124
|
+
```
|
|
125
|
+
OPENAI_API_KEY="..."
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then use `docker compose run --rm codex` to develop with AI.
|
|
129
|
+
|
|
121
130
|
### Contributing
|
|
122
131
|
|
|
123
132
|
Contributions are welcome.
|
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ var autocomplete = require('@codemirror/autocomplete');
|
|
|
8
8
|
var state = require('@codemirror/state');
|
|
9
9
|
var view = require('@codemirror/view');
|
|
10
10
|
|
|
11
|
-
// @ts-
|
|
11
|
+
// @ts-expect-error TS2739
|
|
12
12
|
const t = {
|
|
13
13
|
deserialize: (str) => str,
|
|
14
14
|
};
|
|
@@ -117,7 +117,7 @@ const resolveIdentifier = (nodeTypeId, identifier, config) => {
|
|
|
117
117
|
};
|
|
118
118
|
function resolveTypes(state, node, config) {
|
|
119
119
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
120
|
-
|
|
120
|
+
const types = new Set();
|
|
121
121
|
if (!node) {
|
|
122
122
|
return types;
|
|
123
123
|
}
|
|
@@ -130,19 +130,19 @@ function resolveTypes(state, node, config) {
|
|
|
130
130
|
}
|
|
131
131
|
else if (node.type.is(Variable)) {
|
|
132
132
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
133
|
-
// @ts-
|
|
133
|
+
// @ts-expect-error TS2339
|
|
134
134
|
(_b = (_a = resolveIdentifier(node.type.id, varName, config)) === null || _a === void 0 ? void 0 : _a.type) === null || _b === void 0 ? void 0 : _b.forEach((x) => types.add(x));
|
|
135
135
|
}
|
|
136
136
|
else if (node.type.is(Function)) {
|
|
137
137
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
138
|
-
// @ts-
|
|
138
|
+
// @ts-expect-error TS2339
|
|
139
139
|
(_d = (_c = resolveIdentifier(node.type.id, varName, config)) === null || _c === void 0 ? void 0 : _c.returnType) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
140
140
|
}
|
|
141
141
|
else if (node.type.is(PropertyAccess) && node.firstChild && ((_e = node.lastChild) === null || _e === void 0 ? void 0 : _e.type.is(Property))) {
|
|
142
142
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
143
143
|
(_f = resolveTypes(state, node.firstChild, config)) === null || _f === void 0 ? void 0 : _f.forEach(baseType => {
|
|
144
144
|
var _a, _b, _c, _d;
|
|
145
|
-
// @ts-
|
|
145
|
+
// @ts-expect-error TS2339
|
|
146
146
|
(_d = (_c = resolveIdentifier((_a = node.lastChild) === null || _a === void 0 ? void 0 : _a.type.id, varName, (_b = config.types) === null || _b === void 0 ? void 0 : _b[baseType])) === null || _c === void 0 ? void 0 : _c.type) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
147
147
|
});
|
|
148
148
|
}
|
|
@@ -150,10 +150,22 @@ function resolveTypes(state, node, config) {
|
|
|
150
150
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
151
151
|
(_h = resolveTypes(state, node.firstChild, config)) === null || _h === void 0 ? void 0 : _h.forEach(baseType => {
|
|
152
152
|
var _a, _b, _c, _d;
|
|
153
|
-
// @ts-
|
|
153
|
+
// @ts-expect-error TS2339
|
|
154
154
|
(_d = (_c = resolveIdentifier((_a = node.lastChild) === null || _a === void 0 ? void 0 : _a.type.id, varName, (_b = config.types) === null || _b === void 0 ? void 0 : _b[baseType])) === null || _c === void 0 ? void 0 : _c.returnType) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
|
+
// Array indexing: for typed arrays (e.g. Foo[]) return element type, for generic arrays return any
|
|
158
|
+
else if (node.type.is(ArrayAccess) && node.firstChild) {
|
|
159
|
+
const left = node.firstChild.node;
|
|
160
|
+
resolveTypes(state, left, config).forEach(baseType => {
|
|
161
|
+
if (baseType.endsWith('[]')) {
|
|
162
|
+
types.add(baseType.slice(0, -2));
|
|
163
|
+
}
|
|
164
|
+
else if (baseType === exports.ELScalar.Array) {
|
|
165
|
+
types.add(exports.ELScalar.Any);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
157
169
|
else if (node.type.is(Application) && node.firstChild) {
|
|
158
170
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
159
171
|
}
|
|
@@ -222,13 +234,13 @@ var utils = /*#__PURE__*/Object.freeze({
|
|
|
222
234
|
*/
|
|
223
235
|
const expressionLanguageLinterSource = (state) => {
|
|
224
236
|
const config = getExpressionLanguageConfig(state);
|
|
225
|
-
|
|
237
|
+
const diagnostics = [];
|
|
226
238
|
language.syntaxTree(state).cursor().iterate(node => {
|
|
227
239
|
var _a, _b, _c, _d, _e, _f;
|
|
228
240
|
const { from, to, type: { id } } = node;
|
|
229
241
|
let identifier;
|
|
230
242
|
switch (id) {
|
|
231
|
-
case 0:
|
|
243
|
+
case 0: {
|
|
232
244
|
if (state.doc.length === 0 || from === 0) {
|
|
233
245
|
// Don't show error on empty doc (even though it is an error)
|
|
234
246
|
return;
|
|
@@ -242,7 +254,8 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
242
254
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected ${type} <code>${identifier}</code>` });
|
|
243
255
|
}
|
|
244
256
|
return;
|
|
245
|
-
|
|
257
|
+
}
|
|
258
|
+
case Arguments: {
|
|
246
259
|
const fn = resolveFunctionDefinition(node.node.prevSibling, state, config);
|
|
247
260
|
const args = fn === null || fn === void 0 ? void 0 : fn.args;
|
|
248
261
|
if (!args) {
|
|
@@ -263,7 +276,12 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
263
276
|
const typesUsed = Array.from(resolveTypes(state, n, config));
|
|
264
277
|
const typesExpected = args[i].type;
|
|
265
278
|
if (typesExpected && !typesExpected.includes(exports.ELScalar.Any) && !typesUsed.some(x => typesExpected.includes(x))) {
|
|
266
|
-
diagnostics.push({
|
|
279
|
+
diagnostics.push({
|
|
280
|
+
from: n.from,
|
|
281
|
+
to: n.to,
|
|
282
|
+
severity: 'error',
|
|
283
|
+
message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>`
|
|
284
|
+
});
|
|
267
285
|
}
|
|
268
286
|
i++;
|
|
269
287
|
}
|
|
@@ -271,8 +289,9 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
271
289
|
diagnostics.push({ from: node.from, to: node.to, severity: 'error', message: `Too few arguments – ${argumentCountHintFn()}` });
|
|
272
290
|
}
|
|
273
291
|
break;
|
|
292
|
+
}
|
|
274
293
|
case Property:
|
|
275
|
-
case Method:
|
|
294
|
+
case Method: {
|
|
276
295
|
const leftArgument = (_e = (_d = node.node.parent) === null || _d === void 0 ? void 0 : _d.firstChild) === null || _e === void 0 ? void 0 : _e.node;
|
|
277
296
|
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
278
297
|
identifier = state.sliceDoc(from, to);
|
|
@@ -280,26 +299,46 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
280
299
|
diagnostics.push({ from, to, severity: 'error', message: `${node.name} <code>${identifier}</code> not found in <code>${types.join('|')}</code>` });
|
|
281
300
|
}
|
|
282
301
|
break;
|
|
302
|
+
}
|
|
283
303
|
case Variable:
|
|
284
|
-
case Function:
|
|
304
|
+
case Function: {
|
|
285
305
|
identifier = state.sliceDoc(from, node.node.firstChild ? node.node.firstChild.from - 1 : to);
|
|
286
306
|
if (!resolveIdentifier(id, identifier, config)) {
|
|
287
307
|
diagnostics.push({ from, to, severity: 'error', message: `${node.node.name} <code>${identifier}</code> not found` });
|
|
288
308
|
}
|
|
289
309
|
break;
|
|
290
|
-
|
|
310
|
+
}
|
|
311
|
+
case BinaryExpression: {
|
|
291
312
|
const operatorNode = node.node.getChild(OperatorKeyword);
|
|
292
313
|
if (operatorNode) {
|
|
293
314
|
const operator = state.sliceDoc(operatorNode.from, operatorNode.to);
|
|
315
|
+
const leftArgument = node.node.firstChild;
|
|
316
|
+
const rightArgument = node.node.lastChild;
|
|
294
317
|
if (operator === 'in') {
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
318
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
319
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
320
|
+
const allowsAny = rightTypes.has(exports.ELScalar.Array) || rightTypes.has(exports.ELScalar.Any);
|
|
321
|
+
if (!allowsAny && ![...rightTypes].some(x => x.endsWith('[]'))) {
|
|
322
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${exports.ELScalar.Array}</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
323
|
+
}
|
|
324
|
+
else if (!allowsAny && !rightTypes.has(`${exports.ELScalar.Any}[]`) && ![...leftTypes].some(type => rightTypes.has(`${type}[]`))) {
|
|
325
|
+
diagnostics.push({ from: leftArgument.from, to: rightArgument.to, severity: 'warning', message: `Expression is always <code>false</code> because <code>${[...leftTypes].join('|')}</code> not found in <code>${[...rightTypes].join('|')}</code>` });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else if (["contains", "starts with", "ends with", "matches"].includes(operator)) {
|
|
329
|
+
// Both sides must be string
|
|
330
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
331
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
332
|
+
if (!leftTypes.has(exports.ELScalar.String)) {
|
|
333
|
+
diagnostics.push({ from: leftArgument.from, to: leftArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...leftTypes].join('|')}</code>` });
|
|
334
|
+
}
|
|
335
|
+
if (!rightTypes.has(exports.ELScalar.String)) {
|
|
336
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
299
337
|
}
|
|
300
338
|
}
|
|
301
339
|
}
|
|
302
340
|
break;
|
|
341
|
+
}
|
|
303
342
|
}
|
|
304
343
|
if (identifier && ((_f = node.node.parent) === null || _f === void 0 ? void 0 : _f.type.isError)) {
|
|
305
344
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected identifier <code>${identifier}</code>` });
|
|
@@ -371,7 +410,7 @@ function completeIdentifier(state, config, tree, from, to) {
|
|
|
371
410
|
validFor: /^[a-zA-Z_]+[a-zA-Z_0-9]*$/,
|
|
372
411
|
};
|
|
373
412
|
}
|
|
374
|
-
function completeMember(state, config, tree, from, to
|
|
413
|
+
function completeMember(state, config, tree, from, to) {
|
|
375
414
|
var _a, _b, _c, _d, _e, _f;
|
|
376
415
|
if (!(((_a = tree.parent) === null || _a === void 0 ? void 0 : _a.type.is(PropertyAccess)) || ((_b = tree.parent) === null || _b === void 0 ? void 0 : _b.type.is(MethodAccess))) || !((_c = tree.parent) === null || _c === void 0 ? void 0 : _c.firstChild)) {
|
|
377
416
|
return null;
|
|
@@ -380,7 +419,7 @@ function completeMember(state, config, tree, from, to, explicit) {
|
|
|
380
419
|
if (!(types === null || types === void 0 ? void 0 : types.size)) {
|
|
381
420
|
return null;
|
|
382
421
|
}
|
|
383
|
-
|
|
422
|
+
const options = [];
|
|
384
423
|
for (const type of types) {
|
|
385
424
|
const typeDeclaration = (_d = config.types) === null || _d === void 0 ? void 0 : _d[type];
|
|
386
425
|
options.push(...(((_e = typeDeclaration === null || typeDeclaration === void 0 ? void 0 : typeDeclaration.identifiers) === null || _e === void 0 ? void 0 : _e.map(autocompleteIdentifier)) || []), ...(((_f = typeDeclaration === null || typeDeclaration === void 0 ? void 0 : typeDeclaration.functions) === null || _f === void 0 ? void 0 : _f.map(autocompleteFunction)) || []));
|
|
@@ -442,7 +481,6 @@ function resolveArguments(node) {
|
|
|
442
481
|
}
|
|
443
482
|
function getCursorTooltips(state) {
|
|
444
483
|
const config = getExpressionLanguageConfig(state);
|
|
445
|
-
// @ts-ignore
|
|
446
484
|
return state.selection.ranges
|
|
447
485
|
.filter(range => range.empty)
|
|
448
486
|
.map(range => {
|
|
@@ -468,7 +506,7 @@ function getCursorTooltips(state) {
|
|
|
468
506
|
strictSide: false,
|
|
469
507
|
arrow: true,
|
|
470
508
|
create: () => {
|
|
471
|
-
|
|
509
|
+
const dom = document.createElement("div");
|
|
472
510
|
dom.className = "cm-tooltip-cursor";
|
|
473
511
|
dom.textContent = `${argName}`;
|
|
474
512
|
return { dom };
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { insertCompletionText } from '@codemirror/autocomplete';
|
|
|
6
6
|
import { StateField } from '@codemirror/state';
|
|
7
7
|
import { showTooltip, hoverTooltip, EditorView } from '@codemirror/view';
|
|
8
8
|
|
|
9
|
-
// @ts-
|
|
9
|
+
// @ts-expect-error TS2739
|
|
10
10
|
const t = {
|
|
11
11
|
deserialize: (str) => str,
|
|
12
12
|
};
|
|
@@ -114,7 +114,7 @@ const resolveIdentifier = (nodeTypeId, identifier, config) => {
|
|
|
114
114
|
};
|
|
115
115
|
function resolveTypes(state, node, config) {
|
|
116
116
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
117
|
-
|
|
117
|
+
const types = new Set();
|
|
118
118
|
if (!node) {
|
|
119
119
|
return types;
|
|
120
120
|
}
|
|
@@ -127,19 +127,19 @@ function resolveTypes(state, node, config) {
|
|
|
127
127
|
}
|
|
128
128
|
else if (node.type.is(Variable)) {
|
|
129
129
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
130
|
-
// @ts-
|
|
130
|
+
// @ts-expect-error TS2339
|
|
131
131
|
(_b = (_a = resolveIdentifier(node.type.id, varName, config)) === null || _a === void 0 ? void 0 : _a.type) === null || _b === void 0 ? void 0 : _b.forEach((x) => types.add(x));
|
|
132
132
|
}
|
|
133
133
|
else if (node.type.is(Function)) {
|
|
134
134
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
135
|
-
// @ts-
|
|
135
|
+
// @ts-expect-error TS2339
|
|
136
136
|
(_d = (_c = resolveIdentifier(node.type.id, varName, config)) === null || _c === void 0 ? void 0 : _c.returnType) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
137
137
|
}
|
|
138
138
|
else if (node.type.is(PropertyAccess) && node.firstChild && ((_e = node.lastChild) === null || _e === void 0 ? void 0 : _e.type.is(Property))) {
|
|
139
139
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
140
140
|
(_f = resolveTypes(state, node.firstChild, config)) === null || _f === void 0 ? void 0 : _f.forEach(baseType => {
|
|
141
141
|
var _a, _b, _c, _d;
|
|
142
|
-
// @ts-
|
|
142
|
+
// @ts-expect-error TS2339
|
|
143
143
|
(_d = (_c = resolveIdentifier((_a = node.lastChild) === null || _a === void 0 ? void 0 : _a.type.id, varName, (_b = config.types) === null || _b === void 0 ? void 0 : _b[baseType])) === null || _c === void 0 ? void 0 : _c.type) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
144
144
|
});
|
|
145
145
|
}
|
|
@@ -147,10 +147,22 @@ function resolveTypes(state, node, config) {
|
|
|
147
147
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
148
148
|
(_h = resolveTypes(state, node.firstChild, config)) === null || _h === void 0 ? void 0 : _h.forEach(baseType => {
|
|
149
149
|
var _a, _b, _c, _d;
|
|
150
|
-
// @ts-
|
|
150
|
+
// @ts-expect-error TS2339
|
|
151
151
|
(_d = (_c = resolveIdentifier((_a = node.lastChild) === null || _a === void 0 ? void 0 : _a.type.id, varName, (_b = config.types) === null || _b === void 0 ? void 0 : _b[baseType])) === null || _c === void 0 ? void 0 : _c.returnType) === null || _d === void 0 ? void 0 : _d.forEach((x) => types.add(x));
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
+
// Array indexing: for typed arrays (e.g. Foo[]) return element type, for generic arrays return any
|
|
155
|
+
else if (node.type.is(ArrayAccess) && node.firstChild) {
|
|
156
|
+
const left = node.firstChild.node;
|
|
157
|
+
resolveTypes(state, left, config).forEach(baseType => {
|
|
158
|
+
if (baseType.endsWith('[]')) {
|
|
159
|
+
types.add(baseType.slice(0, -2));
|
|
160
|
+
}
|
|
161
|
+
else if (baseType === ELScalar.Array) {
|
|
162
|
+
types.add(ELScalar.Any);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
154
166
|
else if (node.type.is(Application) && node.firstChild) {
|
|
155
167
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
156
168
|
}
|
|
@@ -219,13 +231,13 @@ var utils = /*#__PURE__*//*@__PURE__*/Object.freeze({
|
|
|
219
231
|
*/
|
|
220
232
|
const expressionLanguageLinterSource = (state) => {
|
|
221
233
|
const config = getExpressionLanguageConfig(state);
|
|
222
|
-
|
|
234
|
+
const diagnostics = [];
|
|
223
235
|
syntaxTree(state).cursor().iterate(node => {
|
|
224
236
|
var _a, _b, _c, _d, _e, _f;
|
|
225
237
|
const { from, to, type: { id } } = node;
|
|
226
238
|
let identifier;
|
|
227
239
|
switch (id) {
|
|
228
|
-
case 0:
|
|
240
|
+
case 0: {
|
|
229
241
|
if (state.doc.length === 0 || from === 0) {
|
|
230
242
|
// Don't show error on empty doc (even though it is an error)
|
|
231
243
|
return;
|
|
@@ -239,7 +251,8 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
239
251
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected ${type} <code>${identifier}</code>` });
|
|
240
252
|
}
|
|
241
253
|
return;
|
|
242
|
-
|
|
254
|
+
}
|
|
255
|
+
case Arguments: {
|
|
243
256
|
const fn = resolveFunctionDefinition(node.node.prevSibling, state, config);
|
|
244
257
|
const args = fn === null || fn === void 0 ? void 0 : fn.args;
|
|
245
258
|
if (!args) {
|
|
@@ -260,7 +273,12 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
260
273
|
const typesUsed = Array.from(resolveTypes(state, n, config));
|
|
261
274
|
const typesExpected = args[i].type;
|
|
262
275
|
if (typesExpected && !typesExpected.includes(ELScalar.Any) && !typesUsed.some(x => typesExpected.includes(x))) {
|
|
263
|
-
diagnostics.push({
|
|
276
|
+
diagnostics.push({
|
|
277
|
+
from: n.from,
|
|
278
|
+
to: n.to,
|
|
279
|
+
severity: 'error',
|
|
280
|
+
message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>`
|
|
281
|
+
});
|
|
264
282
|
}
|
|
265
283
|
i++;
|
|
266
284
|
}
|
|
@@ -268,8 +286,9 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
268
286
|
diagnostics.push({ from: node.from, to: node.to, severity: 'error', message: `Too few arguments – ${argumentCountHintFn()}` });
|
|
269
287
|
}
|
|
270
288
|
break;
|
|
289
|
+
}
|
|
271
290
|
case Property:
|
|
272
|
-
case Method:
|
|
291
|
+
case Method: {
|
|
273
292
|
const leftArgument = (_e = (_d = node.node.parent) === null || _d === void 0 ? void 0 : _d.firstChild) === null || _e === void 0 ? void 0 : _e.node;
|
|
274
293
|
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
275
294
|
identifier = state.sliceDoc(from, to);
|
|
@@ -277,26 +296,46 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
277
296
|
diagnostics.push({ from, to, severity: 'error', message: `${node.name} <code>${identifier}</code> not found in <code>${types.join('|')}</code>` });
|
|
278
297
|
}
|
|
279
298
|
break;
|
|
299
|
+
}
|
|
280
300
|
case Variable:
|
|
281
|
-
case Function:
|
|
301
|
+
case Function: {
|
|
282
302
|
identifier = state.sliceDoc(from, node.node.firstChild ? node.node.firstChild.from - 1 : to);
|
|
283
303
|
if (!resolveIdentifier(id, identifier, config)) {
|
|
284
304
|
diagnostics.push({ from, to, severity: 'error', message: `${node.node.name} <code>${identifier}</code> not found` });
|
|
285
305
|
}
|
|
286
306
|
break;
|
|
287
|
-
|
|
307
|
+
}
|
|
308
|
+
case BinaryExpression: {
|
|
288
309
|
const operatorNode = node.node.getChild(OperatorKeyword);
|
|
289
310
|
if (operatorNode) {
|
|
290
311
|
const operator = state.sliceDoc(operatorNode.from, operatorNode.to);
|
|
312
|
+
const leftArgument = node.node.firstChild;
|
|
313
|
+
const rightArgument = node.node.lastChild;
|
|
291
314
|
if (operator === 'in') {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
315
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
316
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
317
|
+
const allowsAny = rightTypes.has(ELScalar.Array) || rightTypes.has(ELScalar.Any);
|
|
318
|
+
if (!allowsAny && ![...rightTypes].some(x => x.endsWith('[]'))) {
|
|
319
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${ELScalar.Array}</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
320
|
+
}
|
|
321
|
+
else if (!allowsAny && !rightTypes.has(`${ELScalar.Any}[]`) && ![...leftTypes].some(type => rightTypes.has(`${type}[]`))) {
|
|
322
|
+
diagnostics.push({ from: leftArgument.from, to: rightArgument.to, severity: 'warning', message: `Expression is always <code>false</code> because <code>${[...leftTypes].join('|')}</code> not found in <code>${[...rightTypes].join('|')}</code>` });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (["contains", "starts with", "ends with", "matches"].includes(operator)) {
|
|
326
|
+
// Both sides must be string
|
|
327
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
328
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
329
|
+
if (!leftTypes.has(ELScalar.String)) {
|
|
330
|
+
diagnostics.push({ from: leftArgument.from, to: leftArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...leftTypes].join('|')}</code>` });
|
|
331
|
+
}
|
|
332
|
+
if (!rightTypes.has(ELScalar.String)) {
|
|
333
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
296
334
|
}
|
|
297
335
|
}
|
|
298
336
|
}
|
|
299
337
|
break;
|
|
338
|
+
}
|
|
300
339
|
}
|
|
301
340
|
if (identifier && ((_f = node.node.parent) === null || _f === void 0 ? void 0 : _f.type.isError)) {
|
|
302
341
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected identifier <code>${identifier}</code>` });
|
|
@@ -368,7 +407,7 @@ function completeIdentifier(state, config, tree, from, to) {
|
|
|
368
407
|
validFor: /^[a-zA-Z_]+[a-zA-Z_0-9]*$/,
|
|
369
408
|
};
|
|
370
409
|
}
|
|
371
|
-
function completeMember(state, config, tree, from, to
|
|
410
|
+
function completeMember(state, config, tree, from, to) {
|
|
372
411
|
var _a, _b, _c, _d, _e, _f;
|
|
373
412
|
if (!(((_a = tree.parent) === null || _a === void 0 ? void 0 : _a.type.is(PropertyAccess)) || ((_b = tree.parent) === null || _b === void 0 ? void 0 : _b.type.is(MethodAccess))) || !((_c = tree.parent) === null || _c === void 0 ? void 0 : _c.firstChild)) {
|
|
374
413
|
return null;
|
|
@@ -377,7 +416,7 @@ function completeMember(state, config, tree, from, to, explicit) {
|
|
|
377
416
|
if (!(types === null || types === void 0 ? void 0 : types.size)) {
|
|
378
417
|
return null;
|
|
379
418
|
}
|
|
380
|
-
|
|
419
|
+
const options = [];
|
|
381
420
|
for (const type of types) {
|
|
382
421
|
const typeDeclaration = (_d = config.types) === null || _d === void 0 ? void 0 : _d[type];
|
|
383
422
|
options.push(...(((_e = typeDeclaration === null || typeDeclaration === void 0 ? void 0 : typeDeclaration.identifiers) === null || _e === void 0 ? void 0 : _e.map(autocompleteIdentifier)) || []), ...(((_f = typeDeclaration === null || typeDeclaration === void 0 ? void 0 : typeDeclaration.functions) === null || _f === void 0 ? void 0 : _f.map(autocompleteFunction)) || []));
|
|
@@ -439,7 +478,6 @@ function resolveArguments(node) {
|
|
|
439
478
|
}
|
|
440
479
|
function getCursorTooltips(state) {
|
|
441
480
|
const config = getExpressionLanguageConfig(state);
|
|
442
|
-
// @ts-ignore
|
|
443
481
|
return state.selection.ranges
|
|
444
482
|
.filter(range => range.empty)
|
|
445
483
|
.map(range => {
|
|
@@ -465,7 +503,7 @@ function getCursorTooltips(state) {
|
|
|
465
503
|
strictSide: false,
|
|
466
504
|
arrow: true,
|
|
467
505
|
create: () => {
|
|
468
|
-
|
|
506
|
+
const dom = document.createElement("div");
|
|
469
507
|
dom.className = "cm-tooltip-cursor";
|
|
470
508
|
dom.textContent = `${argName}`;
|
|
471
509
|
return { dom };
|
package/package.json
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
"scripts": {
|
|
5
5
|
"pretest": "npm run-script prepare",
|
|
6
6
|
"test": "cm-runtests",
|
|
7
|
-
"prepare": "cm-buildhelper src/index.ts"
|
|
7
|
+
"prepare": "cm-buildhelper src/index.ts",
|
|
8
|
+
"lint-fix": "eslint . --fix --ext .ts,.js",
|
|
9
|
+
"lint": "eslint . --ext .ts,.js"
|
|
8
10
|
},
|
|
9
11
|
"type": "module",
|
|
10
12
|
"main": "dist/index.cjs",
|
|
@@ -28,7 +30,10 @@
|
|
|
28
30
|
"@codemirror/buildhelper": "^1.0.0",
|
|
29
31
|
"@types/mocha": "^10.0.10",
|
|
30
32
|
"@types/node": "^22.10.5",
|
|
31
|
-
"tsdoc-markdown": "^1.1.1"
|
|
33
|
+
"tsdoc-markdown": "^1.1.1",
|
|
34
|
+
"eslint": "^8.48.0",
|
|
35
|
+
"@typescript-eslint/parser": "^6.5.0",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^6.5.0"
|
|
32
37
|
},
|
|
33
38
|
"license": "MIT",
|
|
34
39
|
"repository": {
|
|
@@ -39,5 +44,5 @@
|
|
|
39
44
|
"access": "public",
|
|
40
45
|
"registry": "https://registry.npmjs.org/"
|
|
41
46
|
},
|
|
42
|
-
"version": "1.
|
|
47
|
+
"version": "1.3.0"
|
|
43
48
|
}
|