@valtzu/codemirror-lang-el 1.2.0 → 1.4.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 +14 -0
- package/dist/index.cjs +147 -53
- package/dist/index.js +147 -53
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
1.4
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
* Improve support for typed arrays
|
|
8
|
+
* Add support for null-safe array access
|
|
9
|
+
* Minor bugfixes & adjustments to autocomplete & linting
|
|
10
|
+
|
|
11
|
+
1.3
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
* Lint left- and right-side arguments of `contains`, `starts with`, `ends with` and `matches` operators
|
|
15
|
+
* Add eslint to make contributing/review easier
|
|
16
|
+
* Add type checking for left-side argument in `in` expression
|
|
17
|
+
|
|
4
18
|
1.2
|
|
5
19
|
---
|
|
6
20
|
|
package/dist/index.cjs
CHANGED
|
@@ -8,31 +8,31 @@ 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
|
};
|
|
15
15
|
|
|
16
16
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
17
|
-
const spec_word = {__proto__:null,true:
|
|
18
|
-
const spec_Operator = {__proto__:null,"?":
|
|
17
|
+
const spec_word = {__proto__:null,true:92, TRUE:94, false:96, FALSE:98, null:100, NULL:102, starts:114, with:116, ends:118, contains:120, matches:122, not:124, in:126, and:128, or:130, xor:132};
|
|
18
|
+
const spec_Operator = {__proto__:null,"?":113, "!":135, "~":137, "+":139, "-":141};
|
|
19
19
|
const parser = lr.LRParser.deserialize({
|
|
20
20
|
version: 14,
|
|
21
|
-
states: ")YOYQPOOP!aOPOOQ!
|
|
22
|
-
stateData: ",
|
|
23
|
-
goto: "%
|
|
24
|
-
nodeNames: "⚠ BlockComment Expression PropertyAccess MemberOf NullSafeMemberOf Property ArrayAccess Call Function Arguments MethodAccess Method Number String Boolean Null Object Array Variable TernaryExpression Operator BinaryExpression OperatorKeyword UnaryExpression UnaryOperator Application",
|
|
25
|
-
maxTerm:
|
|
21
|
+
states: ")YOYQPOOP!aOPOOQ!fQQOOO#gQQO'#CgO$}QPO'#CoO%UQPO'#CpOYQPO'#CvOYQPO'#CxOOQO'#DT'#DTO%]QPO'#CfOOQO'#Cm'#CmOOQO'#Cn'#CnOOQO'#Cw'#CwP%bOSO'#C]POOO)C>s)C>sO%mQPO,58yOYQPO,58}OYQPO,59`OYQPO,59^O%rQPO'#CuOOQO'#Cu'#CuO%wQPO'#CuO'RQQO,59ZOOQO,59Z,59ZO'YQQO'#DYO'gQPO,59[O'lQQO,59bO(SQQO,59dO(ZQPO'#ChOOQO,59Q,59QPOOO'#Cy'#CyP(bOSO,58wPOOO,58w,58wO(mQQO'#CbOOQO1G.e1G.eOOQO1G.o1G.oO*TQQO1G.iO*[QQO1G.zO*rQQO1G.xOOQO,59a,59aOYQPO1G.uOYQPO'#CzO*yQPO,59tOOQO1G.v1G.vOOQO1G/O1G/OO+UQPO,59SPOOO-E6w-E6wPOOO1G.c1G.cOOQO7+$T7+$TOYQPO7+$dO+ZQQO7+$aO+eQQO,59fOOQO-E6x-E6xOOQO1G.n1G.nO+rQQO<<HOOYQPO'#C{O,YQPO<<G{OOQO<<G{<<G{O,bQQO,59gOOQO-E6y-E6yOOQOAN=gAN=gOYQPO1G/RO,iQQO7+$m",
|
|
22
|
+
stateData: ",{~OrOSsPQ~O_WO`WOxRO{VO!OYO!PYO!QYO!RYO!SZO!TZO!VSO!XTO!`[O!e[O!f[O!g[O!h[O~Os]O~OS_OT_OW`OX`OgaO!YbO!ZcO!]cO!^dO!_dO!`eO!adO!bdO!cdO!ddO~OSeXTeXWeXXeXgeXpeX{ZX!YeX!ZeX!]eX!^eX!_eX!`eX!aeX!beX!ceX!deX!WeXyeX}eXzeX!UeX~O!UgO~PYOy|P~PYO{lO~OtnOunOvpO~OxqO~O![wO~O!awO_iX`iXxiX{iX!OiX!PiX!QiX!RiX!SiX!TiX!ViX!XiX!`iX!eiX!fiX!giX!hiX~O!WxO~P!fO}yOy|Xz|X~P!fOy{O~Opja!Wjayja}jazja!Uja~P!fOz|O~P!fOz|P~PYOtnOunOv!PO~OSUXTUXWUXXUXgUXpUX{^X!YUX!ZUX!]UX!^UX!_UX!`UX!aUX!bUX!cUX!dUX!WUXyUX}UXzUX!UUX~Oy!QO~P!fOphi!Whiyhi}hizhi!Uhi~P!fO!W!RO~P!fO}yOy|az|a~Oz!VO~O}!XO!U!ZO~P!fOyna}nazna~P!fOpfy!Wfyyfy}fyzfy!Ufy~P!fO}!XO!U!^O~O!W!_O~P!fO}oq!Uoq~P!fO`TgS_xS~",
|
|
23
|
+
goto: "%P}P!OP!RPP!c!RPP!R!f!v!f!yPP!R!R!R!R!R!RP!R!|!R#]!R#m#s#yPPPPPPP$PPPPP$yR^PmWOSTUV`ablxy!R!X!_Rr_mXOSTUV`ablxy!R!X!_RmXRs_kaQfhjktuv!S!T!W![!`mUOSTUV`ablxy!R!X!_Qo]R!OoQzhR!UzQ!Y!SR!]!YQQOQfSShTlQjUQkVQt`QuaQvbQ!SxQ!TyQ!W!RQ![!XR!`!_QiTR}l",
|
|
24
|
+
nodeNames: "⚠ BlockComment Expression PropertyAccess MemberOf NullSafeMemberOf Property ArrayAccess ArrayAccessor NullSafeArrayAccessor Call Function Arguments MethodAccess Method Number String Boolean Null Object Array Variable TernaryExpression Operator BinaryExpression OperatorKeyword UnaryExpression UnaryOperator Application",
|
|
25
|
+
maxTerm: 70,
|
|
26
26
|
nodeProps: [
|
|
27
|
-
[t,
|
|
27
|
+
[t, 15,"number",16,"string",17,"bool",18,"null",19,"object",20,"array"]
|
|
28
28
|
],
|
|
29
|
-
skippedNodes: [0,1,
|
|
29
|
+
skippedNodes: [0,1,29],
|
|
30
30
|
repeatNodeCount: 3,
|
|
31
|
-
tokenData: "-
|
|
32
|
-
tokenizers: [1, new lr.LocalTokenGroup("j~RQYZXz{^~^
|
|
31
|
+
tokenData: "-k~RqXY#YYZ#Y]^#Ypq#Yqr#krs$Quv#{vw%zwx&Sxy'wyz'|z{(R{|#{|}(Z}!O#{!O!P(`!P!Q(h!Q![(u![!]+[!^!_+a!_!`+l!`!a+r!a!b+}!c!},j!}#O,{#P#Q-S#Q#R#{#R#S,j#T#o,j#o#p-X#p#q-^#q#r-f#r#s#{~#_Sr~XY#YYZ#Y]^#Ypq#Y~#pPg~!_!`#s~#xPg~!_!`#{~$QOg~~$TXOr$Qrs$ps#O$Q#O#P$u#P;'S$Q;'S;=`%t<%l~$Q~O$Q~~$p~$uO`~~$xRO;'S$Q;'S;=`%R;=`O$Q~%UYOr$Qrs$ps#O$Q#O#P$u#P;'S$Q;'S;=`%t;=`<%l$Q<%l~$Q~O$Q~~$p~%wP;=`<%l$Q~&PPg~vw#{~&VXOw&Swx$px#O&S#O#P&r#P;'S&S;'S;=`'q<%l~&S~O&S~~$p~&uRO;'S&S;'S;=`'O;=`O&S~'RYOw&Swx$px#O&S#O#P&r#P;'S&S;'S;=`'q;=`<%l&S<%l~&S~O&S~~$p~'tP;=`<%l&S~'|O{~~(ROz~~(WPg~z{#{~(`O}~~(ePS~!O!P#{~(mPg~z{(p~(uOs~~(zT_~!O!P)Z!Q![(u!g!h*Z#R#S+U#X#Y*Z~)^SO!O)j!P;'S)j;'S;=`+O<%lO)j~)oR_~!Q![)x!g!h*Z#X#Y*Z~)}S_~!Q![)x!g!h*Z#R#S*x#X#Y*Z~*^R{|*g}!O*g!Q![*m~*jP!Q![*m~*rQ_~!Q![*m#R#S*g~*{P!Q![)x~+RP;=`<%l)j~+XP!Q![(u~+aO!W~~+fQg~!^!_#{!_!`#{~+oP!_!`#s~+wQg~!_!`#{!`!a#{~,SRg~!O!P,]![!]#{!a!b#{~,bPT~!}#O,e~,jOX~~,oSx~!Q![,j!c!},j#R#S,j#T#o,jU-SO!XQWS~-XOy~~-^O!V~~-cPg~#p#q#{~-kO!U~",
|
|
32
|
+
tokenizers: [1, 2, new lr.LocalTokenGroup("j~RQYZXz{^~^Ou~~aP!P!Qd~iOv~~", 25, 36)],
|
|
33
33
|
topRules: {"Expression":[0,2]},
|
|
34
|
-
specialized: [{term:
|
|
35
|
-
tokenPrec:
|
|
34
|
+
specialized: [{term: 40, get: (value) => spec_word[value] || -1},{term: 23, get: (value) => spec_Operator[value] || -1}],
|
|
35
|
+
tokenPrec: 541
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
// generate CONFIGURATION.md from this file by running "tsdoc --src=src/types.ts --dest=CONFIGURATION.md --noemoji --types"
|
|
@@ -56,21 +56,23 @@ exports.ELScalar = void 0;
|
|
|
56
56
|
const BlockComment = 1,
|
|
57
57
|
Expression = 2,
|
|
58
58
|
PropertyAccess = 3,
|
|
59
|
+
NullSafeMemberOf = 5,
|
|
59
60
|
Property = 6,
|
|
60
61
|
ArrayAccess = 7,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
NullSafeArrayAccessor = 9,
|
|
63
|
+
Call = 10,
|
|
64
|
+
Function = 11,
|
|
65
|
+
Arguments = 12,
|
|
66
|
+
MethodAccess = 13,
|
|
67
|
+
Method = 14,
|
|
68
|
+
String = 16,
|
|
69
|
+
Array$1 = 20,
|
|
70
|
+
Variable = 21,
|
|
71
|
+
TernaryExpression = 22,
|
|
72
|
+
BinaryExpression = 24,
|
|
73
|
+
OperatorKeyword = 25,
|
|
74
|
+
UnaryExpression = 26,
|
|
75
|
+
Application = 28;
|
|
74
76
|
|
|
75
77
|
const createInfoElement = (html) => {
|
|
76
78
|
const dom = document.createElement("div");
|
|
@@ -94,7 +96,7 @@ function resolveFunctionDefinition(node, state, config) {
|
|
|
94
96
|
let identifier;
|
|
95
97
|
if ((node.type.is(PropertyAccess) || node.type.is(MethodAccess)) && node.lastChild) {
|
|
96
98
|
const leftArgument = (_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.node;
|
|
97
|
-
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
99
|
+
const types = window.Array.from(resolveTypes(state, leftArgument, config));
|
|
98
100
|
identifier = state.sliceDoc(node.lastChild.from, node.lastChild.to);
|
|
99
101
|
return types.map(type => { var _a; return resolveCallable(identifier, (_a = config.types) === null || _a === void 0 ? void 0 : _a[type]); }).find(x => x);
|
|
100
102
|
}
|
|
@@ -117,42 +119,70 @@ const resolveIdentifier = (nodeTypeId, identifier, config) => {
|
|
|
117
119
|
};
|
|
118
120
|
function resolveTypes(state, node, config) {
|
|
119
121
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
120
|
-
|
|
122
|
+
const types = new Set();
|
|
121
123
|
if (!node) {
|
|
122
124
|
return types;
|
|
123
125
|
}
|
|
124
126
|
let type;
|
|
125
127
|
if (typeof (type = node.type.prop(t)) !== "undefined") {
|
|
126
|
-
|
|
128
|
+
if (type === exports.ELScalar.Array) {
|
|
129
|
+
// For array literals, infer element types from array contents
|
|
130
|
+
const elementTypes = new Set();
|
|
131
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
132
|
+
if (!child.type.isError) {
|
|
133
|
+
resolveTypes(state, child, config).forEach(elementType => {
|
|
134
|
+
elementTypes.add(elementType);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (elementTypes.size > 0) {
|
|
139
|
+
// Build typed array notation (e.g., "string[]")
|
|
140
|
+
elementTypes.forEach(elementType => {
|
|
141
|
+
types.add(`${elementType}[]`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
types.add(type);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
types.add(type);
|
|
150
|
+
}
|
|
127
151
|
}
|
|
128
152
|
else if (node.type.is(Call) && node.firstChild && node.lastChild) {
|
|
129
153
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
130
154
|
}
|
|
131
155
|
else if (node.type.is(Variable)) {
|
|
132
156
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
133
|
-
// @ts-
|
|
157
|
+
// @ts-expect-error TS2339
|
|
134
158
|
(_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
159
|
}
|
|
136
160
|
else if (node.type.is(Function)) {
|
|
137
161
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
138
|
-
// @ts-
|
|
162
|
+
// @ts-expect-error TS2339
|
|
139
163
|
(_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
164
|
}
|
|
141
165
|
else if (node.type.is(PropertyAccess) && node.firstChild && ((_e = node.lastChild) === null || _e === void 0 ? void 0 : _e.type.is(Property))) {
|
|
142
166
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
143
167
|
(_f = resolveTypes(state, node.firstChild, config)) === null || _f === void 0 ? void 0 : _f.forEach(baseType => {
|
|
144
168
|
var _a, _b, _c, _d;
|
|
145
|
-
// @ts-
|
|
169
|
+
// @ts-expect-error TS2339
|
|
146
170
|
(_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
171
|
});
|
|
172
|
+
if (node.getChild(NullSafeMemberOf)) {
|
|
173
|
+
types.add(exports.ELScalar.Null);
|
|
174
|
+
}
|
|
148
175
|
}
|
|
149
176
|
else if (node.type.is(MethodAccess) && node.firstChild && ((_g = node.lastChild) === null || _g === void 0 ? void 0 : _g.type.is(Method))) {
|
|
150
177
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
151
178
|
(_h = resolveTypes(state, node.firstChild, config)) === null || _h === void 0 ? void 0 : _h.forEach(baseType => {
|
|
152
179
|
var _a, _b, _c, _d;
|
|
153
|
-
// @ts-
|
|
180
|
+
// @ts-expect-error TS2339
|
|
154
181
|
(_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
182
|
});
|
|
183
|
+
if (node.getChild(NullSafeMemberOf)) {
|
|
184
|
+
types.add(exports.ELScalar.Null);
|
|
185
|
+
}
|
|
156
186
|
}
|
|
157
187
|
// Array indexing: for typed arrays (e.g. Foo[]) return element type, for generic arrays return any
|
|
158
188
|
else if (node.type.is(ArrayAccess) && node.firstChild) {
|
|
@@ -165,6 +195,9 @@ function resolveTypes(state, node, config) {
|
|
|
165
195
|
types.add(exports.ELScalar.Any);
|
|
166
196
|
}
|
|
167
197
|
});
|
|
198
|
+
if (node.getChild(NullSafeArrayAccessor)) {
|
|
199
|
+
types.add(exports.ELScalar.Null);
|
|
200
|
+
}
|
|
168
201
|
}
|
|
169
202
|
else if (node.type.is(Application) && node.firstChild) {
|
|
170
203
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
@@ -234,13 +267,13 @@ var utils = /*#__PURE__*/Object.freeze({
|
|
|
234
267
|
*/
|
|
235
268
|
const expressionLanguageLinterSource = (state) => {
|
|
236
269
|
const config = getExpressionLanguageConfig(state);
|
|
237
|
-
|
|
270
|
+
const diagnostics = [];
|
|
238
271
|
language.syntaxTree(state).cursor().iterate(node => {
|
|
239
|
-
var _a, _b, _c, _d, _e, _f;
|
|
272
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
240
273
|
const { from, to, type: { id } } = node;
|
|
241
274
|
let identifier;
|
|
242
275
|
switch (id) {
|
|
243
|
-
case 0:
|
|
276
|
+
case 0: {
|
|
244
277
|
if (state.doc.length === 0 || from === 0) {
|
|
245
278
|
// Don't show error on empty doc (even though it is an error)
|
|
246
279
|
return;
|
|
@@ -254,7 +287,8 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
254
287
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected ${type} <code>${identifier}</code>` });
|
|
255
288
|
}
|
|
256
289
|
return;
|
|
257
|
-
|
|
290
|
+
}
|
|
291
|
+
case Arguments: {
|
|
258
292
|
const fn = resolveFunctionDefinition(node.node.prevSibling, state, config);
|
|
259
293
|
const args = fn === null || fn === void 0 ? void 0 : fn.args;
|
|
260
294
|
if (!args) {
|
|
@@ -275,7 +309,12 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
275
309
|
const typesUsed = Array.from(resolveTypes(state, n, config));
|
|
276
310
|
const typesExpected = args[i].type;
|
|
277
311
|
if (typesExpected && !typesExpected.includes(exports.ELScalar.Any) && !typesUsed.some(x => typesExpected.includes(x))) {
|
|
278
|
-
diagnostics.push({
|
|
312
|
+
diagnostics.push({
|
|
313
|
+
from: n.from,
|
|
314
|
+
to: n.to,
|
|
315
|
+
severity: 'error',
|
|
316
|
+
message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>`
|
|
317
|
+
});
|
|
279
318
|
}
|
|
280
319
|
i++;
|
|
281
320
|
}
|
|
@@ -283,37 +322,93 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
283
322
|
diagnostics.push({ from: node.from, to: node.to, severity: 'error', message: `Too few arguments – ${argumentCountHintFn()}` });
|
|
284
323
|
}
|
|
285
324
|
break;
|
|
325
|
+
}
|
|
286
326
|
case Property:
|
|
287
|
-
case Method:
|
|
288
|
-
const
|
|
327
|
+
case Method: {
|
|
328
|
+
const parent = node.node.parent;
|
|
329
|
+
const isPropertyAccess = parent === null || parent === void 0 ? void 0 : parent.type.is(PropertyAccess);
|
|
330
|
+
const isMethodAccess = parent === null || parent === void 0 ? void 0 : parent.type.is(MethodAccess);
|
|
331
|
+
// Skip validation if this is part of a PropertyAccess/MethodAccess on array type
|
|
332
|
+
// (the PropertyAccess/MethodAccess case will handle the error)
|
|
333
|
+
if ((isPropertyAccess || isMethodAccess) && (parent === null || parent === void 0 ? void 0 : parent.firstChild)) {
|
|
334
|
+
const types = Array.from(resolveTypes(state, parent.firstChild.node, config));
|
|
335
|
+
const hasArrayType = types.includes(exports.ELScalar.Array) || types.some(x => x.endsWith('[]'));
|
|
336
|
+
if (hasArrayType && !types.includes(exports.ELScalar.Any)) {
|
|
337
|
+
break; // Skip property validation, error will be reported by PropertyAccess/MethodAccess case
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const leftArgument = (_d = parent === null || parent === void 0 ? void 0 : parent.firstChild) === null || _d === void 0 ? void 0 : _d.node;
|
|
289
341
|
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
290
342
|
identifier = state.sliceDoc(from, to);
|
|
291
343
|
if (!types.find(type => { var _a; return resolveIdentifier(id, identifier, (_a = config.types) === null || _a === void 0 ? void 0 : _a[type]); })) {
|
|
292
344
|
diagnostics.push({ from, to, severity: 'error', message: `${node.name} <code>${identifier}</code> not found in <code>${types.join('|')}</code>` });
|
|
293
345
|
}
|
|
294
346
|
break;
|
|
347
|
+
}
|
|
348
|
+
case PropertyAccess:
|
|
349
|
+
case MethodAccess: {
|
|
350
|
+
const leftArgument = (_e = node.node.firstChild) === null || _e === void 0 ? void 0 : _e.node;
|
|
351
|
+
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
352
|
+
const hasArrayType = types.includes(exports.ELScalar.Array) || types.some(x => x.endsWith('[]'));
|
|
353
|
+
if (hasArrayType && !types.includes(exports.ELScalar.Any)) {
|
|
354
|
+
const propertyOrMethod = node.node.lastChild;
|
|
355
|
+
const errorFrom = (_f = propertyOrMethod === null || propertyOrMethod === void 0 ? void 0 : propertyOrMethod.from) !== null && _f !== void 0 ? _f : from;
|
|
356
|
+
const errorTo = (_g = propertyOrMethod === null || propertyOrMethod === void 0 ? void 0 : propertyOrMethod.to) !== null && _g !== void 0 ? _g : to;
|
|
357
|
+
diagnostics.push({ from: errorFrom, to: errorTo, severity: 'error', message: `Unexpected object access on <code>${types.join('|')}</code>` });
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case ArrayAccess: {
|
|
362
|
+
const leftArgument = (_h = node.node.firstChild) === null || _h === void 0 ? void 0 : _h.node;
|
|
363
|
+
const arrayAccessor = leftArgument.nextSibling;
|
|
364
|
+
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
365
|
+
const allowsAny = types.includes(exports.ELScalar.Array) || types.includes(exports.ELScalar.Any);
|
|
366
|
+
if (!allowsAny && ![...types].some(x => x.endsWith('[]'))) {
|
|
367
|
+
diagnostics.push({ from: arrayAccessor.from, to, severity: 'error', message: `Unexpected array access on <code>${types.join('|')}</code>` });
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
295
371
|
case Variable:
|
|
296
|
-
case Function:
|
|
372
|
+
case Function: {
|
|
297
373
|
identifier = state.sliceDoc(from, node.node.firstChild ? node.node.firstChild.from - 1 : to);
|
|
298
374
|
if (!resolveIdentifier(id, identifier, config)) {
|
|
299
375
|
diagnostics.push({ from, to, severity: 'error', message: `${node.node.name} <code>${identifier}</code> not found` });
|
|
300
376
|
}
|
|
301
377
|
break;
|
|
302
|
-
|
|
378
|
+
}
|
|
379
|
+
case BinaryExpression: {
|
|
303
380
|
const operatorNode = node.node.getChild(OperatorKeyword);
|
|
304
381
|
if (operatorNode) {
|
|
305
382
|
const operator = state.sliceDoc(operatorNode.from, operatorNode.to);
|
|
383
|
+
const leftArgument = node.node.firstChild;
|
|
384
|
+
const rightArgument = node.node.lastChild;
|
|
306
385
|
if (operator === 'in') {
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
386
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
387
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
388
|
+
const allowsAny = rightTypes.has(exports.ELScalar.Array) || rightTypes.has(exports.ELScalar.Any);
|
|
389
|
+
if (!allowsAny && ![...rightTypes].some(x => x.endsWith('[]'))) {
|
|
390
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${exports.ELScalar.Array}</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
391
|
+
}
|
|
392
|
+
else if (!allowsAny && !rightTypes.has(`${exports.ELScalar.Any}[]`) && ![...leftTypes].some(type => rightTypes.has(`${type}[]`))) {
|
|
393
|
+
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>` });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else if (["contains", "starts with", "ends with", "matches"].includes(operator)) {
|
|
397
|
+
// Both sides must be string
|
|
398
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
399
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
400
|
+
if (!leftTypes.has(exports.ELScalar.String)) {
|
|
401
|
+
diagnostics.push({ from: leftArgument.from, to: leftArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...leftTypes].join('|')}</code>` });
|
|
402
|
+
}
|
|
403
|
+
if (!rightTypes.has(exports.ELScalar.String)) {
|
|
404
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
311
405
|
}
|
|
312
406
|
}
|
|
313
407
|
}
|
|
314
408
|
break;
|
|
409
|
+
}
|
|
315
410
|
}
|
|
316
|
-
if (identifier && ((
|
|
411
|
+
if (identifier && ((_j = node.node.parent) === null || _j === void 0 ? void 0 : _j.type.isError)) {
|
|
317
412
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected identifier <code>${identifier}</code>` });
|
|
318
413
|
}
|
|
319
414
|
});
|
|
@@ -383,7 +478,7 @@ function completeIdentifier(state, config, tree, from, to) {
|
|
|
383
478
|
validFor: /^[a-zA-Z_]+[a-zA-Z_0-9]*$/,
|
|
384
479
|
};
|
|
385
480
|
}
|
|
386
|
-
function completeMember(state, config, tree, from, to
|
|
481
|
+
function completeMember(state, config, tree, from, to) {
|
|
387
482
|
var _a, _b, _c, _d, _e, _f;
|
|
388
483
|
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)) {
|
|
389
484
|
return null;
|
|
@@ -392,7 +487,7 @@ function completeMember(state, config, tree, from, to, explicit) {
|
|
|
392
487
|
if (!(types === null || types === void 0 ? void 0 : types.size)) {
|
|
393
488
|
return null;
|
|
394
489
|
}
|
|
395
|
-
|
|
490
|
+
const options = [];
|
|
396
491
|
for (const type of types) {
|
|
397
492
|
const typeDeclaration = (_d = config.types) === null || _d === void 0 ? void 0 : _d[type];
|
|
398
493
|
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)) || []));
|
|
@@ -409,7 +504,7 @@ function expressionLanguageCompletion(context) {
|
|
|
409
504
|
const { state, pos, explicit } = context;
|
|
410
505
|
const tree = language.syntaxTree(state);
|
|
411
506
|
const lastChar = state.sliceDoc(pos - 1, pos);
|
|
412
|
-
const prevNode = tree.resolveInner(pos, lastChar === ')' ? 0 : -1);
|
|
507
|
+
const prevNode = tree.resolveInner(pos, (lastChar === ')' || lastChar === ']') ? 0 : -1);
|
|
413
508
|
const config = getExpressionLanguageConfig(state);
|
|
414
509
|
const isIdentifier = (node) => (node === null || node === void 0 ? void 0 : node.type.is(Variable)) || (node === null || node === void 0 ? void 0 : node.type.is(Function));
|
|
415
510
|
const isMember = (node) => (node === null || node === void 0 ? void 0 : node.type.is(Property)) || (node === null || node === void 0 ? void 0 : node.type.is(Method));
|
|
@@ -454,7 +549,6 @@ function resolveArguments(node) {
|
|
|
454
549
|
}
|
|
455
550
|
function getCursorTooltips(state) {
|
|
456
551
|
const config = getExpressionLanguageConfig(state);
|
|
457
|
-
// @ts-ignore
|
|
458
552
|
return state.selection.ranges
|
|
459
553
|
.filter(range => range.empty)
|
|
460
554
|
.map(range => {
|
|
@@ -480,7 +574,7 @@ function getCursorTooltips(state) {
|
|
|
480
574
|
strictSide: false,
|
|
481
575
|
arrow: true,
|
|
482
576
|
create: () => {
|
|
483
|
-
|
|
577
|
+
const dom = document.createElement("div");
|
|
484
578
|
dom.className = "cm-tooltip-cursor";
|
|
485
579
|
dom.textContent = `${argName}`;
|
|
486
580
|
return { dom };
|
package/dist/index.js
CHANGED
|
@@ -6,31 +6,31 @@ 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
|
};
|
|
13
13
|
|
|
14
14
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
15
|
-
const spec_word = {__proto__:null,true:
|
|
16
|
-
const spec_Operator = {__proto__:null,"?":
|
|
15
|
+
const spec_word = {__proto__:null,true:92, TRUE:94, false:96, FALSE:98, null:100, NULL:102, starts:114, with:116, ends:118, contains:120, matches:122, not:124, in:126, and:128, or:130, xor:132};
|
|
16
|
+
const spec_Operator = {__proto__:null,"?":113, "!":135, "~":137, "+":139, "-":141};
|
|
17
17
|
const parser = /*@__PURE__*/LRParser.deserialize({
|
|
18
18
|
version: 14,
|
|
19
|
-
states: ")YOYQPOOP!aOPOOQ!
|
|
20
|
-
stateData: ",
|
|
21
|
-
goto: "%
|
|
22
|
-
nodeNames: "⚠ BlockComment Expression PropertyAccess MemberOf NullSafeMemberOf Property ArrayAccess Call Function Arguments MethodAccess Method Number String Boolean Null Object Array Variable TernaryExpression Operator BinaryExpression OperatorKeyword UnaryExpression UnaryOperator Application",
|
|
23
|
-
maxTerm:
|
|
19
|
+
states: ")YOYQPOOP!aOPOOQ!fQQOOO#gQQO'#CgO$}QPO'#CoO%UQPO'#CpOYQPO'#CvOYQPO'#CxOOQO'#DT'#DTO%]QPO'#CfOOQO'#Cm'#CmOOQO'#Cn'#CnOOQO'#Cw'#CwP%bOSO'#C]POOO)C>s)C>sO%mQPO,58yOYQPO,58}OYQPO,59`OYQPO,59^O%rQPO'#CuOOQO'#Cu'#CuO%wQPO'#CuO'RQQO,59ZOOQO,59Z,59ZO'YQQO'#DYO'gQPO,59[O'lQQO,59bO(SQQO,59dO(ZQPO'#ChOOQO,59Q,59QPOOO'#Cy'#CyP(bOSO,58wPOOO,58w,58wO(mQQO'#CbOOQO1G.e1G.eOOQO1G.o1G.oO*TQQO1G.iO*[QQO1G.zO*rQQO1G.xOOQO,59a,59aOYQPO1G.uOYQPO'#CzO*yQPO,59tOOQO1G.v1G.vOOQO1G/O1G/OO+UQPO,59SPOOO-E6w-E6wPOOO1G.c1G.cOOQO7+$T7+$TOYQPO7+$dO+ZQQO7+$aO+eQQO,59fOOQO-E6x-E6xOOQO1G.n1G.nO+rQQO<<HOOYQPO'#C{O,YQPO<<G{OOQO<<G{<<G{O,bQQO,59gOOQO-E6y-E6yOOQOAN=gAN=gOYQPO1G/RO,iQQO7+$m",
|
|
20
|
+
stateData: ",{~OrOSsPQ~O_WO`WOxRO{VO!OYO!PYO!QYO!RYO!SZO!TZO!VSO!XTO!`[O!e[O!f[O!g[O!h[O~Os]O~OS_OT_OW`OX`OgaO!YbO!ZcO!]cO!^dO!_dO!`eO!adO!bdO!cdO!ddO~OSeXTeXWeXXeXgeXpeX{ZX!YeX!ZeX!]eX!^eX!_eX!`eX!aeX!beX!ceX!deX!WeXyeX}eXzeX!UeX~O!UgO~PYOy|P~PYO{lO~OtnOunOvpO~OxqO~O![wO~O!awO_iX`iXxiX{iX!OiX!PiX!QiX!RiX!SiX!TiX!ViX!XiX!`iX!eiX!fiX!giX!hiX~O!WxO~P!fO}yOy|Xz|X~P!fOy{O~Opja!Wjayja}jazja!Uja~P!fOz|O~P!fOz|P~PYOtnOunOv!PO~OSUXTUXWUXXUXgUXpUX{^X!YUX!ZUX!]UX!^UX!_UX!`UX!aUX!bUX!cUX!dUX!WUXyUX}UXzUX!UUX~Oy!QO~P!fOphi!Whiyhi}hizhi!Uhi~P!fO!W!RO~P!fO}yOy|az|a~Oz!VO~O}!XO!U!ZO~P!fOyna}nazna~P!fOpfy!Wfyyfy}fyzfy!Ufy~P!fO}!XO!U!^O~O!W!_O~P!fO}oq!Uoq~P!fO`TgS_xS~",
|
|
21
|
+
goto: "%P}P!OP!RPP!c!RPP!R!f!v!f!yPP!R!R!R!R!R!RP!R!|!R#]!R#m#s#yPPPPPPP$PPPPP$yR^PmWOSTUV`ablxy!R!X!_Rr_mXOSTUV`ablxy!R!X!_RmXRs_kaQfhjktuv!S!T!W![!`mUOSTUV`ablxy!R!X!_Qo]R!OoQzhR!UzQ!Y!SR!]!YQQOQfSShTlQjUQkVQt`QuaQvbQ!SxQ!TyQ!W!RQ![!XR!`!_QiTR}l",
|
|
22
|
+
nodeNames: "⚠ BlockComment Expression PropertyAccess MemberOf NullSafeMemberOf Property ArrayAccess ArrayAccessor NullSafeArrayAccessor Call Function Arguments MethodAccess Method Number String Boolean Null Object Array Variable TernaryExpression Operator BinaryExpression OperatorKeyword UnaryExpression UnaryOperator Application",
|
|
23
|
+
maxTerm: 70,
|
|
24
24
|
nodeProps: [
|
|
25
|
-
[t,
|
|
25
|
+
[t, 15,"number",16,"string",17,"bool",18,"null",19,"object",20,"array"]
|
|
26
26
|
],
|
|
27
|
-
skippedNodes: [0,1,
|
|
27
|
+
skippedNodes: [0,1,29],
|
|
28
28
|
repeatNodeCount: 3,
|
|
29
|
-
tokenData: "-
|
|
30
|
-
tokenizers: [1, /*@__PURE__*/new LocalTokenGroup("j~RQYZXz{^~^
|
|
29
|
+
tokenData: "-k~RqXY#YYZ#Y]^#Ypq#Yqr#krs$Quv#{vw%zwx&Sxy'wyz'|z{(R{|#{|}(Z}!O#{!O!P(`!P!Q(h!Q![(u![!]+[!^!_+a!_!`+l!`!a+r!a!b+}!c!},j!}#O,{#P#Q-S#Q#R#{#R#S,j#T#o,j#o#p-X#p#q-^#q#r-f#r#s#{~#_Sr~XY#YYZ#Y]^#Ypq#Y~#pPg~!_!`#s~#xPg~!_!`#{~$QOg~~$TXOr$Qrs$ps#O$Q#O#P$u#P;'S$Q;'S;=`%t<%l~$Q~O$Q~~$p~$uO`~~$xRO;'S$Q;'S;=`%R;=`O$Q~%UYOr$Qrs$ps#O$Q#O#P$u#P;'S$Q;'S;=`%t;=`<%l$Q<%l~$Q~O$Q~~$p~%wP;=`<%l$Q~&PPg~vw#{~&VXOw&Swx$px#O&S#O#P&r#P;'S&S;'S;=`'q<%l~&S~O&S~~$p~&uRO;'S&S;'S;=`'O;=`O&S~'RYOw&Swx$px#O&S#O#P&r#P;'S&S;'S;=`'q;=`<%l&S<%l~&S~O&S~~$p~'tP;=`<%l&S~'|O{~~(ROz~~(WPg~z{#{~(`O}~~(ePS~!O!P#{~(mPg~z{(p~(uOs~~(zT_~!O!P)Z!Q![(u!g!h*Z#R#S+U#X#Y*Z~)^SO!O)j!P;'S)j;'S;=`+O<%lO)j~)oR_~!Q![)x!g!h*Z#X#Y*Z~)}S_~!Q![)x!g!h*Z#R#S*x#X#Y*Z~*^R{|*g}!O*g!Q![*m~*jP!Q![*m~*rQ_~!Q![*m#R#S*g~*{P!Q![)x~+RP;=`<%l)j~+XP!Q![(u~+aO!W~~+fQg~!^!_#{!_!`#{~+oP!_!`#s~+wQg~!_!`#{!`!a#{~,SRg~!O!P,]![!]#{!a!b#{~,bPT~!}#O,e~,jOX~~,oSx~!Q![,j!c!},j#R#S,j#T#o,jU-SO!XQWS~-XOy~~-^O!V~~-cPg~#p#q#{~-kO!U~",
|
|
30
|
+
tokenizers: [1, 2, /*@__PURE__*/new LocalTokenGroup("j~RQYZXz{^~^Ou~~aP!P!Qd~iOv~~", 25, 36)],
|
|
31
31
|
topRules: {"Expression":[0,2]},
|
|
32
|
-
specialized: [{term:
|
|
33
|
-
tokenPrec:
|
|
32
|
+
specialized: [{term: 40, get: (value) => spec_word[value] || -1},{term: 23, get: (value) => spec_Operator[value] || -1}],
|
|
33
|
+
tokenPrec: 541
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
// generate CONFIGURATION.md from this file by running "tsdoc --src=src/types.ts --dest=CONFIGURATION.md --noemoji --types"
|
|
@@ -53,21 +53,23 @@ return ELScalar})(ELScalar || (ELScalar = {}));
|
|
|
53
53
|
const BlockComment = 1,
|
|
54
54
|
Expression = 2,
|
|
55
55
|
PropertyAccess = 3,
|
|
56
|
+
NullSafeMemberOf = 5,
|
|
56
57
|
Property = 6,
|
|
57
58
|
ArrayAccess = 7,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
NullSafeArrayAccessor = 9,
|
|
60
|
+
Call = 10,
|
|
61
|
+
Function = 11,
|
|
62
|
+
Arguments = 12,
|
|
63
|
+
MethodAccess = 13,
|
|
64
|
+
Method = 14,
|
|
65
|
+
String = 16,
|
|
66
|
+
Array$1 = 20,
|
|
67
|
+
Variable = 21,
|
|
68
|
+
TernaryExpression = 22,
|
|
69
|
+
BinaryExpression = 24,
|
|
70
|
+
OperatorKeyword = 25,
|
|
71
|
+
UnaryExpression = 26,
|
|
72
|
+
Application = 28;
|
|
71
73
|
|
|
72
74
|
const createInfoElement = (html) => {
|
|
73
75
|
const dom = document.createElement("div");
|
|
@@ -91,7 +93,7 @@ function resolveFunctionDefinition(node, state, config) {
|
|
|
91
93
|
let identifier;
|
|
92
94
|
if ((node.type.is(PropertyAccess) || node.type.is(MethodAccess)) && node.lastChild) {
|
|
93
95
|
const leftArgument = (_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.node;
|
|
94
|
-
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
96
|
+
const types = window.Array.from(resolveTypes(state, leftArgument, config));
|
|
95
97
|
identifier = state.sliceDoc(node.lastChild.from, node.lastChild.to);
|
|
96
98
|
return types.map(type => { var _a; return resolveCallable(identifier, (_a = config.types) === null || _a === void 0 ? void 0 : _a[type]); }).find(x => x);
|
|
97
99
|
}
|
|
@@ -114,42 +116,70 @@ const resolveIdentifier = (nodeTypeId, identifier, config) => {
|
|
|
114
116
|
};
|
|
115
117
|
function resolveTypes(state, node, config) {
|
|
116
118
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
117
|
-
|
|
119
|
+
const types = new Set();
|
|
118
120
|
if (!node) {
|
|
119
121
|
return types;
|
|
120
122
|
}
|
|
121
123
|
let type;
|
|
122
124
|
if (typeof (type = node.type.prop(t)) !== "undefined") {
|
|
123
|
-
|
|
125
|
+
if (type === ELScalar.Array) {
|
|
126
|
+
// For array literals, infer element types from array contents
|
|
127
|
+
const elementTypes = new Set();
|
|
128
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
129
|
+
if (!child.type.isError) {
|
|
130
|
+
resolveTypes(state, child, config).forEach(elementType => {
|
|
131
|
+
elementTypes.add(elementType);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (elementTypes.size > 0) {
|
|
136
|
+
// Build typed array notation (e.g., "string[]")
|
|
137
|
+
elementTypes.forEach(elementType => {
|
|
138
|
+
types.add(`${elementType}[]`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
types.add(type);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
types.add(type);
|
|
147
|
+
}
|
|
124
148
|
}
|
|
125
149
|
else if (node.type.is(Call) && node.firstChild && node.lastChild) {
|
|
126
150
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
127
151
|
}
|
|
128
152
|
else if (node.type.is(Variable)) {
|
|
129
153
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
130
|
-
// @ts-
|
|
154
|
+
// @ts-expect-error TS2339
|
|
131
155
|
(_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
156
|
}
|
|
133
157
|
else if (node.type.is(Function)) {
|
|
134
158
|
const varName = state.sliceDoc(node.from, node.to) || '';
|
|
135
|
-
// @ts-
|
|
159
|
+
// @ts-expect-error TS2339
|
|
136
160
|
(_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
161
|
}
|
|
138
162
|
else if (node.type.is(PropertyAccess) && node.firstChild && ((_e = node.lastChild) === null || _e === void 0 ? void 0 : _e.type.is(Property))) {
|
|
139
163
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
140
164
|
(_f = resolveTypes(state, node.firstChild, config)) === null || _f === void 0 ? void 0 : _f.forEach(baseType => {
|
|
141
165
|
var _a, _b, _c, _d;
|
|
142
|
-
// @ts-
|
|
166
|
+
// @ts-expect-error TS2339
|
|
143
167
|
(_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
168
|
});
|
|
169
|
+
if (node.getChild(NullSafeMemberOf)) {
|
|
170
|
+
types.add(ELScalar.Null);
|
|
171
|
+
}
|
|
145
172
|
}
|
|
146
173
|
else if (node.type.is(MethodAccess) && node.firstChild && ((_g = node.lastChild) === null || _g === void 0 ? void 0 : _g.type.is(Method))) {
|
|
147
174
|
const varName = state.sliceDoc(node.lastChild.from, node.lastChild.to) || '';
|
|
148
175
|
(_h = resolveTypes(state, node.firstChild, config)) === null || _h === void 0 ? void 0 : _h.forEach(baseType => {
|
|
149
176
|
var _a, _b, _c, _d;
|
|
150
|
-
// @ts-
|
|
177
|
+
// @ts-expect-error TS2339
|
|
151
178
|
(_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
179
|
});
|
|
180
|
+
if (node.getChild(NullSafeMemberOf)) {
|
|
181
|
+
types.add(ELScalar.Null);
|
|
182
|
+
}
|
|
153
183
|
}
|
|
154
184
|
// Array indexing: for typed arrays (e.g. Foo[]) return element type, for generic arrays return any
|
|
155
185
|
else if (node.type.is(ArrayAccess) && node.firstChild) {
|
|
@@ -162,6 +192,9 @@ function resolveTypes(state, node, config) {
|
|
|
162
192
|
types.add(ELScalar.Any);
|
|
163
193
|
}
|
|
164
194
|
});
|
|
195
|
+
if (node.getChild(NullSafeArrayAccessor)) {
|
|
196
|
+
types.add(ELScalar.Null);
|
|
197
|
+
}
|
|
165
198
|
}
|
|
166
199
|
else if (node.type.is(Application) && node.firstChild) {
|
|
167
200
|
resolveTypes(state, node.firstChild, config).forEach(x => types.add(x));
|
|
@@ -231,13 +264,13 @@ var utils = /*#__PURE__*//*@__PURE__*/Object.freeze({
|
|
|
231
264
|
*/
|
|
232
265
|
const expressionLanguageLinterSource = (state) => {
|
|
233
266
|
const config = getExpressionLanguageConfig(state);
|
|
234
|
-
|
|
267
|
+
const diagnostics = [];
|
|
235
268
|
syntaxTree(state).cursor().iterate(node => {
|
|
236
|
-
var _a, _b, _c, _d, _e, _f;
|
|
269
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
237
270
|
const { from, to, type: { id } } = node;
|
|
238
271
|
let identifier;
|
|
239
272
|
switch (id) {
|
|
240
|
-
case 0:
|
|
273
|
+
case 0: {
|
|
241
274
|
if (state.doc.length === 0 || from === 0) {
|
|
242
275
|
// Don't show error on empty doc (even though it is an error)
|
|
243
276
|
return;
|
|
@@ -251,7 +284,8 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
251
284
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected ${type} <code>${identifier}</code>` });
|
|
252
285
|
}
|
|
253
286
|
return;
|
|
254
|
-
|
|
287
|
+
}
|
|
288
|
+
case Arguments: {
|
|
255
289
|
const fn = resolveFunctionDefinition(node.node.prevSibling, state, config);
|
|
256
290
|
const args = fn === null || fn === void 0 ? void 0 : fn.args;
|
|
257
291
|
if (!args) {
|
|
@@ -272,7 +306,12 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
272
306
|
const typesUsed = Array.from(resolveTypes(state, n, config));
|
|
273
307
|
const typesExpected = args[i].type;
|
|
274
308
|
if (typesExpected && !typesExpected.includes(ELScalar.Any) && !typesUsed.some(x => typesExpected.includes(x))) {
|
|
275
|
-
diagnostics.push({
|
|
309
|
+
diagnostics.push({
|
|
310
|
+
from: n.from,
|
|
311
|
+
to: n.to,
|
|
312
|
+
severity: 'error',
|
|
313
|
+
message: `<code>${typesExpected.join('|')}</code> expected, got <code>${typesUsed.join('|')}</code>`
|
|
314
|
+
});
|
|
276
315
|
}
|
|
277
316
|
i++;
|
|
278
317
|
}
|
|
@@ -280,37 +319,93 @@ const expressionLanguageLinterSource = (state) => {
|
|
|
280
319
|
diagnostics.push({ from: node.from, to: node.to, severity: 'error', message: `Too few arguments – ${argumentCountHintFn()}` });
|
|
281
320
|
}
|
|
282
321
|
break;
|
|
322
|
+
}
|
|
283
323
|
case Property:
|
|
284
|
-
case Method:
|
|
285
|
-
const
|
|
324
|
+
case Method: {
|
|
325
|
+
const parent = node.node.parent;
|
|
326
|
+
const isPropertyAccess = parent === null || parent === void 0 ? void 0 : parent.type.is(PropertyAccess);
|
|
327
|
+
const isMethodAccess = parent === null || parent === void 0 ? void 0 : parent.type.is(MethodAccess);
|
|
328
|
+
// Skip validation if this is part of a PropertyAccess/MethodAccess on array type
|
|
329
|
+
// (the PropertyAccess/MethodAccess case will handle the error)
|
|
330
|
+
if ((isPropertyAccess || isMethodAccess) && (parent === null || parent === void 0 ? void 0 : parent.firstChild)) {
|
|
331
|
+
const types = Array.from(resolveTypes(state, parent.firstChild.node, config));
|
|
332
|
+
const hasArrayType = types.includes(ELScalar.Array) || types.some(x => x.endsWith('[]'));
|
|
333
|
+
if (hasArrayType && !types.includes(ELScalar.Any)) {
|
|
334
|
+
break; // Skip property validation, error will be reported by PropertyAccess/MethodAccess case
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const leftArgument = (_d = parent === null || parent === void 0 ? void 0 : parent.firstChild) === null || _d === void 0 ? void 0 : _d.node;
|
|
286
338
|
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
287
339
|
identifier = state.sliceDoc(from, to);
|
|
288
340
|
if (!types.find(type => { var _a; return resolveIdentifier(id, identifier, (_a = config.types) === null || _a === void 0 ? void 0 : _a[type]); })) {
|
|
289
341
|
diagnostics.push({ from, to, severity: 'error', message: `${node.name} <code>${identifier}</code> not found in <code>${types.join('|')}</code>` });
|
|
290
342
|
}
|
|
291
343
|
break;
|
|
344
|
+
}
|
|
345
|
+
case PropertyAccess:
|
|
346
|
+
case MethodAccess: {
|
|
347
|
+
const leftArgument = (_e = node.node.firstChild) === null || _e === void 0 ? void 0 : _e.node;
|
|
348
|
+
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
349
|
+
const hasArrayType = types.includes(ELScalar.Array) || types.some(x => x.endsWith('[]'));
|
|
350
|
+
if (hasArrayType && !types.includes(ELScalar.Any)) {
|
|
351
|
+
const propertyOrMethod = node.node.lastChild;
|
|
352
|
+
const errorFrom = (_f = propertyOrMethod === null || propertyOrMethod === void 0 ? void 0 : propertyOrMethod.from) !== null && _f !== void 0 ? _f : from;
|
|
353
|
+
const errorTo = (_g = propertyOrMethod === null || propertyOrMethod === void 0 ? void 0 : propertyOrMethod.to) !== null && _g !== void 0 ? _g : to;
|
|
354
|
+
diagnostics.push({ from: errorFrom, to: errorTo, severity: 'error', message: `Unexpected object access on <code>${types.join('|')}</code>` });
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case ArrayAccess: {
|
|
359
|
+
const leftArgument = (_h = node.node.firstChild) === null || _h === void 0 ? void 0 : _h.node;
|
|
360
|
+
const arrayAccessor = leftArgument.nextSibling;
|
|
361
|
+
const types = Array.from(resolveTypes(state, leftArgument, config));
|
|
362
|
+
const allowsAny = types.includes(ELScalar.Array) || types.includes(ELScalar.Any);
|
|
363
|
+
if (!allowsAny && ![...types].some(x => x.endsWith('[]'))) {
|
|
364
|
+
diagnostics.push({ from: arrayAccessor.from, to, severity: 'error', message: `Unexpected array access on <code>${types.join('|')}</code>` });
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
292
368
|
case Variable:
|
|
293
|
-
case Function:
|
|
369
|
+
case Function: {
|
|
294
370
|
identifier = state.sliceDoc(from, node.node.firstChild ? node.node.firstChild.from - 1 : to);
|
|
295
371
|
if (!resolveIdentifier(id, identifier, config)) {
|
|
296
372
|
diagnostics.push({ from, to, severity: 'error', message: `${node.node.name} <code>${identifier}</code> not found` });
|
|
297
373
|
}
|
|
298
374
|
break;
|
|
299
|
-
|
|
375
|
+
}
|
|
376
|
+
case BinaryExpression: {
|
|
300
377
|
const operatorNode = node.node.getChild(OperatorKeyword);
|
|
301
378
|
if (operatorNode) {
|
|
302
379
|
const operator = state.sliceDoc(operatorNode.from, operatorNode.to);
|
|
380
|
+
const leftArgument = node.node.firstChild;
|
|
381
|
+
const rightArgument = node.node.lastChild;
|
|
303
382
|
if (operator === 'in') {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
383
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
384
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
385
|
+
const allowsAny = rightTypes.has(ELScalar.Array) || rightTypes.has(ELScalar.Any);
|
|
386
|
+
if (!allowsAny && ![...rightTypes].some(x => x.endsWith('[]'))) {
|
|
387
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>${ELScalar.Array}</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
388
|
+
}
|
|
389
|
+
else if (!allowsAny && !rightTypes.has(`${ELScalar.Any}[]`) && ![...leftTypes].some(type => rightTypes.has(`${type}[]`))) {
|
|
390
|
+
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>` });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (["contains", "starts with", "ends with", "matches"].includes(operator)) {
|
|
394
|
+
// Both sides must be string
|
|
395
|
+
const leftTypes = resolveTypes(state, leftArgument, config);
|
|
396
|
+
const rightTypes = resolveTypes(state, rightArgument, config);
|
|
397
|
+
if (!leftTypes.has(ELScalar.String)) {
|
|
398
|
+
diagnostics.push({ from: leftArgument.from, to: leftArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...leftTypes].join('|')}</code>` });
|
|
399
|
+
}
|
|
400
|
+
if (!rightTypes.has(ELScalar.String)) {
|
|
401
|
+
diagnostics.push({ from: rightArgument.from, to: rightArgument.to, severity: 'error', message: `<code>string</code> expected, got <code>${[...rightTypes].join('|')}</code>` });
|
|
308
402
|
}
|
|
309
403
|
}
|
|
310
404
|
}
|
|
311
405
|
break;
|
|
406
|
+
}
|
|
312
407
|
}
|
|
313
|
-
if (identifier && ((
|
|
408
|
+
if (identifier && ((_j = node.node.parent) === null || _j === void 0 ? void 0 : _j.type.isError)) {
|
|
314
409
|
diagnostics.push({ from, to, severity: 'error', message: `Unexpected identifier <code>${identifier}</code>` });
|
|
315
410
|
}
|
|
316
411
|
});
|
|
@@ -380,7 +475,7 @@ function completeIdentifier(state, config, tree, from, to) {
|
|
|
380
475
|
validFor: /^[a-zA-Z_]+[a-zA-Z_0-9]*$/,
|
|
381
476
|
};
|
|
382
477
|
}
|
|
383
|
-
function completeMember(state, config, tree, from, to
|
|
478
|
+
function completeMember(state, config, tree, from, to) {
|
|
384
479
|
var _a, _b, _c, _d, _e, _f;
|
|
385
480
|
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)) {
|
|
386
481
|
return null;
|
|
@@ -389,7 +484,7 @@ function completeMember(state, config, tree, from, to, explicit) {
|
|
|
389
484
|
if (!(types === null || types === void 0 ? void 0 : types.size)) {
|
|
390
485
|
return null;
|
|
391
486
|
}
|
|
392
|
-
|
|
487
|
+
const options = [];
|
|
393
488
|
for (const type of types) {
|
|
394
489
|
const typeDeclaration = (_d = config.types) === null || _d === void 0 ? void 0 : _d[type];
|
|
395
490
|
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)) || []));
|
|
@@ -406,7 +501,7 @@ function expressionLanguageCompletion(context) {
|
|
|
406
501
|
const { state, pos, explicit } = context;
|
|
407
502
|
const tree = syntaxTree(state);
|
|
408
503
|
const lastChar = state.sliceDoc(pos - 1, pos);
|
|
409
|
-
const prevNode = tree.resolveInner(pos, lastChar === ')' ? 0 : -1);
|
|
504
|
+
const prevNode = tree.resolveInner(pos, (lastChar === ')' || lastChar === ']') ? 0 : -1);
|
|
410
505
|
const config = getExpressionLanguageConfig(state);
|
|
411
506
|
const isIdentifier = (node) => (node === null || node === void 0 ? void 0 : node.type.is(Variable)) || (node === null || node === void 0 ? void 0 : node.type.is(Function));
|
|
412
507
|
const isMember = (node) => (node === null || node === void 0 ? void 0 : node.type.is(Property)) || (node === null || node === void 0 ? void 0 : node.type.is(Method));
|
|
@@ -451,7 +546,6 @@ function resolveArguments(node) {
|
|
|
451
546
|
}
|
|
452
547
|
function getCursorTooltips(state) {
|
|
453
548
|
const config = getExpressionLanguageConfig(state);
|
|
454
|
-
// @ts-ignore
|
|
455
549
|
return state.selection.ranges
|
|
456
550
|
.filter(range => range.empty)
|
|
457
551
|
.map(range => {
|
|
@@ -477,7 +571,7 @@ function getCursorTooltips(state) {
|
|
|
477
571
|
strictSide: false,
|
|
478
572
|
arrow: true,
|
|
479
573
|
create: () => {
|
|
480
|
-
|
|
574
|
+
const dom = document.createElement("div");
|
|
481
575
|
dom.className = "cm-tooltip-cursor";
|
|
482
576
|
dom.textContent = `${argName}`;
|
|
483
577
|
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.4.0"
|
|
43
48
|
}
|