@xano/xanoscript-language-server 11.8.1 → 11.8.3

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/lexer/table.js CHANGED
@@ -19,7 +19,18 @@ export const ItemsToken = createTokenByName("items", {
19
19
  categories: [Identifier],
20
20
  });
21
21
 
22
- export const TableTokens = [TableToken, AutoCompleteToken, ItemsToken];
22
+ // "visibility"
23
+ export const VisibilityToken = createTokenByName("visibility", {
24
+ longer_alt: Identifier,
25
+ categories: [Identifier],
26
+ });
27
+
28
+ export const TableTokens = [
29
+ TableToken,
30
+ AutoCompleteToken,
31
+ ItemsToken,
32
+ VisibilityToken,
33
+ ];
23
34
 
24
35
  /**
25
36
  * Maps a token name to a type
@@ -31,6 +42,7 @@ export function mapTokenToType(token) {
31
42
  return "keyword";
32
43
  case AutoCompleteToken.name:
33
44
  case ItemsToken.name:
45
+ case VisibilityToken.name:
34
46
  return "identifier";
35
47
  default:
36
48
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "11.8.1",
3
+ "version": "11.8.3",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -1,12 +1,13 @@
1
1
  import { columnDefaultValueAttribute } from "./columnDefaultValueAttribute.js";
2
2
  import { descriptionFieldAttribute } from "./descriptionFieldAttribute.js";
3
3
  import { disabledFieldAttribute } from "./disabledFieldAttribute.js";
4
- import { docsFieldAttribute } from "./docsFieldAttribute.js"
4
+ import { docsFieldAttribute } from "./docsFieldAttribute.js";
5
5
  import { guidFieldAttribute } from "./guidFieldAttribute.js";
6
6
  import { inputFilterFn } from "./inputFilterFn.js";
7
7
  import { sensitiveFieldAttribute } from "./sensitiveFieldAttribute.js";
8
8
  import { valueFieldAttribute } from "./valueFieldAttribute.js";
9
9
  import { valuesFieldAttribute } from "./valuesFieldAttribute.js";
10
+ import { visibilityFieldAttribute } from "./visibilityFieldAttribute.js";
10
11
 
11
12
  /**
12
13
  * Registers all the parsers in this folder
@@ -15,32 +16,30 @@ import { valuesFieldAttribute } from "./valuesFieldAttribute.js";
15
16
  export const register = ($) => {
16
17
  $.columnDefaultValueAttribute = $.RULE(
17
18
  "columnDefaultValueAttribute",
18
- columnDefaultValueAttribute($)
19
+ columnDefaultValueAttribute($),
19
20
  );
20
21
  $.descriptionFieldAttribute = $.RULE(
21
22
  "descriptionFieldAttribute",
22
- descriptionFieldAttribute($)
23
+ descriptionFieldAttribute($),
23
24
  );
24
25
  $.disabledFieldAttribute = $.RULE(
25
26
  "disabledFieldAttribute",
26
- disabledFieldAttribute($)
27
- );
28
- $.docsFieldAttribute = $.RULE(
29
- "docsFieldAttribute",
30
- docsFieldAttribute($)
31
- );
32
- $.guidFieldAttribute = $.RULE(
33
- "guidFieldAttribute",
34
- guidFieldAttribute($)
27
+ disabledFieldAttribute($),
35
28
  );
29
+ $.docsFieldAttribute = $.RULE("docsFieldAttribute", docsFieldAttribute($));
30
+ $.guidFieldAttribute = $.RULE("guidFieldAttribute", guidFieldAttribute($));
36
31
  $.inputFilterFn = $.RULE("inputFilterFn", inputFilterFn($));
37
32
  $.sensitiveFieldAttribute = $.RULE(
38
33
  "sensitiveFieldAttribute",
39
- sensitiveFieldAttribute($)
34
+ sensitiveFieldAttribute($),
40
35
  );
41
36
  $.valueFieldAttribute = $.RULE("valueFieldAttribute", valueFieldAttribute($));
42
37
  $.valuesFieldAttribute = $.RULE(
43
38
  "valuesFieldAttribute",
44
- valuesFieldAttribute($)
39
+ valuesFieldAttribute($),
40
+ );
41
+ $.visibilityFieldAttribute = $.RULE(
42
+ "visibilityFieldAttribute",
43
+ visibilityFieldAttribute($),
45
44
  );
46
45
  };
@@ -0,0 +1,17 @@
1
+ import { EqualToken } from "../../lexer/control.js";
2
+ import { VisibilityToken } from "../../lexer/table.js";
3
+
4
+ /**
5
+ * Visibility Field Attribute Parser on table metadata definitions
6
+ * @param {import('../base_parser.js').XanoBaseParser} $
7
+ * @returns
8
+ */
9
+ export const visibilityFieldAttribute = ($) => () => {
10
+ $.sectionStack.push("visibilityFieldAttribute");
11
+ const parent = $.CONSUME(VisibilityToken); // "visibility"
12
+ $.CONSUME(EqualToken); // "="
13
+ $.SUBRULE($.schemaParseEnumFn, {
14
+ ARGS: [parent, ["private", "internal", "public"]],
15
+ });
16
+ $.sectionStack.pop();
17
+ };
@@ -0,0 +1,44 @@
1
+ import { expect } from "chai";
2
+ import { describe, it } from "mocha";
3
+ import { lexDocument } from "../../lexer/lexer.js";
4
+ import { parser } from "../test_parser.js";
5
+
6
+ function parse(inputText) {
7
+ parser.reset();
8
+ const lexResult = lexDocument(inputText);
9
+ parser.input = lexResult.tokens;
10
+ parser.visibilityFieldAttribute();
11
+ return parser;
12
+ }
13
+
14
+ describe("visibilityFieldAttribute", () => {
15
+ it("visibilityFieldAttribute accepts a private value", () => {
16
+ const parser = parse(`visibility = "private"`);
17
+ expect(parser.errors).to.be.empty;
18
+ });
19
+
20
+ it("visibilityFieldAttribute accepts an internal value", () => {
21
+ const parser = parse(`visibility = "internal"`);
22
+ expect(parser.errors).to.be.empty;
23
+ });
24
+
25
+ it("visibilityFieldAttribute accepts a public value", () => {
26
+ const parser = parse(`visibility = "public"`);
27
+ expect(parser.errors).to.be.empty;
28
+ });
29
+
30
+ it("visibilityFieldAttribute rejects anything else", () => {
31
+ const parser = parse(`visibility = "something"`);
32
+ expect(parser.errors).to.not.be.empty;
33
+ });
34
+
35
+ it("visibilityFieldAttribute rejects digit", () => {
36
+ const parser = parse("visibility = 123");
37
+ expect(parser.errors).to.not.be.empty;
38
+ });
39
+
40
+ it("visibilityFieldAttribute can be compact", () => {
41
+ const parser = parse(`visibility="private"`);
42
+ expect(parser.errors).to.be.empty;
43
+ });
44
+ });
@@ -3,10 +3,19 @@ import { describe, it } from "mocha";
3
3
  import { lexDocument } from "../../lexer/lexer.js";
