@xano/xanoscript-language-server 12.6.0 → 12.7.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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { documentCache } from "../cache/documentCache.js";
|
|
2
|
+
import { scanDbReferences } from "../parser/dbReferenceScanner.js";
|
|
2
3
|
import { validateVariables } from "../parser/variableValidator.js";
|
|
3
4
|
import { crossFileValidate } from "../workspace/crossFileValidator.js";
|
|
4
5
|
import { workspaceIndex } from "../workspace/workspaceIndex.js";
|
|
@@ -118,8 +119,12 @@ export function onDidChangeContent(params, connection, features = {}) {
|
|
|
118
119
|
|
|
119
120
|
// Run cross-file validation (skip for multidoc — already done internally)
|
|
120
121
|
if (!parser.isMultidoc && features.crossFileValidation !== false && parser.__symbolTable?.references) {
|
|
122
|
+
// `$db.<table>.<column>` accessor chains aren't captured as references by
|
|
123
|
+
// the grammar, so scan them from the token stream and validate alongside
|
|
124
|
+
// the parser-emitted references (table + column existence).
|
|
125
|
+
const dbRefs = scanDbReferences(lexResult.tokens);
|
|
121
126
|
const crossFileWarnings = crossFileValidate(
|
|
122
|
-
parser.__symbolTable.references,
|
|
127
|
+
[...parser.__symbolTable.references, ...dbRefs],
|
|
123
128
|
workspaceIndex,
|
|
124
129
|
);
|
|
125
130
|
for (const warning of crossFileWarnings) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Identifier } from "../lexer/identifier.js";
|
|
2
|
+
import { DotToken } from "../lexer/tokens.js";
|
|
3
|
+
import { ShortFormVariable } from "../lexer/variables.js";
|
|
4
|
+
|
|
5
|
+
const SHORT_FORM = ShortFormVariable.name;
|
|
6
|
+
const DOT = DotToken.name;
|
|
7
|
+
const IDENTIFIER = Identifier.name;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Scan the token stream for `$db.<table>.<column>` accessor chains and emit
|
|
11
|
+
* cross-file table references, so the workspace validator can check that the
|
|
12
|
+
* referenced table (and, when statically known, the column) actually exists.
|
|
13
|
+
*
|
|
14
|
+
* Emitted references match the shape consumed by {@link crossFileValidate}:
|
|
15
|
+
* `{ refType: "table", name, startOffset, endOffset }` with an optional
|
|
16
|
+
* `fieldName` (+ offsets) when the column is a plain identifier.
|
|
17
|
+
*
|
|
18
|
+
* `$`-prefixed properties (e.g. `$db.t.$search`, `$db.t.$this`) are pseudo /
|
|
19
|
+
* dynamic columns, not real columns, so only the table is validated for them.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('chevrotain').IToken[]} tokens - The token stream
|
|
22
|
+
* @returns {Array<{refType: string, name: string, startOffset: number, endOffset: number, fieldName?: string, fieldNameStartOffset?: number, fieldNameEndOffset?: number}>}
|
|
23
|
+
*/
|
|
24
|
+
export function scanDbReferences(tokens) {
|
|
25
|
+
const references = [];
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
28
|
+
const token = tokens[i];
|
|
29
|
+
if (token.tokenType.name !== SHORT_FORM || token.image !== "$db") continue;
|
|
30
|
+
|
|
31
|
+
// Expect `$db . <table>` where <table> is a plain identifier.
|
|
32
|
+
const dot = tokens[i + 1];
|
|
33
|
+
const tableToken = tokens[i + 2];
|
|
34
|
+
if (
|
|
35
|
+
!dot ||
|
|
36
|
+
dot.tokenType.name !== DOT ||
|
|
37
|
+
!tableToken ||
|
|
38
|
+
tableToken.tokenType.name !== IDENTIFIER
|
|
39
|
+
) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ref = {
|
|
44
|
+
refType: "table",
|
|
45
|
+
name: tableToken.image,
|
|
46
|
+
startOffset: tableToken.startOffset,
|
|
47
|
+
endOffset: tableToken.endOffset,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Optionally capture a plain-identifier column: `. <column>`.
|
|
51
|
+
const colDot = tokens[i + 3];
|
|
52
|
+
const colToken = tokens[i + 4];
|
|
53
|
+
if (
|
|
54
|
+
colDot &&
|
|
55
|
+
colDot.tokenType.name === DOT &&
|
|
56
|
+
colToken &&
|
|
57
|
+
colToken.tokenType.name === IDENTIFIER
|
|
58
|
+
) {
|
|
59
|
+
ref.fieldName = colToken.image;
|
|
60
|
+
ref.fieldNameStartOffset = colToken.startOffset;
|
|
61
|
+
ref.fieldNameEndOffset = colToken.endOffset;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
references.push(ref);
|
|
65
|
+
// Advance past the table token; the column (if any) can't start a new $db.
|
|
66
|
+
i += 2;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return references;
|
|
70
|
+
}
|
|
@@ -14,6 +14,12 @@ export function chainedIdentifier($) {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* `$` prefixed properties that are allowed without a warning, e.g.
|
|
19
|
+
* `$my_var.$search` or `$my_var.$this`.
|
|
20
|
+
*/
|
|
21
|
+
const ALLOWED_DOLLAR_PROPERTIES = new Set(["$search", "$this"]);
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* to be used after a variable to reference a single property depth
|
|
19
25
|
* like foo.bar , foo[1] or foo["key"]
|
|
@@ -30,10 +36,12 @@ export function singleChainedIdentifier($) {
|
|
|
30
36
|
{
|
|
31
37
|
ALT: () => {
|
|
32
38
|
const token = $.CONSUME(ShortFormVariable);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
if (!ALLOWED_DOLLAR_PROPERTIES.has(token.image)) {
|
|
40
|
+
$.addWarning(
|
|
41
|
+
`Using \`$\` prefixed properties '${token.image}' is discouraged.`,
|
|
42
|
+
token
|
|
43
|
+
);
|
|
44
|
+
}
|
|
37
45
|
},
|
|
38
46
|
}, // e.g., .bar
|
|
39
47
|
{ ALT: () => $.CONSUME(Identifier) }, // e.g., .bar
|
package/parser/multidoc.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lexDocument } from "../lexer/lexer.js";
|
|
2
2
|
import { crossFileValidate } from "../workspace/crossFileValidator.js";
|
|
3
3
|
import { WorkspaceIndex } from "../workspace/workspaceIndex.js";
|
|
4
|
+
import { scanDbReferences } from "./dbReferenceScanner.js";
|
|
4
5
|
import { xanoscriptParser } from "./parser.js";
|
|
5
6
|
import { validateVariables } from "./variableValidator.js";
|
|
6
7
|
|
|
@@ -188,6 +189,15 @@ export function multidocParser(text) {
|
|
|
188
189
|
|
|
189
190
|
// Variable validation with segment-local offsets and tokens
|
|
190
191
|
const segLexResult = lexDocument(segmentText);
|
|
192
|
+
|
|
193
|
+
// Capture `$db.<table>.<column>` accessor references (segment-local offsets);
|
|
194
|
+
// offsetResult() will shift them to global coordinates like other references.
|
|
195
|
+
if (snapshot.__symbolTable?.references) {
|
|
196
|
+
snapshot.__symbolTable.references.push(
|
|
197
|
+
...scanDbReferences(segLexResult.tokens),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
191
201
|
if (snapshot.__symbolTable?.varDeclarations) {
|
|
192
202
|
const varResult = validateVariables(
|
|
193
203
|
snapshot.__symbolTable,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { DotToken } from "../lexer/tokens.js";
|
|
1
2
|
import { LongFormVariable,ShortFormVariable } from "../lexer/variables.js";
|
|
2
3
|
import { BLACKLISTED_VARIABLE_NAMES } from "./generic/assignableVariableProperty.js";
|
|
3
4
|
|
|
4
5
|
const SHORT_FORM = ShortFormVariable.name;
|
|
5
6
|
const LONG_FORM = LongFormVariable.name;
|
|
7
|
+
const DOT = DotToken.name;
|
|
6
8
|
|
|
7
9
|
const BUILTIN_NAMES = new Set(BLACKLISTED_VARIABLE_NAMES.map((n) => `$${n}`));
|
|
8
10
|
// Also skip $$ (temporary variable)
|
|
@@ -31,6 +33,11 @@ export function scanVariableUsages(tokens, declarations) {
|
|
|
31
33
|
const typeName = token.tokenType.name;
|
|
32
34
|
|
|
33
35
|
if (typeName === SHORT_FORM) {
|
|
36
|
+
// Skip chained property accessors like `$db.my_table.$search`, where the
|
|
37
|
+
// `$search` token is a property name in a member-access chain, not a
|
|
38
|
+
// standalone variable reference. Such tokens are immediately preceded by
|
|
39
|
+
// a Dot token (see chainedIdentifier's singleChainedIdentifier rule).
|
|
40
|
+
if (i > 0 && tokens[i - 1].tokenType.name === DOT) continue;
|
|
34
41
|
// Skip if this is a declaration site
|
|
35
42
|
if (declOffsets.has(token.startOffset)) continue;
|
|
36
43
|
// Skip built-in variable names
|