@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 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@0.1.0"></script>
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/turse2xq/)
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-ignore
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
- let types = new Set();
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-ignore
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-ignore
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-ignore
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-ignore
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
- let diagnostics = [];
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
- case Arguments:
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({ from: n.from, to: n.to, severity: 'error', message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>` });
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
- case BinaryExpression:
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 rightArgument = node.node.lastChild;
296
- const types = resolveTypes(state, rightArgument, config);
297
- if (!types.has(exports.ELScalar.Array)) {
298
- diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${exports.ELScalar.Array}</code> expected, got <code>${[...types].join('|')}</code>` });
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, explicit) {
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
- let options = [];
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
- let dom = document.createElement("div");
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-ignore
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
- let types = new Set();
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-ignore
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-ignore
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-ignore
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-ignore
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
- let diagnostics = [];
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
- case Arguments:
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({ from: n.from, to: n.to, severity: 'error', message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>` });
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
- case BinaryExpression:
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 rightArgument = node.node.lastChild;
293
- const types = resolveTypes(state, rightArgument, config);
294
- if (!types.has(ELScalar.Array)) {
295
- diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${ELScalar.Array}</code> expected, got <code>${[...types].join('|')}</code>` });
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, explicit) {
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
- let options = [];
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
- let dom = document.createElement("div");
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.1.0"
47
+ "version": "1.3.0"
43
48
  }