@xano/xanoscript-language-server 11.12.0 → 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.
- package/.claude/memory/feedback_no_git.md +11 -0
- package/.claude/settings.local.json +13 -28
- package/lexer/cast.js +3 -3
- package/lexer/control.js +0 -2
- package/lexer/literal.js +2 -2
- package/lexer/regexp.spec.js +84 -0
- package/lexer/security.js +2 -2
- package/lexer/tokens.js +22 -3
- package/package.json +1 -1
- package/parser/attributes/columnDefaultValueAttribute.js +10 -3
- package/parser/attributes/inputFilterFn.js +11 -4
- package/parser/clauses/flexibleStringClause.js +2 -2
- package/parser/clauses/nakedStackFn.js +43 -36
- package/parser/clauses/nakedStackFn.spec.js +10 -0
- package/parser/clauses/responseClause.spec.js +5 -0
- package/parser/definitions/columnDefinition.js +7 -2
- package/parser/functions/array/arrayNoValueAs.js +1 -1
- package/parser/functions/array/arrayPopFn.js +1 -3
- package/parser/functions/controls/forEachFn.spec.js +8 -0
- package/parser/functions/controls/functionRunFn.spec.js +10 -0
- package/parser/functions/controls/preconditionFn.js +3 -0
- package/parser/functions/db/dbExternalDirectQueryFn.js +1 -0
- package/parser/functions/db/dbQueryFn.spec.js +31 -0
- package/parser/functions/schema/schemaParseConstantFn.js +11 -10
- package/parser/functions/varFn.js +14 -2
- package/parser/functions/varFn.spec.js +20 -12
- package/parser/generic/asVariable.spec.js +12 -8
- package/parser/generic/assignableVariableAs.js +50 -11
- package/parser/generic/assignableVariableAs.spec.js +29 -7
- package/parser/generic/assignableVariableProperty.js +22 -10
- package/parser/generic/assignableVariableProperty.spec.js +21 -12
- package/parser/generic/expressionFn.js +9 -2
- package/parser/generic/expressionFn.spec.js +5 -0
- package/parser/generic/filterNames.js +9 -0
- package/parser/multidoc.js +38 -17
- package/parser/multidoc.spec.js +7 -1
- package/parser/query_parser.js +11 -3
- package/parser/query_parser.spec.js +0 -3
- package/parser/tests/function/function_parser.spec.js +4 -4
- package/parser/tests/query/query_parser.spec.js +3 -3
- package/parser/tool_parser.js +12 -3
- package/parser/tool_parser.spec.js +19 -6
package/parser/multidoc.js
CHANGED
|
@@ -6,6 +6,7 @@ import { validateVariables } from "./variableValidator.js";
|
|
|
6
6
|
|
|
7
7
|
const SEPARATOR = "\n---\n";
|
|
8
8
|
const SEPARATOR_LENGTH = 5;
|
|
9
|
+
const SEPARATOR_NEWLINES = 2; // number of \n characters in SEPARATOR
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Find the segment that contains the given global offset.
|
|
@@ -38,6 +39,23 @@ export function findSegmentAtOffset(parserResult, offset) {
|
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Snapshot a Chevrotain token (or minimal token-like object) into a plain object.
|
|
44
|
+
* @param {Object|null} token
|
|
45
|
+
* @returns {Object|null}
|
|
46
|
+
*/
|
|
47
|
+
function snapshotToken(token) {
|
|
48
|
+
if (!token) return null;
|
|
49
|
+
return {
|
|
50
|
+
startOffset: token.startOffset,
|
|
51
|
+
endOffset: token.endOffset,
|
|
52
|
+
startLine: token.startLine,
|
|
53
|
+
startColumn: token.startColumn,
|
|
54
|
+
image: token.image,
|
|
55
|
+
tokenTypeName: token.tokenType?.name,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
/**
|
|
42
60
|
* Snapshot parser state into a plain object.
|
|
43
61
|
* Must be called immediately after xanoscriptParser() before parsing next segment,
|
|
@@ -49,28 +67,22 @@ function snapshotParser(parser) {
|
|
|
49
67
|
return {
|
|
50
68
|
errors: parser.errors.map((e) => ({
|
|
51
69
|
message: e.message,
|
|
52
|
-
token: e.token
|
|
53
|
-
|
|
54
|
-
: null,
|
|
70
|
+
token: snapshotToken(e.token),
|
|
71
|
+
previousToken: snapshotToken(e.previousToken),
|
|
55
72
|
})),
|
|
56
73
|
warnings: parser.warnings.map((w) => ({
|
|
57
74
|
message: w.message,
|
|
58
|
-
token: w.token
|
|
59
|
-
? { startOffset: w.token.startOffset, endOffset: w.token.endOffset }
|
|
60
|
-
: null,
|
|
75
|
+
token: snapshotToken(w.token),
|
|
61
76
|
})),
|
|
62
77
|
informations: parser.informations.map((i) => ({
|
|
63
78
|
message: i.message,
|
|
64
|
-
token: i.token
|
|
65
|
-
? { startOffset: i.token.startOffset, endOffset: i.token.endOffset }
|
|
66
|
-
: null,
|
|
79
|
+
token: snapshotToken(i.token),
|
|
67
80
|
})),
|
|
68
81
|
hints: parser.hints.map((h) => ({
|
|
69
82
|
message: h.message,
|
|
70
|
-
token: h.token
|
|
71
|
-
? { startOffset: h.token.startOffset, endOffset: h.token.endOffset }
|
|
72
|
-
: null,
|
|
83
|
+
token: snapshotToken(h.token),
|
|
73
84
|
})),
|
|
85
|
+
sectionStack: [...parser.sectionStack],
|
|
74
86
|
__symbolTable: parser.__symbolTable
|
|
75
87
|
? {
|
|
76
88
|
input: { ...parser.__symbolTable.input },
|
|
@@ -94,17 +106,24 @@ function snapshotParser(parser) {
|
|
|
94
106
|
* Apply a global offset to all position-bearing fields in a parser snapshot.
|
|
95
107
|
* @param {Object} snapshot - The parser state snapshot
|
|
96
108
|
* @param {number} globalOffset - The character offset to add
|
|
109
|
+
* @param {number} lineOffset - The line number offset to add
|
|
97
110
|
*/
|
|
98
|
-
function offsetResult(snapshot, globalOffset) {
|
|
111
|
+
function offsetResult(snapshot, globalOffset, lineOffset) {
|
|
99
112
|
if (globalOffset === 0) return;
|
|
100
113
|
|
|
101
114
|
const offsetToken = (token) => {
|
|
102
115
|
if (!token) return;
|
|
103
116
|
token.startOffset += globalOffset;
|
|
104
117
|
token.endOffset += globalOffset;
|
|
118
|
+
if (token.startLine != null) {
|
|
119
|
+
token.startLine += lineOffset;
|
|
120
|
+
}
|
|
105
121
|
};
|
|
106
122
|
|
|
107
|
-
for (const err of snapshot.errors)
|
|
123
|
+
for (const err of snapshot.errors) {
|
|
124
|
+
offsetToken(err.token);
|
|
125
|
+
offsetToken(err.previousToken);
|
|
126
|
+
}
|
|
108
127
|
for (const warn of snapshot.warnings) offsetToken(warn.token);
|
|
109
128
|
for (const info of snapshot.informations) offsetToken(info.token);
|
|
110
129
|
for (const hint of snapshot.hints) offsetToken(hint.token);
|
|
@@ -161,6 +180,7 @@ export function multidocParser(text) {
|
|
|
161
180
|
const segments = text.split(SEPARATOR);
|
|
162
181
|
const results = [];
|
|
163
182
|
let globalOffset = 0;
|
|
183
|
+
let lineOffset = 0;
|
|
164
184
|
|
|
165
185
|
for (const segmentText of segments) {
|
|
166
186
|
const parser = xanoscriptParser(segmentText);
|
|
@@ -199,8 +219,9 @@ export function multidocParser(text) {
|
|
|
199
219
|
: null;
|
|
200
220
|
// Deep-copy the snapshot before offsetResult mutates it (needed for hover providers)
|
|
201
221
|
const localSnapshot = JSON.parse(JSON.stringify(snapshot));
|
|
202
|
-
results.push({ snapshot, localSnapshot, segmentText, segmentSymbolTable, globalOffset });
|
|
222
|
+
results.push({ snapshot, localSnapshot, segmentText, segmentSymbolTable, globalOffset, lineOffset });
|
|
203
223
|
globalOffset += segmentText.length + SEPARATOR_LENGTH;
|
|
224
|
+
lineOffset += (segmentText.match(/\n/g) || []).length + SEPARATOR_NEWLINES;
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
// For now, merge without offsetting (next task)
|
|
@@ -221,8 +242,8 @@ export function multidocParser(text) {
|
|
|
221
242
|
segmentCount: segments.length,
|
|
222
243
|
};
|
|
223
244
|
|
|
224
|
-
for (const { snapshot, globalOffset } of results) {
|
|
225
|
-
offsetResult(snapshot, globalOffset);
|
|
245
|
+
for (const { snapshot, globalOffset, lineOffset } of results) {
|
|
246
|
+
offsetResult(snapshot, globalOffset, lineOffset);
|
|
226
247
|
merged.errors.push(...snapshot.errors);
|
|
227
248
|
merged.warnings.push(...snapshot.warnings);
|
|
228
249
|
merged.informations.push(...snapshot.informations);
|
package/parser/multidoc.spec.js
CHANGED
|
@@ -299,6 +299,10 @@ function bar {
|
|
|
299
299
|
var.update $x {
|
|
300
300
|
value = 2
|
|
301
301
|
}
|
|
302
|
+
|
|
303
|
+
var $y {
|
|
304
|
+
value = 2
|
|
305
|
+
}
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
response = null
|
|
@@ -691,7 +695,9 @@ function bar {
|
|
|
691
695
|
// The error must be within seg3's global range
|
|
692
696
|
const error = result.errors[0];
|
|
693
697
|
expect(error.token.startOffset).to.be.at.least(expectedSeg3Start);
|
|
694
|
-
expect(error.token.startOffset).to.be.lessThan(
|
|
698
|
+
expect(error.token.startOffset).to.be.lessThan(
|
|
699
|
+
expectedSeg3Start + seg3.length,
|
|
700
|
+
);
|
|
695
701
|
});
|
|
696
702
|
|
|
697
703
|
it("should isolate errors — bad seg1 should not prevent seg2 from parsing", () => {
|
package/parser/query_parser.js
CHANGED
|
@@ -30,6 +30,7 @@ export function queryDeclaration($) {
|
|
|
30
30
|
let hasTags = false;
|
|
31
31
|
let hasApiGroup = false;
|
|
32
32
|
let hasResponseType = false;
|
|
33
|
+
let responseType = "standard";
|
|
33
34
|
|
|
34
35
|
const testNames = [];
|
|
35
36
|
|
|
@@ -80,8 +81,8 @@ export function queryDeclaration($) {
|
|
|
80
81
|
$.CONSUME(ResponseTypeToken);
|
|
81
82
|
$.CONSUME8(EqualToken);
|
|
82
83
|
const valueToken = $.CONSUME2(StringLiteral);
|
|
83
|
-
|
|
84
|
-
if (!["stream", "standard"].includes(
|
|
84
|
+
responseType = getVarName(valueToken);
|
|
85
|
+
if (!["stream", "standard"].includes(responseType)) {
|
|
85
86
|
$.addInvalidValueError(
|
|
86
87
|
valueToken,
|
|
87
88
|
"response_type must be either 'stream' or 'standard'",
|
|
@@ -193,10 +194,17 @@ export function queryDeclaration($) {
|
|
|
193
194
|
$.addMissingError(parent, "{} is missing a stack clause");
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
if (!hasResponse) {
|
|
197
|
+
if (!hasResponse && responseType === "standard") {
|
|
197
198
|
$.addMissingError(parent, "{} is missing a response clause");
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
if (hasResponse && responseType === "stream") {
|
|
202
|
+
$.addInvalidValueError(
|
|
203
|
+
parent,
|
|
204
|
+
"response clause is not allowed when response_type is 'stream'",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
200
208
|
$.MANY1(() => $.CONSUME1(NewlineToken)); // optional new line
|
|
201
209
|
$.CONSUME(RCurly); // "}"
|
|
202
210
|
$.MANY2(() => $.CONSUME2(NewlineToken)); // optional new line
|
|
@@ -74,7 +74,6 @@ describe("query_parser", () => {
|
|
|
74
74
|
expect(parser.errors).to.be.empty;
|
|
75
75
|
|
|
76
76
|
parser = xanoscriptParser(`query foo verb=GET {
|
|
77
|
-
|
|
78
77
|
response_type = "stream"
|
|
79
78
|
|
|
80
79
|
input {
|
|
@@ -82,8 +81,6 @@ describe("query_parser", () => {
|
|
|
82
81
|
|
|
83
82
|
stack {
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
response = null
|
|
87
84
|
}`);
|
|
88
85
|
expect(parser.errors).to.be.empty;
|
|
89
86
|
});
|
|
@@ -9,12 +9,12 @@ describe("Xanoscript Function Parser", () => {
|
|
|
9
9
|
it("parses all the valid sources", () => {
|
|
10
10
|
const errors = [];
|
|
11
11
|
const files = readdirSync(
|
|
12
|
-
join(dirname("./"), "parser/tests/function/valid_sources")
|
|
12
|
+
join(dirname("./"), "parser/tests/function/valid_sources"),
|
|
13
13
|
);
|
|
14
14
|
files.forEach((file) => {
|
|
15
15
|
const source = readFileSync(
|
|
16
16
|
join(dirname("./"), "parser/tests/function/valid_sources", file),
|
|
17
|
-
"utf8"
|
|
17
|
+
"utf8",
|
|
18
18
|
);
|
|
19
19
|
const parser = xanoscriptParser(source);
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ describe("Xanoscript Function Parser", () => {
|
|
|
26
26
|
error.previousToken.image,
|
|
27
27
|
{
|
|
28
28
|
length: 20,
|
|
29
|
-
}
|
|
29
|
+
},
|
|
30
30
|
)})`
|
|
31
31
|
: "N/A",
|
|
32
32
|
file: `parser/tests/function/valid_sources/${file}:${error.token.startLine}:${error.token.startColumn}`,
|
|
@@ -36,7 +36,7 @@ describe("Xanoscript Function Parser", () => {
|
|
|
36
36
|
error.token.image,
|
|
37
37
|
{
|
|
38
38
|
length: 20,
|
|
39
|
-
}
|
|
39
|
+
},
|
|
40
40
|
)})`,
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -9,13 +9,13 @@ describe("Xanoscript Query Parser", () => {
|
|
|
9
9
|
it("parses all the valid sources", () => {
|
|
10
10
|
const errors = [];
|
|
11
11
|
const files = readdirSync(
|
|
12
|
-
join(dirname("./"), "parser/tests/query/valid_sources")
|
|
12
|
+
join(dirname("./"), "parser/tests/query/valid_sources"),
|
|
13
13
|
);
|
|
14
14
|
|
|
15
15
|
files.forEach((file) => {
|
|
16
16
|
const source = readFileSync(
|
|
17
17
|
join(dirname("./"), "parser/tests/query/valid_sources", file),
|
|
18
|
-
"utf8"
|
|
18
|
+
"utf8",
|
|
19
19
|
);
|
|
20
20
|
const parser = xanoscriptParser(source);
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ describe("Xanoscript Query Parser", () => {
|
|
|
27
27
|
error.previousToken.image,
|
|
28
28
|
{
|
|
29
29
|
length: 20,
|
|
30
|
-
}
|
|
30
|
+
},
|
|
31
31
|
)})`
|
|
32
32
|
: "N/A",
|
|
33
33
|
file: error.token
|
package/parser/tool_parser.js
CHANGED
|
@@ -111,15 +111,24 @@ export function toolDeclaration($) {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
if (!hasInput) {
|
|
114
|
-
$.addMissingError(
|
|
114
|
+
$.addMissingError(
|
|
115
|
+
parent,
|
|
116
|
+
`{} ${nameToken.image} is missing an input clause`,
|
|
117
|
+
);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
if (!hasStack) {
|
|
118
|
-
$.addMissingError(
|
|
121
|
+
$.addMissingError(
|
|
122
|
+
parent,
|
|
123
|
+
`{} ${nameToken.image} is missing a stack clause`,
|
|
124
|
+
);
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
if (!hasResponse) {
|
|
122
|
-
$.addMissingError(
|
|
128
|
+
$.addMissingError(
|
|
129
|
+
parent,
|
|
130
|
+
`{} ${nameToken.image} is missing a response clause`,
|
|
131
|
+
);
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
$.MANY1(() => $.CONSUME1(NewlineToken)); // optional new line
|
|
@@ -19,15 +19,28 @@ describe("tool_parser", () => {
|
|
|
19
19
|
|
|
20
20
|
it("should parse a basic a string tool url", () => {
|
|
21
21
|
const parser = xanoscriptParser(`tool "foo/bar" {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
input {
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
stack {
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
response = null
|
|
29
|
+
}`);
|
|
30
|
+
|
|
31
|
+
expect(parser.errors).to.be.empty;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should parse a tool with a string as a response", () => {
|
|
35
|
+
const parser = xanoscriptParser(`tool silent_exception {
|
|
36
|
+
input {
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
stack {
|
|
40
|
+
}
|
|
30
41
|
|
|
42
|
+
response = "something is wrong"
|
|
43
|
+
}`);
|
|
31
44
|
expect(parser.errors).to.be.empty;
|
|
32
45
|
});
|
|
33
46
|
|