Skip to content

Commit eb42ab7

Browse files
committed
Enhance ScrapScript Language Server: Implemented comprehensive tests for parsing, validation, completion, and hover functionalities. Refactored existing code for improved readability and maintainability, while removing deprecated functions and optimizing symbol resolution.
1 parent 0711523 commit eb42ab7

File tree

2 files changed

+601
-674
lines changed

2 files changed

+601
-674
lines changed

server/src/server.test.ts

Lines changed: 148 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,175 @@
11
import { TextDocument } from "vscode-languageserver-textdocument";
2-
import { SymbolKind } from "vscode-languageserver";
32
import {
43
initializeParser,
54
parse,
6-
findNodesOfType,
5+
validateScrapScript,
6+
getCompletionItems,
7+
getHoverInfo,
78
getDocumentSymbols,
89
} from "./server";
10+
import {
11+
CompletionItem,
12+
Hover,
13+
MarkupContent,
14+
} from "vscode-languageserver/node";
915

10-
describe("Parser", () => {
11-
beforeAll(async () => {
12-
await initializeParser();
13-
});
16+
describe("ScrapScript Language Server", () => {
17+
const exampleCode = `
18+
()
1419
15-
test("should parse simple expressions", () => {
16-
const result = parse("1 + 2");
17-
expect(result).toBeDefined();
18-
expect(result.rootNode).toBeDefined();
19-
20-
// Find the binary expression node
21-
const binaryNodes = findNodesOfType(result.rootNode, "binary");
22-
expect(binaryNodes).toHaveLength(1);
23-
const binaryNode = binaryNodes[0];
24-
25-
// Check the operator
26-
const operatorNode = binaryNode.child(1);
27-
expect(operatorNode?.text).toBe("+");
28-
29-
// Check the operands
30-
const leftNode = binaryNode.child(0);
31-
const rightNode = binaryNode.child(2);
32-
expect(leftNode?.text).toBe("1");
33-
expect(rightNode?.text).toBe("2");
34-
});
20+
; xyz = x + y * z
21+
; x = 10
22+
; y = 20
23+
; z = 30
3524
36-
test("should parse function definitions", () => {
37-
const result = parse("fn add(a, b) { a + b }");
38-
expect(result).toBeDefined();
39-
expect(result.rootNode).toBeDefined();
25+
; double = x -> x * 2
4026
41-
// Find the function node
42-
const funNodes = findNodesOfType(result.rootNode, "fun");
43-
expect(funNodes).toHaveLength(1);
44-
const funNode = funNodes[0];
27+
; is-zero =
28+
| 0 -> #true ()
29+
| _ -> #false ()
4530
46-
// Check the function name
47-
const idNode = funNode.child(0);
48-
expect(idNode?.text).toBe("add");
31+
; person =
32+
{ name = "John"
33+
, age = 30
34+
, address =
35+
{ street = "123 Main St"
36+
, city = "Anytown"
37+
, country = "USA"
38+
}
39+
}
4940
50-
// Check the parameters
51-
const paramsNode = funNode.child(1);
52-
expect(paramsNode?.text).toBe("(a, b)");
53-
});
41+
; numbers =
42+
[1, 2, 3, 4, 5]
43+
|> list/map (x -> x * 2)
44+
|> list/map (x -> x + 1)
5445
55-
test("should handle nested expressions", () => {
56-
const result = parse("fn calculate(x) { (x + 1) * 2 }");
57-
expect(result).toBeDefined();
58-
expect(result.rootNode).toBeDefined();
46+
; sum = list/fold 0 (+) numbers
5947
60-
// Find the function node
61-
const funNodes = findNodesOfType(result.rootNode, "fun");
62-
expect(funNodes).toHaveLength(1);
48+
; factorial =
49+
| n ? n < 0 -> #error "negative input"
50+
| 0 -> #ok 1
51+
| n -> #ok (n * compute (n - 1))
6352
64-
// Find the binary expressions
65-
const binaryNodes = findNodesOfType(result.rootNode, "binary");
66-
expect(binaryNodes.length).toBeGreaterThan(0);
67-
});
53+
; compose = f -> g -> x -> f (g x)
54+
`;
6855

69-
test("should handle invalid syntax", () => {
70-
expect(() => parse("1 +")).toThrow();
71-
expect(() => parse("fn add(a,) {}")).toThrow();
72-
});
73-
});
56+
let document: TextDocument;
7457

75-
describe("Symbol Resolution", () => {
7658
beforeAll(async () => {
7759
await initializeParser();
60+
document = TextDocument.create(
61+
"file:///example.ss",
62+
"scrapscript",
63+
1,
64+
exampleCode
65+
);
66+
});
67+
68+
describe("Parser", () => {
69+
it("should initialize parser successfully", async () => {
70+
await expect(initializeParser()).resolves.not.toThrow();
71+
});
72+
73+
it("should parse valid ScrapScript code", () => {
74+
const tree = parse(exampleCode);
75+
expect(tree.rootNode).toBeDefined();
76+
expect(tree.rootNode.hasError).toBe(false);
77+
});
78+
});
79+
80+
describe("Validation", () => {
81+
it("should validate valid ScrapScript code", () => {
82+
const diagnostics = validateScrapScript(exampleCode, 1000);
83+
expect(diagnostics).toHaveLength(0);
84+
});
85+
86+
it("should detect syntax errors", () => {
87+
const invalidCode = "; x = 10 + ;";
88+
const diagnostics = validateScrapScript(invalidCode, 1000);
89+
expect(diagnostics.length).toBeGreaterThan(0);
90+
expect(diagnostics[0].severity).toBe(1); // Error severity
91+
});
7892
});
7993

80-
function createDocument(content: string): TextDocument {
81-
return TextDocument.create("file:///test.scrap", "scrapscript", 1, content);
82-
}
83-
84-
test("should resolve local variables", () => {
85-
const code = `x + 2 ; x = 1`;
86-
const document = createDocument(code);
87-
const symbols = getDocumentSymbols(document);
88-
expect(symbols).toHaveLength(1);
89-
expect(symbols[0].name).toBe("x");
90-
expect(symbols[0].kind).toBe(SymbolKind.Variable);
94+
describe("Completion", () => {
95+
it("should provide operator completions", () => {
96+
const position = { line: 1, character: 8 }; // After "xyz = x + y"
97+
const completions = getCompletionItems(document, position);
98+
expect(completions.some((c: CompletionItem) => c.label === "*")).toBe(
99+
true
100+
);
101+
expect(completions.some((c: CompletionItem) => c.label === "/")).toBe(
102+
true
103+
);
104+
});
105+
106+
it("should provide tag completions", () => {
107+
const position = { line: 10, character: 12 }; // After "| 0 -> #"
108+
const completions = getCompletionItems(document, position);
109+
expect(completions.some((c: CompletionItem) => c.label === "true")).toBe(
110+
true
111+
);
112+
expect(completions.some((c: CompletionItem) => c.label === "false")).toBe(
113+
true
114+
);
115+
});
91116
});
92117

93-
test("should resolve function parameters", () => {
94-
const code = `a -> b -> a + b`;
95-
const document = createDocument(code);
96-
const symbols = getDocumentSymbols(document);
97-
expect(symbols).toHaveLength(1); // Only the function itself is a symbol
98-
expect(symbols[0].name).toBe("add");
99-
expect(symbols[0].kind).toBe(SymbolKind.Function);
118+
describe("Hover", () => {
119+
it("should provide hover info for operators", () => {
120+
const position = { line: 1, character: 8 }; // After "xyz = x + y"
121+
const hover = getHoverInfo(document, position);
122+
expect(hover).toBeDefined();
123+
if (
124+
hover &&
125+
typeof hover.contents === "object" &&
126+
"value" in hover.contents
127+
) {
128+
expect(hover.contents.value).toContain("Operator");
129+
}
130+
});
131+
132+
it("should provide hover info for tags", () => {
133+
const position = { line: 10, character: 12 }; // After "| 0 -> #"
134+
const hover = getHoverInfo(document, position);
135+
expect(hover).toBeDefined();
136+
if (
137+
hover &&
138+
typeof hover.contents === "object" &&
139+
"value" in hover.contents
140+
) {
141+
expect(hover.contents.value).toContain("Tag");
142+
}
143+
});
100144
});
101145

102-
test("should detect undefined variables", () => {
103-
const code = `x + 1`;
104-
const document = createDocument(code);
105-
const symbols = getDocumentSymbols(document);
106-
expect(symbols).toHaveLength(1); // Only the function itself is a symbol
107-
expect(symbols[0].name).toBe("test");
108-
expect(symbols[0].kind).toBe(SymbolKind.Function);
146+
describe("Document Symbols", () => {
147+
it("should find all declarations", () => {
148+
const symbols = getDocumentSymbols(document);
149+
expect(symbols.length).toBeGreaterThan(0);
150+
151+
// Check for specific declarations
152+
const symbolNames = symbols.map((s) => s.name);
153+
expect(symbolNames).toContain("double");
154+
expect(symbolNames).toContain("is-zero");
155+
expect(symbolNames).toContain("person");
156+
expect(symbolNames).toContain("numbers");
157+
expect(symbolNames).toContain("sum");
158+
expect(symbolNames).toContain("factorial");
159+
expect(symbolNames).toContain("compose");
160+
});
161+
162+
it("should correctly identify symbol kinds", () => {
163+
const symbols = getDocumentSymbols(document);
164+
165+
const doubleSymbol = symbols.find((s) => s.name === "double");
166+
expect(doubleSymbol?.kind).toBe(2); // Function kind
167+
168+
const personSymbol = symbols.find((s) => s.name === "person");
169+
expect(personSymbol?.kind).toBe(5); // Object kind
170+
171+
const numbersSymbol = symbols.find((s) => s.name === "numbers");
172+
expect(numbersSymbol?.kind).toBe(3); // Array kind
173+
});
109174
});
110175
});

0 commit comments

Comments
 (0)