4
4
  import { parser } from "../test_parser.js";
5
5
 
6
- function parse(inputText) {
6
+ function parse(inputText, context = null) {
7
+ parser.reset();
7
8
  const lexResult = lexDocument(inputText);
8
9
  parser.input = lexResult.tokens;
9
- parser.schemaClause();
10
+
11
+ if (context) {
12
+ parser.sectionStack.push(context);
13
+ parser.schemaClause();
14
+ parser.sectionStack.pop();
15
+ } else {
16
+ parser.schemaClause();
17
+ }
18
+
10
19
  return parser;
11
20
  }
12
21
 
@@ -39,6 +48,33 @@ describe("schemaClause", () => {
39
48
  expect(parser.errors).to.be.empty;
40
49
  });
41
50
 
51
+ it("schemaClause accepts a visibility when in a table definition", () => {
52
+ const parser = parse(
53
+ `schema {
54
+ int rank {
55
+ visibility = "private"
56
+ }
57
+ text label {
58
+ visibility = "internal"
59
+ }
60
+ }`,
61
+ "tableDeclaration",
62
+ );
63
+ expect(parser.errors).to.be.empty;
64
+ });
65
+
66
+ it("schemaClause rejects a visibility when not in a table definition", () => {
67
+ const parser = parse(`schema {
68
+ int rank {
69
+ visibility = "private"
70
+ }
71
+ text label {
72
+ visibility = "internal"
73
+ }
74
+ }`);
75
+ expect(parser.errors).to.not.be.empty;
76
+ });
77
+
42
78
  it("schemaClause accepts a description", () => {
43
79
  const parser = parse(`schema {
44
80
  text label {
@@ -107,6 +107,7 @@ export function columnDefinition($) {
107
107
  "table?": "[string]",
108
108
  "size?": "[number]",
109
109
  "sensitive?": "[boolean]",
110
+ "visibility?": ["public", "private", "internal"],
110
111
  },
111
112
  captured,
112
113
  ],
@@ -115,7 +116,15 @@ export function columnDefinition($) {
115
116
  if (inputTypeToken.image == "vector" && !captured.size) {
116
117
  $.addWarning(
117
118
  'Column named "vector" should have a size attribute defining its length.',
118
- nameToken
119
+ nameToken,
120
+ );
121
+ }
122
+
123
+ const inTable = $.sectionStack.includes("tableDeclaration");
124
+ if (captured.visibility && !inTable) {
125
+ $.addInvalidValueError(
126
+ nameToken,
127
+ 'The "visibility" attribute is only valid within a table schema definition.',
119
128
  );
120
129
  }
121
130
 
@@ -131,7 +140,7 @@ export function columnDefinition($) {
131
140
  inputTypeToken.image,
132
141
  iterable,
133
142
  nullable,
134
- optional
143
+ optional,
135
144
  );
136
145
 
137
146
  $.sectionStack.pop();
@@ -34,6 +34,15 @@ describe("dbQueryFn", () => {
34
34
  expect(parser.errors).to.be.empty;
35
35
  });
36
36
 
37
+ it("dbQueryFn where can split line on binary operators", () => {
38
+ const parser = parse(`query user {
39
+ where = $db.user.visa_work_permit_end_date != null &&
40
+ $db.user.visa_work_permit_end_date <= $input.next_120_days && $db.bug_test_candidate.candidate_status == "mediated" && ($db.user.billing_end_date > now || $db.user.billing_end_date == null)
41
+ return = {type: "list"}
42
+ } as $results`);
43
+ expect(parser.errors).to.be.empty;
44
+ });
45
+
37
46
  it("dbQueryFn accepts a description", () => {
38
47
  const parser = parse(`query user {
39
48
  where = $db.array_columns @> $db.array_columns.id
@@ -17,6 +17,7 @@ export function redisPopFn($) {
17
17
  key: "[expression]",
18
18
  "description?": "[string]",
19
19
  "disabled?": "[boolean]",
20
+ "count?": "[expression]",
20
21
  },
21
22
  ],
22
23
  });
@@ -64,4 +64,12 @@ describe("redis.pop", () => {
64
64
  } as $x7`);
65
65
  expect(parser.errors).to.be.empty;
66
66
  });
67
+
68
+ it("pop accepts an optional count attribute", () => {
69
+ const parser = parse(`pop {
70
+ key = "list"
71
+ count = 5
72
+ } as $x7`);
73
+ expect(parser.errors).to.be.empty;
74
+ });
67
75
  });
@@ -17,6 +17,7 @@ export function redisShiftFn($) {
17
17
  key: "[expression]",
18
18
  "description?": "[string]",
19
19
  "disabled?": "[boolean]",
20
+ "count?": "[expression]",
20
21
  },
21
22
  ],
22
23
  });
@@ -64,4 +64,12 @@ describe("redis.shift", () => {
64
64
  } as $x8`);
65
65
  expect(parser.errors).to.be.empty;
66
66
  });
