@xano/xanoscript-language-server 11.12.1 → 11.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { lexDocument } from "./lexer.js";
|
|
4
|
+
|
|
5
|
+
function getTokenTypes(text) {
|
|
6
|
+
const result = lexDocument(text);
|
|
7
|
+
return {
|
|
8
|
+
tokens: result.tokens.map((t) => ({
|
|
9
|
+
type: t.tokenType.name,
|
|
10
|
+
image: t.image,
|
|
11
|
+
})),
|
|
12
|
+
errors: result.errors,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("RegExpToken lexer", () => {
|
|
17
|
+
it("should not lex a standalone regex without preceding colon", () => {
|
|
18
|
+
const { tokens } = getTokenTypes("/[^213]*/");
|
|
19
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
20
|
+
expect(regexpTokens).to.have.length(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should lex a regex after a colon", () => {
|
|
24
|
+
const { tokens, errors } = getTokenTypes("pattern:/[^213]*/");
|
|
25
|
+
expect(errors).to.be.empty;
|
|
26
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
27
|
+
expect(regexpTokens).to.have.length(1);
|
|
28
|
+
expect(regexpTokens[0].image).to.equal("/[^213]*/");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should lex / as divide after an identifier", () => {
|
|
32
|
+
const { tokens } = getTokenTypes("var_rel/55");
|
|
33
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
34
|
+
expect(divideTokens).to.have.length(1);
|
|
35
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
36
|
+
expect(regexpTokens).to.have.length(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should lex / as divide after a number", () => {
|
|
40
|
+
const { tokens } = getTokenTypes("55/2");
|
|
41
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
42
|
+
expect(divideTokens).to.have.length(1);
|
|
43
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
44
|
+
expect(regexpTokens).to.have.length(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should lex / as divide after closing paren", () => {
|
|
48
|
+
const { tokens } = getTokenTypes("(1+2)/3");
|
|
49
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
50
|
+
expect(divideTokens).to.have.length(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should not capture division as regex in complex expressions", () => {
|
|
54
|
+
const { tokens, errors } = getTokenTypes("$SomeVar.0.var_rel/55");
|
|
55
|
+
expect(errors).to.be.empty;
|
|
56
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
57
|
+
expect(regexpTokens).to.have.length(0);
|
|
58
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
59
|
+
expect(divideTokens).to.have.length(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should lex / as divide with spaces around it", () => {
|
|
63
|
+
const { tokens, errors } = getTokenTypes("$var /64");
|
|
64
|
+
expect(errors).to.be.empty;
|
|
65
|
+
const regexpTokens = tokens.filter((t) => t.type === "RegExpToken");
|
|
66
|
+
expect(regexpTokens).to.have.length(0);
|
|
67
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
68
|
+
expect(divideTokens).to.have.length(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should lex / as divide with space only before it", () => {
|
|
72
|
+
const { tokens, errors } = getTokenTypes("$var/ 64");
|
|
73
|
+
expect(errors).to.be.empty;
|
|
74
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
75
|
+
expect(divideTokens).to.have.length(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should lex / as divide when no spaces around a value", () => {
|
|
79
|
+
const { tokens, errors } = getTokenTypes("$var /64/ 34");
|
|
80
|
+
expect(errors).to.be.empty;
|
|
81
|
+
const divideTokens = tokens.filter((t) => t.type === "Divide");
|
|
82
|
+
expect(divideTokens).to.have.length(2);
|
|
83
|
+
});
|
|
84
|
+
});
|
package/lexer/tokens.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Lexer } from "chevrotain";
|
|
1
|
+
import { createToken as chevrotainCreateToken, Lexer } from "chevrotain";
|
|
2
2
|
import { Identifier } from "./identifier.js";
|
|
3
3
|
import {
|
|
4
4
|
createTokenByName,
|
|
@@ -150,10 +150,29 @@ export const NullToken = createTokenByName("null", {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
// regular expression (regexp) pattern
|
|
153
|
-
|
|
153
|
+
// A regexp literal only appears after a colon (e.g., filter:pattern:/[^abc]*/)
|
|
154
|
+
const regexpPattern = /\/(?:[^/\n\\ ]|\\.)(?:[^/\n\\]|\\.)*\//y;
|
|
155
|
+
function matchRegExp(text, startOffset, tokens) {
|
|
156
|
+
if (
|
|
157
|
+
tokens.length === 0 ||
|
|
158
|
+
tokens[tokens.length - 1].tokenType.name !== "ColonToken"
|
|
159
|
+
) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
regexpPattern.lastIndex = startOffset;
|
|
163
|
+
const result = regexpPattern.exec(text);
|
|
164
|
+
if (result) {
|
|
165
|
+
return [result[0]];
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const RegExpToken = chevrotainCreateToken({
|
|
154
171
|
name: "RegExpToken",
|
|
155
172
|
label: "regexp",
|
|
156
|
-
pattern:
|
|
173
|
+
pattern: { exec: matchRegExp },
|
|
174
|
+
line_breaks: false,
|
|
175
|
+
start_chars_hint: ["/"],
|
|
157
176
|
});
|
|
158
177
|
|
|
159
178
|
export const WhiteSpace = createToken({
|
package/package.json
CHANGED
|
@@ -26,6 +26,16 @@ describe("functionRunFn", () => {
|
|
|
26
26
|
expect(parser.errors).to.be.empty;
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
it("functionRunFn accepts complex expression as input", () => {
|
|
30
|
+
const parser = parse(`function.run "my_complex_function" {
|
|
31
|
+
input = {
|
|
32
|
+
Snumber : (($SomeVar.0.var_rel/55) > 0 ? ($SomeVar.0.var_rel1/61) : 6)+(($My_Var_S2.0.elo/ 60) > 7 ? ($My_Var_S2.0.elo/62) : 9)+(($Optional_var2.0.det/63) > 4 ? ($Optional_var2.0.det /64) : 2)+(($PtSgeS_sge.0.elo/65) > 1 ? ($PtSgeS_sge.0.elo/66) : 3)
|
|
33
|
+
NumberObject: []
|
|
34
|
+
}
|
|
35
|
+
} as $Var_Round`);
|
|
36
|
+
expect(parser.errors).to.be.empty;
|
|
37
|
+
});
|
|
38
|
+
|
|
29
39
|
it("functionRunFn accepts a string for function name", () => {
|
|
30
40
|
const parser = parse(`function.run "my/function" {
|
|
31
41
|
input = {a: $input.a, b: $b}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { expect } from "chai";
|
|
2
|
-
import { readdirSync, readFileSync } from "fs";
|
|
3
|
-
import { truncate } from "lodash-es";
|
|
4
|
-
import { describe, it } from "mocha";
|
|
5
|
-
import { dirname, join } from "path";
|
|
6
|
-
import { multidocParser } from "../../multidoc.js";
|
|
7
|
-
|
|
8
|
-
describe("Xanoscript multidoc Parser", () => {
|
|
9
|
-
it("parses all the valid sources", () => {
|
|
10
|
-
const errors = [];
|
|
11
|
-
const files = readdirSync(
|
|
12
|
-
join(dirname("./"), "parser/tests/multidoc/valid_sources"),
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
files.forEach((file) => {
|
|
16
|
-
const source = readFileSync(
|
|
17
|
-
join(dirname("./"), "parser/tests/multidoc/valid_sources", file),
|
|
18
|
-
"utf8",
|
|
19
|
-
);
|
|
20
|
-
const parser = multidocParser(source);
|
|
21
|
-
|
|
22
|
-
if (parser.errors.length > 0) {
|
|
23
|
-
parser.errors.forEach((error) => {
|
|
24
|
-
errors.push({
|
|
25
|
-
previous: error.previousToken
|
|
26
|
-
? `${error.previousToken.tokenTypeName} (${truncate(
|
|
27
|
-
error.previousToken.image,
|
|
28
|
-
{
|
|
29
|
-
length: 20,
|
|
30
|
-
},
|
|
31
|
-
)})`
|
|
32
|
-
: "N/A",
|
|
33
|
-
file: error.token
|
|
34
|
-
? `parser/tests/multidoc/valid_sources/${file}:${error.token.startLine}:${error.token.startColumn}`
|
|
35
|
-
: `parser/tests/multidoc/valid_sources/${file}`,
|
|
36
|
-
stack:
|
|
37
|
-
parser.sectionStack?.[parser.sectionStack.length - 1] ?? "N/A",
|
|
38
|
-
message: truncate(error.message, { length: 50 }),
|
|
39
|
-
token: error.token
|
|
40
|
-
? `${error.token.tokenTypeName} (${truncate(error.token.image, {
|
|
41
|
-
length: 20,
|
|
42
|
-
})})`
|
|
43
|
-
: "N/A",
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (errors.length > 0) {
|
|
50
|
-
console.table(errors);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
expect(errors).to.be.empty;
|
|
54
|
-
});
|
|
55
|
-
});
|