@xano/xanoscript-language-server 11.10.0 → 11.11.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.
- package/.claude/settings.local.json +28 -13
- package/language-server-worker.js +2 -1
- package/onCompletion/onCompletion.js +24 -3
- package/onSemanticCheck/highlight.js +45 -4
- package/onSemanticCheck/onSemanticCheck.js +14 -3
- package/package.json +1 -1
- package/parser/functions/db/dbDelFn.spec.js +5 -0
- package/parser/functions/schema/schemaParseAttributeFn.js +2 -10
- package/parser/functions/schema/schemaParseAttributeFn.spec.js +4 -2
- package/parser/multidoc.spec.js +58 -0
- package/.claude/memory/feedback_no_git.md +0 -11
|
@@ -2,20 +2,35 @@
|
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
4
|
"Bash(npm test:*)",
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
5
|
+
"Bash(npm run lint)",
|
|
6
|
+
"Bash(npm run test:*)",
|
|
7
|
+
"Bash(npx eslint:*)",
|
|
8
|
+
"Bash(node:*)",
|
|
9
|
+
"Bash(npm install)",
|
|
8
10
|
"Bash(grep:*)",
|
|
11
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all --pretty=format:\"%h %s\")",
|
|
12
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all -S \"password\\\\|secret\\\\|api_key\\\\|token\" --oneline)",
|
|
13
|
+
"Bash(xargs file:*)",
|
|
9
14
|
"Bash(find:*)",
|
|
10
|
-
"
|
|
11
|
-
"Bash(git
|
|
12
|
-
"Bash(
|
|
13
|
-
"Bash(
|
|
14
|
-
"Bash(
|
|
15
|
-
"Bash(
|
|
16
|
-
"Bash(
|
|
17
|
-
"Bash(
|
|
18
|
-
"Bash(
|
|
19
|
-
|
|
15
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log -p --all -S \"webhook\\\\|Bearer\\\\|discord\\\\|slack\\\\|xano_insiders\" -- \"*.xs\" \"*.js\")",
|
|
16
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server ls-files:*)",
|
|
17
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server status)",
|
|
18
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server diff --stat)",
|
|
19
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --oneline -5)",
|
|
20
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server add README.md LICENSE package.json lexer/tests/query/valid_sources/basic_query.xs parser/functions/stream/streamFromRequestFn.js parser/functions/stream/streamFromRequestFn.spec.js parser/tests/function/valid_sources/discord_poll_send_to_slack.xs parser/tests/query/valid_sources/all_basics.xs)",
|
|
21
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server commit -m \"$\\(cat <<''EOF''\nPrepare package for public npm release as @xano/xanoscript-language-server\n\n- Rename package to @xano/xanoscript-language-server\n- Add MIT LICENSE file\n- Add npm metadata \\(description, repository, author, license, bugs, homepage\\)\n- Remove private flag to allow publishing\n- Sanitize test files: replace internal URLs, IDs, and env var names with placeholders\n- Simplify README maintainer section\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
22
|
+
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log -1 --oneline)",
|
|
23
|
+
"Bash(git add:*)",
|
|
24
|
+
"Bash(git commit:*)",
|
|
25
|
+
"Bash(git push)",
|
|
26
|
+
"Bash(npm whoami:*)",
|
|
27
|
+
"Bash(npm view:*)",
|
|
28
|
+
"Bash(npm version:*)",
|
|
29
|
+
"Bash(npm publish:*)",
|
|
30
|
+
"Bash(wc:*)",
|
|
31
|
+
"Bash(git push:*)"
|
|
32
|
+
],
|
|
33
|
+
"deny": [],
|
|
34
|
+
"ask": []
|
|
20
35
|
}
|
|
21
36
|
}
|
|
@@ -11,6 +11,7 @@ import { onDidChangeContent } from "./onDidChangeContent/onDidChangeContent.js";
|
|
|
11
11
|
import { onHover } from "./onHover/onHover.js";
|
|
12
12
|
import { onSemanticCheck } from "./onSemanticCheck/onSemanticCheck.js";
|
|
13
13
|
import { TOKEN_TYPES } from "./onSemanticCheck/tokens.js";
|
|
14
|
+
import pkg from "./package.json" with { type: "json" };
|
|
14
15
|
|
|
15
16
|
const messageReader = new BrowserMessageReader(self);
|
|
16
17
|
const messageWriter = new BrowserMessageWriter(self);
|
|
@@ -47,6 +48,6 @@ connection.onRequest("textDocument/semanticTokens/full", (params) =>
|
|
|
47
48
|
documents.onDidChangeContent((params) =>
|
|
48
49
|
onDidChangeContent(params, connection)
|
|
49
50
|
);
|
|
50
|
-
connection.onInitialized(() => console.log(
|
|
51
|
+
connection.onInitialized(() => console.log(`XanoScript Language Server v${pkg.version}`));
|
|
51
52
|
documents.listen(connection);
|
|
52
53
|
connection.listen();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { documentCache } from "../cache/documentCache.js";
|
|
1
2
|
import { mapToVirtualJS } from "../embedded/embeddedContent.js";
|
|
3
|
+
import { findSegmentAtOffset } from "../parser/multidoc.js";
|
|
2
4
|
import { getSchemeFromContent } from "../utils.js";
|
|
3
5
|
import { workspaceIndex } from "../workspace/workspaceIndex.js";
|
|
4
6
|
import { getContentAssistSuggestions } from "./contentAssist.js";
|
|
@@ -32,9 +34,28 @@ export function onCompletion(params, documents) {
|
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
37
|
+
// For multidoc, resolve to the correct segment
|
|
38
|
+
const { parser } = documentCache.getOrParse(
|
|
39
|
+
params.textDocument.uri,
|
|
40
|
+
document.version,
|
|
41
|
+
text,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
let scheme;
|
|
45
|
+
let prefix;
|
|
46
|
+
|
|
47
|
+
if (parser.isMultidoc) {
|
|
48
|
+
const match = findSegmentAtOffset(parser, offset);
|
|
49
|
+
if (!match) return null;
|
|
50
|
+
scheme = getSchemeFromContent(match.segment.text);
|
|
51
|
+
// Prefix is everything in the full text up to the cursor — contentAssist
|
|
52
|
+
// parses backwards from the cursor to find context, so it needs the segment
|
|
53
|
+
// text up to the local offset
|
|
54
|
+
prefix = match.segment.text.slice(0, match.localOffset);
|
|
55
|
+
} else {
|
|
56
|
+
scheme = getSchemeFromContent(text);
|
|
57
|
+
prefix = text.slice(0, offset);
|
|
58
|
+
}
|
|
38
59
|
|
|
39
60
|
const suggestions = getContentAssistSuggestions(prefix, scheme);
|
|
40
61
|
|
|
@@ -10,10 +10,51 @@ import { encodeTokenType } from "./tokens.js";
|
|
|
10
10
|
* @returns {SemanticTokensBuilder} Returns null if the scheme is not supported
|
|
11
11
|
*/
|
|
12
12
|
export function higlightText(scheme, text, SemanticTokensBuilder) {
|
|
13
|
-
return higlightDefault(text, SemanticTokensBuilder);
|
|
13
|
+
return higlightDefault(text, 0, SemanticTokensBuilder);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Highlight a multidoc by highlighting each segment independently.
|
|
18
|
+
* @param {Array<{text: string, globalOffset: number}>} segments - The segments from multidocParser
|
|
19
|
+
* @param {SemanticTokensBuilder} SemanticTokensBuilder - The semantic tokens builder constructor
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Highlight a multidoc by highlighting each segment independently.
|
|
23
|
+
* @param {Array<{text: string, globalOffset: number}>} segments - The segments from multidocParser
|
|
24
|
+
* @param {SemanticTokensBuilder} SemanticTokensBuilder - The semantic tokens builder constructor
|
|
25
|
+
*/
|
|
26
|
+
export function higlightSegments(segments, SemanticTokensBuilder) {
|
|
27
|
+
const builder = new SemanticTokensBuilder();
|
|
28
|
+
|
|
29
|
+
// Pre-compute line offsets: count newlines in each previous segment + separator
|
|
30
|
+
let lineOffset = 0;
|
|
31
|
+
|
|
32
|
+
for (const segment of segments) {
|
|
33
|
+
const lexResult = lexDocument(segment.text, true);
|
|
34
|
+
|
|
35
|
+
for (const token of lexResult.tokens) {
|
|
36
|
+
const tokenType = mapTokenToType(token.tokenType.name);
|
|
37
|
+
if (tokenType) {
|
|
38
|
+
builder.push(
|
|
39
|
+
token.startLine - 1 + lineOffset,
|
|
40
|
+
token.startColumn - 1,
|
|
41
|
+
token.image.length,
|
|
42
|
+
encodeTokenType(tokenType),
|
|
43
|
+
0,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Advance lineOffset: lines in this segment's text + 2 for the \n---\n separator
|
|
49
|
+
// (separator has 2 newlines: one before --- and one after)
|
|
50
|
+
const segmentLines = segment.text.split("\n").length - 1;
|
|
51
|
+
lineOffset += segmentLines + 2;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return builder.build();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function higlightDefault(text, lineOffset, SemanticTokensBuilder) {
|
|
17
58
|
const builder = new SemanticTokensBuilder();
|
|
18
59
|
|
|
19
60
|
// Map Chevrotain tokens to semantic token types
|
|
@@ -23,8 +64,8 @@ function higlightDefault(text, SemanticTokensBuilder) {
|
|
|
23
64
|
lexResult.tokens.forEach((token) => {
|
|
24
65
|
const tokenType = mapTokenToType(token.tokenType.name);
|
|
25
66
|
if (tokenType) {
|
|
26
|
-
const line = token.startLine - 1
|
|
27
|
-
const character = token.startColumn - 1;
|
|
67
|
+
const line = token.startLine - 1 + lineOffset;
|
|
68
|
+
const character = token.startColumn - 1;
|
|
28
69
|
builder.push(
|
|
29
70
|
line,
|
|
30
71
|
character,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { documentCache } from "../cache/documentCache.js";
|
|
2
|
+
import { getSchemeFromContent } from "../utils.js";
|
|
3
|
+
import { higlightSegments,higlightText } from "./highlight.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Handles a semantic tokens request for the full document.
|
|
@@ -19,7 +20,17 @@ export function onSemanticCheck(params, documents, SemanticTokensBuilder) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const text = document.getText();
|
|
22
|
-
const scheme = getSchemeFromContent(text);
|
|
23
23
|
|
|
24
|
+
const { parser } = documentCache.getOrParse(
|
|
25
|
+
params.textDocument.uri,
|
|
26
|
+
document.version,
|
|
27
|
+
text,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (parser.isMultidoc) {
|
|
31
|
+
return higlightSegments(parser.segments, SemanticTokensBuilder);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const scheme = getSchemeFromContent(text);
|
|
24
35
|
return higlightText(scheme, text, SemanticTokensBuilder);
|
|
25
36
|
}
|
package/package.json
CHANGED
|
@@ -46,6 +46,11 @@ describe("dbDelFn", () => {
|
|
|
46
46
|
expect(parser.errors).to.be.empty;
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it("dbDelFn rejects comma separated attributes", () => {
|
|
50
|
+
const parser = parse(`del account {field_name = "id", field_value = "a"}`);
|
|
51
|
+
expect(parser.errors).to.not.be.empty;
|
|
52
|
+
});
|
|
53
|
+
|
|
49
54
|
it("dbDelFn requires a field_name", () => {
|
|
50
55
|
const parser = parse(`del user {
|
|
51
56
|
field_value = $input.email
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { get, isPlainObject, keys, some } from "lodash-es";
|
|
2
2
|
import { ExclamationToken } from "../../../lexer/cast.js";
|
|
3
|
-
import {
|
|
4
|
-
CommaToken,
|
|
5
|
-
EqualToken,
|
|
6
|
-
LCurly,
|
|
7
|
-
RCurly,
|
|
8
|
-
} from "../../../lexer/control.js";
|
|
3
|
+
import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
|
|
9
4
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
10
5
|
import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
|
|
11
6
|
import {
|
|
@@ -156,10 +151,7 @@ function _schemaParseAttributeFn($) {
|
|
|
156
151
|
$.CONSUME1(NewlineToken);
|
|
157
152
|
});
|
|
158
153
|
|
|
159
|
-
|
|
160
|
-
needSeparator = false;
|
|
161
|
-
$.CONSUME1(CommaToken);
|
|
162
|
-
});
|
|
154
|
+
lastToken = objectKeyToken;
|
|
163
155
|
});
|
|
164
156
|
$.CONSUME(RCurly); // "}"
|
|
165
157
|
|
|
@@ -127,15 +127,17 @@ describe("schemaParseAttributeFn", () => {
|
|
|
127
127
|
expect(parser.errors).to.be.empty;
|
|
128
128
|
|
|
129
129
|
parse(`value = {
|
|
130
|
-
something = 123.2
|
|
130
|
+
something = 123.2
|
|
131
131
|
other_thing = 44
|
|
132
132
|
}`).schemaParseAttributeFn_flat();
|
|
133
133
|
expect(parser.errors).to.be.empty;
|
|
134
|
+
});
|
|
134
135
|
|
|
136
|
+
it("schemaParseAttributeFn rejects comma separated attributes", () => {
|
|
135
137
|
parse(
|
|
136
138
|
`value = { something = 123.2, other_thing = 44 }`,
|
|
137
139
|
).schemaParseAttributeFn_flat();
|
|
138
|
-
expect(parser.errors).to.be.empty;
|
|
140
|
+
expect(parser.errors).to.not.be.empty;
|
|
139
141
|
});
|
|
140
142
|
|
|
141
143
|
it("schemaParseAttributeFn immutable restricts to valid token", () => {
|
package/parser/multidoc.spec.js
CHANGED
|
@@ -860,6 +860,64 @@ function foo {
|
|
|
860
860
|
// Both segments are empty strings — should not crash
|
|
861
861
|
});
|
|
862
862
|
|
|
863
|
+
it("should parse workspace + table multidoc without errors on separator", () => {
|
|
864
|
+
const text = `workspace "car-coverage" {
|
|
865
|
+
acceptance = {ai_terms: false}
|
|
866
|
+
preferences = {
|
|
867
|
+
internal_docs : false
|
|
868
|
+
track_performance: true
|
|
869
|
+
sql_names : false
|
|
870
|
+
sql_columns : true
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
realtime = {canonical: "UJCEVaoiEJN-wjghNzMxj324-8w"}
|
|
874
|
+
env = {api_key: "12345"}
|
|
875
|
+
}
|
|
876
|
+
---
|
|
877
|
+
table user {
|
|
878
|
+
auth = true
|
|
879
|
+
|
|
880
|
+
schema {
|
|
881
|
+
int id
|
|
882
|
+
timestamp created_at?=now
|
|
883
|
+
text name filters=trim
|
|
884
|
+
email? email filters=trim|lower
|
|
885
|
+
password? password filters=min:8|minAlpha:1|minDigit:1
|
|
886
|
+
text? refresh_token {
|
|
887
|
+
sensitive = true
|
|
888
|
+
visibility = "private"
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
timestamp? refresh_token_expires
|
|
892
|
+
|
|
893
|
+
// some description
|
|
894
|
+
enum my_enum {
|
|
895
|
+
values = ["true", "false", "2"]
|
|
896
|
+
sensitive = true
|
|
897
|
+
visibility = "private"
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
object test_visibility? {
|
|
901
|
+
schema {
|
|
902
|
+
text internal? filters=trim {
|
|
903
|
+
visibility = "internal"
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
index = [
|
|
910
|
+
{type: "primary", field: [{name: "id"}]}
|
|
911
|
+
{type: "btree", field: [{name: "created_at", op: "desc"}]}
|
|
912
|
+
{type: "btree|unique", field: [{name: "email", op: "asc"}]}
|
|
913
|
+
]
|
|
914
|
+
}`;
|
|
915
|
+
const result = multidocParser(text);
|
|
916
|
+
expect(result.isMultidoc).to.be.true;
|
|
917
|
+
expect(result.segmentCount).to.equal(2);
|
|
918
|
+
expect(result.errors).to.be.empty;
|
|
919
|
+
});
|
|
920
|
+
|
|
863
921
|
it("should not treat --- inside a string literal as separator", () => {
|
|
864
922
|
// This is a known limitation — but test current behavior
|
|
865
923
|
const text = `function foo {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: no-git-interactions
|
|
3
|
-
description: User does not want Claude to interact with git (no commits, no staging, no git commands)
|
|
4
|
-
type: feedback
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Do not interact with git — no commits, no staging, no git commands. Focus purely on implementation and testing.
|
|
8
|
-
|
|
9
|
-
**Why:** User manages their own git workflow.
|
|
10
|
-
|
|
11
|
-
**How to apply:** Skip all git-related steps in plans. When dispatching subagents, explicitly tell them not to run any git commands. Remove commit steps from task instructions.
|