67
+
68
+ it("shift accepts an optional count attribute", () => {
69
+ const parser = parse(`shift {
70
+ key = "list"
71
+ count = 5
72
+ } as $x8`);
73
+ expect(parser.errors).to.be.empty;
74
+ });
67
75
  });
@@ -27,7 +27,6 @@ describe("workflowExpectFn", () => {
27
27
  ) {
28
28
  value = $som_value
29
29
  }`);
30
- console.log(parser.errors);
31
30
  expect(parser.errors).to.be.empty;
32
31
  });
33
32
  });
@@ -354,6 +354,7 @@ export function expressionTestFn($) {
354
354
  DEF: () => $.CONSUME1(Question), // "?"
355
355
  });
356
356
 
357
+ $.OPTION1(() => $.CONSUME(NewlineToken));
357
358
  $.SUBRULE($.expressionFn, { ARGS: [token, options] });
358
359
  };
359
360
  }
@@ -107,6 +107,13 @@ describe("expressionFn", () => {
107
107
  expect(parser.errors).to.be.empty;
108
108
  });
109
109
 
110
+ it("expressionFn can split on multiple lines on binary operators", () => {
111
+ let parser = parse(`$var.value == "test" &&
112
+ $var.other_value != "example" ||
113
+ $var.another_value > 10`);
114
+ expect(parser.errors).to.be.empty;
115
+ });
116
+
110
117
  it("expressionFn accepts modulus symbol in expression", () => {
111
118
  const parser = parse("$this % 2 == 1");
112
119
  expect(parser.errors).to.be.empty;
@@ -1,46 +1,33 @@
1
- import { LCurly, RCurly } from "../../lexer/control.js";
2
- import { NewlineToken } from "../../lexer/tokens.js";
3
-
4
1
  /**
5
2
  *
6
3
  * @param {import('../base_parser.js').XanoBaseParser} $
7
4
  */
8
5
  export function enumColumnMetadataDefinition($) {
9
- return (parent) => {
6
+ return (parent, captured = {}) => {
10
7
  $.sectionStack.push("enumColumnMetadataDefinition");
11
- $.CONSUME(LCurly); // "{"
12
- // values = [...] is a required field here
13
- let hasValues = false;
14
- let hasDescription = false;
15
- let hasSensitive = false;
16
8
 
17
- $.MANY(() => {
18
- $.AT_LEAST_ONE1(() => $.CONSUME1(NewlineToken)); // Require at least one new line
19
- $.OR([
20
- {
21
- GATE: () => !hasValues,
22
- ALT: () => {
23
- hasValues = true;
24
- $.SUBRULE($.valuesFieldAttribute);
25
- },
26
- },
27
- {
28
- GATE: () => !hasDescription,
29
- ALT: () => $.SUBRULE($.sensitiveFieldAttribute),
30
- },
9
+ $.SUBRULE($.schemaParseAttributeFn, {
10
+ ARGS: [
11
+ parent,
31
12
  {
32
- GATE: () => !hasSensitive,
33
- ALT: () => $.SUBRULE($.descriptionFieldAttribute),
13
+ "description?": "[string]",
14
+ values: ["[string]"],
15
+ "sensitive?": "[boolean]",
16
+ "visibility?": ["public", "private", "internal"],
34
17
  },
35
- ]);
18
+ captured,
19
+ ],
36
20
  });
37
21
 
38
- if (!hasValues) {
39
- $.addMissingError(parent, "{} is missing the values attribute");
22
+ // you can only provide a visibility attribute if you're in a table definition
23
+ const inTable = $.sectionStack.includes("tableDeclaration");
24
+ if (!inTable && "visibility" in captured) {
25
+ $.addInvalidValueError(
26
+ parent,
27
+ 'The "visibility" attribute is only valid within a table schema definition.',
28
+ );
40
29
  }
41
30
 
42
- $.AT_LEAST_ONE2(() => $.CONSUME2(NewlineToken)); // Require at least one new line after
43
- $.CONSUME(RCurly); // "}"
44
31
  $.sectionStack.pop();
45
32
  };
46
33
  }
@@ -3,9 +3,12 @@ import { describe, it } from "mocha";
3
3
  import { lexDocument } from "../../lexer/lexer.js";
4
4
  import { parser } from "../test_parser.js";
5
5
 
6
- function parse(inputText) {
6
+ function parse(inputText, context = null) {
7
7
  const lexResult = lexDocument(inputText);
8
8
  parser.input = lexResult.tokens;
9
+ if (context) {
10
+ parser.sectionStack.push(context);
11
+ }
9
12
  parser.enumColumnMetadataDefinition();
10
13
  return parser;
11
14
  }
@@ -27,6 +30,25 @@ describe("enumColumnMetadataDefinition", () => {
27
30
  expect(parser.errors).to.be.empty;
28
31
  });
29
32
 
33
+ it("enumColumnMetadataDefinition accepts a visibility value when in a table declaration", () => {
34
+ const parser = parse(
35
+ `{
36
+ values = ["active", "inactive", "unknown"]
37
+ visibility = "private"
38
+ }`,
39
+ "tableDeclaration",
40
+ );
41
+ expect(parser.errors).to.be.empty;
42
+ });
43
+
44
+ it("enumColumnMetadataDefinition rejects a visibility value when not in a table declaration", () => {
45
+ const parser = parse(`{
46
+ values = ["active", "inactive", "unknown"]
47
+ visibility = "private"
48
+ }`);
49
+ expect(parser.errors).to.not.be.empty;
50
+ });
51
+
30
52
  it("enumColumnMetadataDefinition accepts a description value", () => {
31
53
  const parser = parse(`{
32
54
  values = ["active", "inactive", "unknown"]
@@ -7,20 +7,57 @@ import { NewlineToken } from "../../lexer/tokens.js";
7
7
  export function objectColumnMetadataDefinition($) {
8
8
  return () => {
9
9
  $.sectionStack.push("objectColumnMetadataDefinition");
10
+
11
+ let hasSensitiveField = false;
12
+ let hasVisibilityField = false;
13
+ let hasDescriptionField = false;
14
+ let hasSchemaClause = false;
15
+
10
16
  $.CONSUME(LCurly); // "{"
11
17
  $.MANY(() => {
12
18
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // Require at least one new line after {
13
19
  $.OR([
14
- { ALT: () => $.SUBRULE($.sensitiveFieldAttribute) },
15
- { ALT: () => $.SUBRULE($.descriptionFieldAttribute) },
16
20
  {
17
- ALT: () =>
18
- $.SUBRULE($.schemaClause, { ARGS: [{ include_file: false }] }),
21
+ GATE: () => !hasSensitiveField, // Only allow the sensitive attribute if it hasn't been used yet
22
+ ALT: () => {
23
+ $.SUBRULE($.sensitiveFieldAttribute);
24
+ hasSensitiveField = true;
25
+ },
26
+ },
27
+ {
28
+ GATE: () => !hasDescriptionField, // Only allow the description attribute if it hasn't been used yet
29
+ ALT: () => {
30
+ $.SUBRULE($.descriptionFieldAttribute);
31
+ hasDescriptionField = true;
32
+ },
33
+ },
34
+ {
35
+ GATE: () => !hasVisibilityField, // Only allow the visibility attribute if it hasn't been used yet
36
+ ALT: () => {
37
+ $.SUBRULE($.visibilityFieldAttribute);
38
+ hasVisibilityField = true;
39
+ },
40
+ },
41
+ {
42
+ GATE: () => !hasSchemaClause, // Only allow the schema clause if it hasn't been used yet
43
+ ALT: () => {
44
+ $.SUBRULE($.schemaClause, { ARGS: [{ include_file: false }] });
45
+ hasSchemaClause = true;
46
+ },
19
47
  },
20
48
  ]);
21
49
  });
22
50
  $.AT_LEAST_ONE1(() => $.CONSUME1(NewlineToken)); // Require at least one new line after {
23
51
  $.CONSUME(RCurly); // "}"
52
+
53
+ const inTable = $.sectionStack.includes("tableDeclaration");
54
+ if (hasVisibilityField && !inTable) {
55
+ $.addInvalidValueError(
56
+ null,
57
+ 'The "visibility" attribute is only valid within a table schema definition.',
58
+ );
59
+ }
60
+
24
61
  $.sectionStack.pop();
25
62
  };
26
63
  }
@@ -69,6 +69,24 @@ describe("table_parser", () => {
69
69
  expect(parser.errors).to.be.empty;
70
70
  });
71
71
 
72
+ it("should should accept a private and internal visibility on input", () => {
73
+ const parser = xanoscriptParser(`table foo {
74
+ auth = false
75
+
76
+ schema {
77
+ int id {
78
+ visibility = "private"
79
+ }
80
+ text cd {
81
+ visibility = "internal"
82
+ }
83
+ }
84
+
85
+ autocomplete = [{ name: "id" }, { name: "cd" }]
86
+ }`);
87
+ expect(parser.errors).to.be.empty;
88
+ });
89
+
72
90
  // autocomplete = [{ name: "id" }, { name: "cd" }];
73
91
  it("should requires a new line between statements", () => {
74
92
  const parser = xanoscriptParser(`table foo {
@@ -3,11 +3,14 @@ table array_columns {
3
3
 
4
4
  schema {
5
5
  int id
6
- timestamp created_at?=now
6
+ timestamp created_at?=now {
7
+ visibility = "internal"
8
+ }
7
9
  text[] many_text? filters=trim
8
10
  email[] many_email_required_sensitive filters=trim|lower {
9
11
  description = "with a description"
10
12
  sensitive = true
13
+ visibility = "private"
11
14
  }
12
15
 
13
16
  enum[] many_enum_required_private {
@@ -131,7 +131,6 @@ describe("workflow_test_parser", () => {
131
131
  }
132
132
  }
133
133
  }`);
134
- console.log(parser.errors);
135
134
  expect(parser.errors).to.be.empty;
136
135
  });
137
136
  });