@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "12.6.0",
3
+ "version": "12.7.0",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -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
- $.addWarning(
34
- `Using \`$\` prefixed properties '${token.image}' is discouraged.`,
35
- token
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
@@ -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