@xano/xanoscript-language-server 11.8.4 → 11.8.5
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/settings.local.json +2 -1
- package/cache/documentCache.js +58 -10
- package/lexer/db.js +1 -2
- package/lexer/security.js +16 -0
- package/onCompletion/onCompletion.js +61 -1
- package/onDefinition/onDefinition.js +150 -0
- package/onDefinition/onDefinition.spec.js +313 -0
- package/onDidChangeContent/onDidChangeContent.js +52 -5
- package/onHover/functions.md +28 -0
- package/package.json +1 -1
- package/parser/base_parser.js +61 -3
- package/parser/clauses/middlewareClause.js +16 -0
- package/parser/definitions/columnDefinition.js +5 -0
- package/parser/functions/api/apiCallFn.js +5 -3
- package/parser/functions/controls/functionCallFn.js +5 -3
- package/parser/functions/controls/functionRunFn.js +61 -5
- package/parser/functions/controls/taskCallFn.js +5 -3
- package/parser/functions/db/captureFieldName.js +63 -0
- package/parser/functions/db/dbAddFn.js +5 -3
- package/parser/functions/db/dbAddOrEditFn.js +13 -3
- package/parser/functions/db/dbBulkAddFn.js +5 -3
- package/parser/functions/db/dbBulkDeleteFn.js +5 -3
- package/parser/functions/db/dbBulkPatchFn.js +5 -3
- package/parser/functions/db/dbBulkUpdateFn.js +5 -3
- package/parser/functions/db/dbDelFn.js +10 -3
- package/parser/functions/db/dbEditFn.js +13 -3
- package/parser/functions/db/dbGetFn.js +10 -3
- package/parser/functions/db/dbHasFn.js +9 -3
- package/parser/functions/db/dbPatchFn.js +10 -3
- package/parser/functions/db/dbQueryFn.js +29 -3
- package/parser/functions/db/dbSchemaFn.js +5 -3
- package/parser/functions/db/dbTruncateFn.js +5 -3
- package/parser/functions/middlewareCallFn.js +3 -1
- package/parser/functions/security/register.js +19 -9
- package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
- package/parser/functions/securityFn.js +2 -0
- package/parser/functions/varFn.js +1 -1
- package/parser/generic/asVariable.js +2 -0
- package/parser/generic/assignableVariableAs.js +1 -0
- package/parser/generic/assignableVariableProperty.js +5 -2
- package/parser/tests/variable_test/coverage_check.xs +293 -0
- package/parser/variableScanner.js +64 -0
- package/parser/variableValidator.js +44 -0
- package/parser/variableValidator.spec.js +179 -0
- package/server.js +164 -10
- package/utils.js +32 -0
- package/utils.spec.js +93 -1
- package/workspace/crossFileValidator.js +166 -0
- package/workspace/crossFileValidator.spec.js +654 -0
- package/workspace/referenceTracking.spec.js +420 -0
- package/workspace/workspaceIndex.js +149 -0
- package/workspace/workspaceIndex.spec.js +189 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { documentCache } from "../cache/documentCache.js";
|
|
2
|
+
import { validateVariables } from "../parser/variableValidator.js";
|
|
3
|
+
import { crossFileValidate } from "../workspace/crossFileValidator.js";
|
|
4
|
+
import { workspaceIndex } from "../workspace/workspaceIndex.js";
|
|
2
5
|
|
|
3
6
|
// Diagnostic severity constants
|
|
4
7
|
const SEVERITY = {
|
|
@@ -71,16 +74,14 @@ export function onDidChangeContent(params, connection) {
|
|
|
71
74
|
|
|
72
75
|
try {
|
|
73
76
|
// Parse the XanoScript file using cache
|
|
74
|
-
const { parser, scheme } = documentCache.getOrParse(
|
|
77
|
+
const { lexResult, parser, scheme } = documentCache.getOrParse(
|
|
75
78
|
document.uri,
|
|
76
79
|
document.version,
|
|
77
80
|
text,
|
|
78
81
|
);
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
connection.sendDiagnostics({ uri: document.uri, diagnostics: [] });
|
|
83
|
-
}
|
|
83
|
+
// Update workspace index with already-parsed data (no re-parse needed)
|
|
84
|
+
workspaceIndex.addParsed(document.uri, text, parser.__symbolTable);
|
|
84
85
|
|
|
85
86
|
for (const error of parser.errors) {
|
|
86
87
|
console.error(
|
|
@@ -91,6 +92,52 @@ export function onDidChangeContent(params, connection) {
|
|
|
91
92
|
// Create diagnostics in a single pass
|
|
92
93
|
const diagnostics = createDiagnostics(parser, document);
|
|
93
94
|
|
|
95
|
+
// Run cross-file validation and append as warnings
|
|
96
|
+
if (parser.__symbolTable?.references) {
|
|
97
|
+
const crossFileWarnings = crossFileValidate(
|
|
98
|
+
parser.__symbolTable.references,
|
|
99
|
+
workspaceIndex,
|
|
100
|
+
);
|
|
101
|
+
for (const warning of crossFileWarnings) {
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
severity: SEVERITY.WARNING,
|
|
104
|
+
range: {
|
|
105
|
+
start: document.positionAt(warning.startOffset),
|
|
106
|
+
end: document.positionAt(warning.endOffset + 1),
|
|
107
|
+
},
|
|
108
|
+
message: warning.message,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Run variable validation and append warnings/hints
|
|
114
|
+
if (parser.__symbolTable?.varDeclarations) {
|
|
115
|
+
const varResult = validateVariables(
|
|
116
|
+
parser.__symbolTable,
|
|
117
|
+
lexResult.tokens,
|
|
118
|
+
);
|
|
119
|
+
for (const warning of varResult.warnings) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
severity: SEVERITY.WARNING,
|
|
122
|
+
range: {
|
|
123
|
+
start: document.positionAt(warning.startOffset),
|
|
124
|
+
end: document.positionAt(warning.endOffset + 1),
|
|
125
|
+
},
|
|
126
|
+
message: warning.message,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
for (const hint of varResult.hints) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
severity: SEVERITY.HINT,
|
|
132
|
+
range: {
|
|
133
|
+
start: document.positionAt(hint.startOffset),
|
|
134
|
+
end: document.positionAt(hint.endOffset + 1),
|
|
135
|
+
},
|
|
136
|
+
message: hint.message,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
94
141
|
console.log(
|
|
95
142
|
`onDidChangeContent(): sending diagnostic (${parser.errors.length} errors) for scheme:`,
|
|
96
143
|
scheme,
|
package/onHover/functions.md
CHANGED
|
@@ -1538,6 +1538,34 @@ security.jwe_encode {
|
|
|
1538
1538
|
|
|
1539
1539
|
Encodes a payload as a JSON Web Encryption (JWE) token with `headers`, `claims`, and a `key`. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256GCM`) secure the token, and `ttl` sets its validity (0 for no expiration). The token is stored in the variable defined by `as`, here `$encrypted_token`.
|
|
1540
1540
|
|
|
1541
|
+
# security.jwe_encode_legacy
|
|
1542
|
+
|
|
1543
|
+
```xs
|
|
1544
|
+
security.jwe_encode_legacy {
|
|
1545
|
+
payload = $jwt_payload
|
|
1546
|
+
audience = "LegacyApp"
|
|
1547
|
+
key = $env.magic_jwt_secret
|
|
1548
|
+
key_algorithm = "A256KW"
|
|
1549
|
+
content_algorithm = "A256CBC-HS512"
|
|
1550
|
+
} as $jwt
|
|
1551
|
+
```
|
|
1552
|
+
|
|
1553
|
+
Encodes a payload as a JSON Web Encryption (JWE) token using legacy algorithms. The `payload` is the data to encrypt, `audience` specifies the intended recipient, and `key` is used for encryption. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256CBC-HS512`) secure the token. The resulting JWE token is stored in the variable defined by `as`, here `$jwt`.
|
|
1554
|
+
|
|
1555
|
+
# security.jwe_decode_legacy
|
|
1556
|
+
|
|
1557
|
+
```xs
|
|
1558
|
+
security.jwe_decode_legacy {
|
|
1559
|
+
token = $input.magic_token
|
|
1560
|
+
key = $env.magic_jwt_secret
|
|
1561
|
+
audience = "LegacyApp"
|
|
1562
|
+
key_algorithm = "A256KW"
|
|
1563
|
+
content_algorithm = "A256CBC-HS512"
|
|
1564
|
+
} as $decoded_magic_token
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
Decodes a JSON Web Encryption (JWE) token that was encoded using legacy algorithms. The `token` is the JWE to decode, `key` is used for decryption, and `audience` specifies the expected recipient. The `key_algorithm` (e.g., `A256KW`) and `content_algorithm` (e.g., `A256CBC-HS512`) are used to properly decode the token. The decoded payload is stored in the variable defined by `as`, here `$decoded_magic_token`.
|
|
1568
|
+
|
|
1541
1569
|
# security.create_secret_key
|
|
1542
1570
|
|
|
1543
1571
|
```xs
|
package/package.json
CHANGED
package/parser/base_parser.js
CHANGED
|
@@ -42,10 +42,16 @@ export class XanoBaseParser extends CstParser {
|
|
|
42
42
|
* Add a variable to the registry
|
|
43
43
|
* @param {string} name the name of the variable
|
|
44
44
|
* @param {string} type the type of the variable (int, decimal, text, bool, etc)
|
|
45
|
-
* @param {
|
|
45
|
+
* @param {import('chevrotain').IToken} [token] the token for position tracking
|
|
46
46
|
*/
|
|
47
|
-
addVariable(name, type,
|
|
48
|
-
this.__symbolTable.var[name] = { type, value };
|
|
47
|
+
addVariable(name, type, token = null) {
|
|
48
|
+
this.__symbolTable.var[name] = { type, value: null };
|
|
49
|
+
this.__symbolTable.varDeclarations.push({
|
|
50
|
+
name,
|
|
51
|
+
type,
|
|
52
|
+
startOffset: token?.startOffset ?? null,
|
|
53
|
+
endOffset: token?.endOffset ?? null,
|
|
54
|
+
});
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/**
|
|
@@ -58,6 +64,56 @@ export class XanoBaseParser extends CstParser {
|
|
|
58
64
|
this.__symbolTable.input[name] = { type, iterable, nullable, optional };
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Record a cross-file reference for validation.
|
|
69
|
+
* @param {string} refType - The type of object referenced ("function", "table", "query", "task")
|
|
70
|
+
* @param {string} name - The referenced object name
|
|
71
|
+
* @param {import('chevrotain').IToken} token - The token containing the name (for diagnostics positioning)
|
|
72
|
+
*/
|
|
73
|
+
addReference(refType, name, token) {
|
|
74
|
+
if (name == null || !token) return;
|
|
75
|
+
this.__symbolTable.references.push({
|
|
76
|
+
refType,
|
|
77
|
+
name,
|
|
78
|
+
startOffset: token.startOffset,
|
|
79
|
+
endOffset: token.endOffset,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Attach argument keys to the most recent reference (for input validation).
|
|
85
|
+
* @param {Object} args - Map of argument names to { value, startOffset, endOffset }
|
|
86
|
+
*/
|
|
87
|
+
setReferenceArgs(args) {
|
|
88
|
+
const refs = this.__symbolTable.references;
|
|
89
|
+
if (refs.length === 0) return;
|
|
90
|
+
refs[refs.length - 1].args = args;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Attach field_name to the most recent reference (for column validation).
|
|
95
|
+
* @param {string} fieldName - The field name value
|
|
96
|
+
* @param {number} startOffset
|
|
97
|
+
* @param {number} endOffset
|
|
98
|
+
*/
|
|
99
|
+
setReferenceFieldName(fieldName, startOffset, endOffset) {
|
|
100
|
+
const refs = this.__symbolTable.references;
|
|
101
|
+
if (refs.length === 0) return;
|
|
102
|
+
refs[refs.length - 1].fieldName = fieldName;
|
|
103
|
+
refs[refs.length - 1].fieldNameStartOffset = startOffset;
|
|
104
|
+
refs[refs.length - 1].fieldNameEndOffset = endOffset;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Attach data keys to the most recent reference (for column validation).
|
|
109
|
+
* @param {Array<{name: string, startOffset: number, endOffset: number}>} dataKeys
|
|
110
|
+
*/
|
|
111
|
+
setReferenceDataKeys(dataKeys) {
|
|
112
|
+
const refs = this.__symbolTable.references;
|
|
113
|
+
if (refs.length === 0) return;
|
|
114
|
+
refs[refs.length - 1].dataKeys = dataKeys;
|
|
115
|
+
}
|
|
116
|
+
|
|
61
117
|
/**
|
|
62
118
|
* Add a warning message to the parser
|
|
63
119
|
* @param {*} message
|
|
@@ -244,6 +300,8 @@ export class XanoBaseParser extends CstParser {
|
|
|
244
300
|
var: {},
|
|
245
301
|
auth: {},
|
|
246
302
|
env: {},
|
|
303
|
+
references: [],
|
|
304
|
+
varDeclarations: [],
|
|
247
305
|
};
|
|
248
306
|
}
|
|
249
307
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EqualToken } from "../../lexer/control.js";
|
|
2
2
|
import { MiddlewareToken } from "../../lexer/tokens.js";
|
|
3
|
+
import { getVarName } from "../generic/utils.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* response = $entries
|
|
@@ -12,6 +13,7 @@ export function middlewareClause($) {
|
|
|
12
13
|
|
|
13
14
|
const parent = $.CONSUME(MiddlewareToken); // "middleware"
|
|
14
15
|
$.CONSUME(EqualToken); // "="
|
|
16
|
+
const captured = {};
|
|
15
17
|
$.SUBRULE($.schemaParseObjectFn, {
|
|
16
18
|
ARGS: [
|
|
17
19
|
parent,
|
|
@@ -31,9 +33,23 @@ export function middlewareClause($) {
|
|
|
31
33
|
},
|
|
32
34
|
],
|
|
33
35
|
},
|
|
36
|
+
captured,
|
|
34
37
|
],
|
|
35
38
|
});
|
|
36
39
|
|
|
40
|
+
$.ACTION(() => {
|
|
41
|
+
for (const phase of ["pre", "post"]) {
|
|
42
|
+
const items = captured[phase]?.value;
|
|
43
|
+
if (!Array.isArray(items)) continue;
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const nameEntry = item.name;
|
|
46
|
+
if (!nameEntry?.value) continue;
|
|
47
|
+
const token = nameEntry.value;
|
|
48
|
+
$.addReference("middleware", getVarName(token), token);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
37
53
|
$.sectionStack.pop();
|
|
38
54
|
};
|
|
39
55
|
}
|
|
@@ -113,6 +113,11 @@ export function columnDefinition($) {
|
|
|
113
113
|
],
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
// Track table reference from FK column definition
|
|
117
|
+
if (captured.table?.value) {
|
|
118
|
+
$.addReference("table", getVarName(captured.table.value), captured.table.value);
|
|
119
|
+
}
|
|
120
|
+
|
|
116
121
|
if (inputTypeToken.image == "vector" && !captured.size) {
|
|
117
122
|
$.addWarning(
|
|
118
123
|
'Column named "vector" should have a size attribute defining its length.',
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
VerbToken,
|
|
11
11
|
} from "../../../lexer/query.js";
|
|
12
12
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
13
|
+
import { getVarName } from "../../generic/utils.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
@@ -36,10 +37,11 @@ export function apiCallFn($) {
|
|
|
36
37
|
);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
$.OR([
|
|
40
|
-
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
41
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
40
|
+
const nameToken = $.OR([
|
|
41
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
42
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
42
43
|
]);
|
|
44
|
+
$.addReference("query", getVarName(nameToken), nameToken);
|
|
43
45
|
$.CONSUME(VerbToken);
|
|
44
46
|
$.CONSUME(EqualToken);
|
|
45
47
|
$.OR1([
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CallToken, FunctionToken } from "../../../lexer/function.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { DotToken, Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
*
|
|
@@ -17,10 +18,11 @@ export function functionCallFn($) {
|
|
|
17
18
|
$.CONSUME(DotToken); // "."
|
|
18
19
|
const fnToken = $.CONSUME(CallToken); // "call"
|
|
19
20
|
|
|
20
|
-
$.OR([
|
|
21
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
22
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
21
|
+
const nameToken = $.OR([
|
|
22
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
23
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
23
24
|
]);
|
|
25
|
+
$.addReference("function", getVarName(nameToken), nameToken);
|
|
24
26
|
|
|
25
27
|
$.SUBRULE($.functionAttrReq, {
|
|
26
28
|
ARGS: [
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { FunctionToken, RunToken } from "../../../lexer/function.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
FloatLiteral,
|
|
4
|
+
IntegerLiteral,
|
|
5
|
+
SingleQuotedStringLiteral,
|
|
6
|
+
StringLiteral,
|
|
7
|
+
} from "../../../lexer/literal.js";
|
|
8
|
+
import { DotToken, FalseToken, Identifier, TrueToken } from "../../../lexer/tokens.js";
|
|
9
|
+
import { getVarName } from "../../generic/utils.js";
|
|
10
|
+
|
|
11
|
+
const literalTypeMap = new Map([
|
|
12
|
+
[IntegerLiteral.name, "int"],
|
|
13
|
+
[FloatLiteral.name, "decimal"],
|
|
14
|
+
[StringLiteral.name, "text"],
|
|
15
|
+
[SingleQuotedStringLiteral.name, "text"],
|
|
16
|
+
[TrueToken.name, "bool"],
|
|
17
|
+
[FalseToken.name, "bool"],
|
|
18
|
+
]);
|
|
4
19
|
|
|
5
20
|
/**
|
|
6
21
|
*
|
|
@@ -14,11 +29,13 @@ export function functionRunFn($) {
|
|
|
14
29
|
$.CONSUME(DotToken); // "."
|
|
15
30
|
const fnToken = $.CONSUME(RunToken); // "run"
|
|
16
31
|
|
|
17
|
-
$.OR([
|
|
18
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
19
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
32
|
+
const nameToken = $.OR([
|
|
33
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
34
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
20
35
|
]);
|
|
36
|
+
$.addReference("function", getVarName(nameToken), nameToken);
|
|
21
37
|
|
|
38
|
+
const captured = {};
|
|
22
39
|
$.OPTION(() => {
|
|
23
40
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
24
41
|
ARGS: [
|
|
@@ -30,10 +47,49 @@ export function functionRunFn($) {
|
|
|
30
47
|
"runtime_mode?": ["async-shared", "async-dedicated"],
|
|
31
48
|
"mock?": { "![string]": "[expression]" },
|
|
32
49
|
},
|
|
50
|
+
captured,
|
|
33
51
|
],
|
|
34
52
|
});
|
|
35
53
|
});
|
|
36
54
|
|
|
55
|
+
$.ACTION(() => {
|
|
56
|
+
if (captured.input) {
|
|
57
|
+
const args = {};
|
|
58
|
+
const tokens = $.input;
|
|
59
|
+
for (const [k, v] of Object.entries(captured.input)) {
|
|
60
|
+
if (k === "key" || k === "value") continue;
|
|
61
|
+
const keyOffset = v?.key?.startOffset;
|
|
62
|
+
|
|
63
|
+
// Find value token: scan from key token, skip colon, grab next token
|
|
64
|
+
let type = null;
|
|
65
|
+
let value = v?.value?.image ?? null;
|
|
66
|
+
if (keyOffset != null) {
|
|
67
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
68
|
+
if (tokens[i].startOffset === keyOffset) {
|
|
69
|
+
// tokens[i] = key, tokens[i+1] = colon, tokens[i+2] = value
|
|
70
|
+
const valueToken = tokens[i + 2];
|
|
71
|
+
if (valueToken) {
|
|
72
|
+
type = literalTypeMap.get(valueToken.tokenType.name) ?? null;
|
|
73
|
+
if (type && !value) value = valueToken.image;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
args[k] = {
|
|
81
|
+
value,
|
|
82
|
+
type,
|
|
83
|
+
startOffset: v?.key?.startOffset ?? null,
|
|
84
|
+
endOffset: v?.key?.endOffset ?? null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(args).length > 0) {
|
|
88
|
+
$.setReferenceArgs(args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
37
93
|
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
38
94
|
$.sectionStack.pop();
|
|
39
95
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
2
2
|
import { CallToken, TaskToken } from "../../../lexer/task.js";
|
|
3
3
|
import { DotToken, Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
*
|
|
@@ -17,10 +18,11 @@ export function taskCallFn($) {
|
|
|
17
18
|
$.CONSUME(DotToken); // "."
|
|
18
19
|
const fnToken = $.CONSUME(CallToken); // "call"
|
|
19
20
|
|
|
20
|
-
$.OR([
|
|
21
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
22
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
21
|
+
const nameToken = $.OR([
|
|
22
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
23
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
23
24
|
]);
|
|
25
|
+
$.addReference("task", getVarName(nameToken), nameToken);
|
|
24
26
|
|
|
25
27
|
$.SUBRULE($.functionAttrReq, {
|
|
26
28
|
ARGS: [fnToken, requiredAttrs, optionalAttrs],
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SingleQuotedStringLiteral,StringLiteral } from "../../../lexer/literal.js";
|
|
2
|
+
import { Identifier } from "../../../lexer/tokens.js";
|
|
3
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
|
+
|
|
5
|
+
const identifierLikeTokens = new Set([
|
|
6
|
+
Identifier.name,
|
|
7
|
+
StringLiteral.name,
|
|
8
|
+
SingleQuotedStringLiteral.name,
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract data object keys from captured schema and attach to last reference.
|
|
13
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
14
|
+
* @param {Object} captured
|
|
15
|
+
*/
|
|
16
|
+
export function captureDataKeys($, captured) {
|
|
17
|
+
if (!captured.data) return;
|
|
18
|
+
|
|
19
|
+
const dataKeys = [];
|
|
20
|
+
for (const [k, v] of Object.entries(captured.data)) {
|
|
21
|
+
if (k === "key" || k === "value") continue;
|
|
22
|
+
const keyToken = v?.key;
|
|
23
|
+
if (keyToken) {
|
|
24
|
+
dataKeys.push({
|
|
25
|
+
name: k,
|
|
26
|
+
startOffset: keyToken.startOffset,
|
|
27
|
+
endOffset: keyToken.endOffset,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (dataKeys.length > 0) {
|
|
32
|
+
$.setReferenceDataKeys(dataKeys);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract field_name value from captured schema object and attach to last reference.
|
|
38
|
+
* Only captures when the value is an identifier or string literal (not a variable expression).
|
|
39
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
40
|
+
* @param {Object} captured
|
|
41
|
+
*/
|
|
42
|
+
export function captureFieldName($, captured) {
|
|
43
|
+
if (!captured.field_name) return;
|
|
44
|
+
|
|
45
|
+
const keyOffset = captured.field_name.key?.startOffset;
|
|
46
|
+
if (keyOffset == null) return;
|
|
47
|
+
|
|
48
|
+
const tokens = $.input;
|
|
49
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
50
|
+
if (tokens[i].startOffset === keyOffset) {
|
|
51
|
+
// tokens[i] = "field_name" key, tokens[i+1] = "=", tokens[i+2] = value
|
|
52
|
+
const valueToken = tokens[i + 2];
|
|
53
|
+
if (valueToken && identifierLikeTokens.has(valueToken.tokenType.name)) {
|
|
54
|
+
$.setReferenceFieldName(
|
|
55
|
+
getVarName(valueToken),
|
|
56
|
+
valueToken.startOffset,
|
|
57
|
+
valueToken.endOffset
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AddToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
import { addonSchema } from "./schema.js";
|
|
5
6
|
/**
|
|
6
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -10,13 +11,14 @@ export function dbAddFn($) {
|
|
|
10
11
|
$.sectionStack.push("dbAddFn");
|
|
11
12
|
const fnToken = $.CONSUME(AddToken); // "add"
|
|
12
13
|
|
|
13
|
-
$.OR({
|
|
14
|
+
const nameToken = $.OR({
|
|
14
15
|
DEF: [
|
|
15
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
16
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
17
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
17
18
|
],
|
|
18
19
|
ERR_MSG: "Expected a table name",
|
|
19
20
|
});
|
|
21
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
20
22
|
|
|
21
23
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
22
24
|
ARGS: [
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AddOrEditToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
5
|
+
import { captureDataKeys,captureFieldName } from "./captureFieldName.js";
|
|
4
6
|
import { addonSchema } from "./schema.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -10,14 +12,16 @@ export function dbAddOrEditFn($) {
|
|
|
10
12
|
return () => {
|
|
11
13
|
$.sectionStack.push("dbAddOrEditFn");
|
|
12
14
|
const fnToken = $.CONSUME(AddOrEditToken); // "edit"
|
|
13
|
-
$.OR({
|
|
15
|
+
const nameToken = $.OR({
|
|
14
16
|
DEF: [
|
|
15
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
17
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
18
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
17
19
|
],
|
|
18
20
|
ERR_MSG: "Expected a table name",
|
|
19
21
|
});
|
|
22
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
20
23
|
|
|
24
|
+
const captured = {};
|
|
21
25
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
22
26
|
ARGS: [
|
|
23
27
|
fnToken,
|
|
@@ -30,9 +34,15 @@ export function dbAddOrEditFn($) {
|
|
|
30
34
|
"addon?": [addonSchema],
|
|
31
35
|
"mock?": { "![string]": "[expression]" },
|
|
32
36
|
},
|
|
37
|
+
captured,
|
|
33
38
|
],
|
|
34
39
|
});
|
|
35
40
|
|
|
41
|
+
$.ACTION(() => {
|
|
42
|
+
captureFieldName($, captured);
|
|
43
|
+
captureDataKeys($, captured);
|
|
44
|
+
});
|
|
45
|
+
|
|
36
46
|
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
37
47
|
$.sectionStack.pop();
|
|
38
48
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AddToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -9,13 +10,14 @@ export function dbBulkAddFn($) {
|
|
|
9
10
|
return () => {
|
|
10
11
|
$.sectionStack.push("dbBulkAddFn");
|
|
11
12
|
const fnToken = $.CONSUME(AddToken); // "add"
|
|
12
|
-
$.OR({
|
|
13
|
+
const nameToken = $.OR({
|
|
13
14
|
DEF: [
|
|
14
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
15
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
15
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
16
17
|
],
|
|
17
18
|
ERR_MSG: "Expected a table name",
|
|
18
19
|
});
|
|
20
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
19
21
|
const defined = {};
|
|
20
22
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
21
23
|
ARGS: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DeleteToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -9,13 +10,14 @@ export function dbBulkDeleteFn($) {
|
|
|
9
10
|
return () => {
|
|
10
11
|
$.sectionStack.push("dbBulkDeleteFn");
|
|
11
12
|
const fnToken = $.CONSUME(DeleteToken); // "delete"
|
|
12
|
-
$.OR({
|
|
13
|
+
const nameToken = $.OR({
|
|
13
14
|
DEF: [
|
|
14
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
15
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
15
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
16
17
|
],
|
|
17
18
|
ERR_MSG: "Expected a table name",
|
|
18
19
|
});
|
|
20
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
19
21
|
|
|
20
22
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
21
23
|
ARGS: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PatchToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -9,13 +10,14 @@ export function dbBulkPatchFn($) {
|
|
|
9
10
|
return () => {
|
|
10
11
|
$.sectionStack.push("dbBulkPatchFn");
|
|
11
12
|
const fnToken = $.CONSUME(PatchToken); // "patch"
|
|
12
|
-
$.OR({
|
|
13
|
+
const nameToken = $.OR({
|
|
13
14
|
DEF: [
|
|
14
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
15
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
15
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
16
17
|
],
|
|
17
18
|
ERR_MSG: "Expected a table name",
|
|
18
19
|
});
|
|
20
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
19
21
|
|
|
20
22
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
21
23
|
ARGS: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { UpdateToken } from "../../../lexer/db.js";
|
|
2
2
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
3
3
|
import { Identifier } from "../../../lexer/tokens.js";
|
|
4
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
@@ -9,13 +10,14 @@ export function dbBulkUpdateFn($) {
|
|
|
9
10
|
return () => {
|
|
10
11
|
$.sectionStack.push("dbBulkUpdateFn");
|
|
11
12
|
const fnToken = $.CONSUME(UpdateToken); // "update"
|
|
12
|
-
$.OR({
|
|
13
|
+
const nameToken = $.OR({
|
|
13
14
|
DEF: [
|
|
14
|
-
{ ALT: () => $.CONSUME(Identifier) },
|
|
15
|
-
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
15
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
16
|
+
{ ALT: () => $.CONSUME1(StringLiteral) },
|
|
16
17
|
],
|
|
17
18
|
ERR_MSG: "Expected a table name",
|
|
18
19
|
});
|
|
20
|
+
$.addReference("table", getVarName(nameToken), nameToken);
|
|
19
21
|
|
|
20
22
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
21
23
|
ARGS: [
|