aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/hover_features_test.js
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
commitecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch)
tree70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /tests/drivers/hover_features_test.js
a
Diffstat (limited to 'tests/drivers/hover_features_test.js')
-rw-r--r--tests/drivers/hover_features_test.js85
1 files changed, 85 insertions, 0 deletions
diff --git a/tests/drivers/hover_features_test.js b/tests/drivers/hover_features_test.js
new file mode 100644
index 0000000..895e923
--- /dev/null
+++ b/tests/drivers/hover_features_test.js
@@ -0,0 +1,85 @@
+// focused type, inference, and block-documentation hover regressions.
+// usage: node tests/drivers/hover_features_test.js path/to/zclsp
+const assert = require('assert');
+const cp = require('child_process');
+const server = process.argv[2];
+const uri = 'file:///hover-features.zc';
+const frame = m => {
+ const body = Buffer.from(JSON.stringify(m));
+ return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]);
+};
+function hover(text, needle, nth = 0) {
+ let offset = -1;
+ for (let i = 0; i <= nth; i++) offset = text.indexOf(needle, offset + 1);
+ assert.ok(offset >= 0, 'fixture contains ' + needle);
+ const before = text.slice(0, offset);
+ const line = before.split('\n').length - 1;
+ const character = before.length - before.lastIndexOf('\n') - 1;
+ const messages = [
+ { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} },
+ { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } },
+ { jsonrpc: '2.0', id: 2, method: 'textDocument/hover', params: { textDocument: { uri }, position: { line, character } } },
+ { jsonrpc: '2.0', method: 'exit' },
+ ];
+ const result = cp.spawnSync(server, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 10000, maxBuffer: 4 << 20 });
+ assert.strictEqual(result.status, 0, String(result.stderr));
+ let data = result.stdout;
+ while (data.length) {
+ const end = data.indexOf('\r\n\r\n');
+ const count = Number(/Content-Length:\s*(\d+)/i.exec(data.subarray(0, end).toString())[1]);
+ const msg = JSON.parse(data.subarray(end + 4, end + 4 + count).toString());
+ data = data.subarray(end + 4 + count);
+ if (msg.id === 2) return msg.result && msg.result.contents.value;
+ }
+ return null;
+}
+for (const type of ['word', 'int', 'char', 'bool', 'float', 'f64', 'f32', 'byte', 'u8', 'i8', 'u16', 'i16', 'u32', 'i32']) {
+ const value = hover(type + ' global;\nfn main() { return 0; }\n', type);
+ assert.ok(value && value.startsWith('```zc\n') && value.includes('\n\n'), type + ' has formatted built-in documentation');
+}
+assert.match(hover('void noop() {}\nfn main() { return 0; }\n', 'void'), /no meaningful result/);
+const array = 'fn main() {\n [4]u8 values;\n return values[0];\n}\n';
+assert.match(hover(array, '[4]'), /fixed length of 4/);
+assert.match(hover(array, 'values', 1), /type \[4\]u8/);
+const struct = 'struct pair { int left; float right; };\nfn main() { pair value; return value.left; }\n';
+assert.match(hover(struct, 'pair', 1), /int left;[\s\S]*float right;/);
+assert.match(hover(struct, 'value', 1), /type pair/);
+const fields = 'struct pair { int /* left field */ left, /* right field */ right; };\nfn main() { pair p; return p.left + p.right; }\n';
+assert.match(hover(fields, 'left +'), /field; type int[\s\S]*left field/);
+assert.match(hover(fields, 'right;'), /field; type int[\s\S]*right field/);
+const empty = '/**/ int helper() { return 1; }\nfn main() { return helper(); }\n';
+assert.match(hover(empty, 'helper();'), /int helper\(\)/);
+const variableDocs = 'fn main() {\n int /* first variable */ first = 1, /* second variable */ second = 2;\n return first + second;\n}\n';
+const first = hover(variableDocs, 'first +');
+const second = hover(variableDocs, 'second;');
+assert.ok(first.includes('first variable') && !first.includes('second variable'), first);
+assert.ok(second.includes('second variable') && !second.includes('first variable'), second);
+const paramDocs = 'int add(int /* left argument */ a, int /* right argument */ b) { return a + b; }\nfn main() { return add(1, 2); }\n';
+const a = hover(paramDocs, 'a +');
+const b = hover(paramDocs, 'b;');
+assert.ok(a.includes('left argument') && !a.includes('right argument'), a);
+assert.ok(b.includes('right argument') && !b.includes('left argument'), b);
+const functionDocs = '/**\n * adds **one** to its argument.\n */\nint add(int x) { return x + 1; }\nfn main() { return add(2); }\n';
+assert.match(hover(functionDocs, 'add(2)'), /adds \*\*one\*\*/);
+const inferred = 'fn twice(x) { return x * 2; }\nfn main() { let value = twice(3); return value; }\n';
+assert.match(hover(inferred, 'twice(3)'), /int twice\(int x\)/);
+assert.match(hover(inferred, 'x *'), /type int/);
+assert.match(hover(inferred, 'value;'), /type int/);
+const pointer = 'int read(int* p) { return p[0]; }\nfn main() { int value = 3; return read(&value); }\n';
+assert.match(hover(pointer, 'p[0]'), /type int\*[\s\S]*pointer to `int`/);
+const alias = 'type row = [3]int;\nfn main() { row values; return values[0]; }\n';
+assert.match(hover(alias, 'row values'), /\[3\]int[\s\S]*fixed length of 3/);
+const enumeration = 'enum color { red, green = 5, blue };\nfn main() { color shade = green; return shade; }\n';
+assert.match(hover(enumeration, 'shade;'), /type color/);
+const dynamic = 'fn main() { []int items; push(items, 3); return len(items); }\n';
+assert.match(hover(dynamic, 'items);'), /type \[\]int[\s\S]*growable array handle/);
+const callable = 'fn main() { let apply = fn(int x) { return x + 1; }; return apply(2); }\n';
+assert.match(hover(callable, 'apply(2)'), /type .*\([\s\S]*callable value/);
+assert.match(hover(callable, 'apply(2)'), /type int\(int\)/);
+const floatClosure = 'fn main() { let f = fn(float x) { return x + 1.5; }; let result = f(2.0); return result; }\n';
+assert.match(hover(floatClosure, 'result;'), /type float/);
+const inferredPointer = 'struct node { int value; };\nfn make_node() { return new(node); }\nfn main() { let p = make_node(); return p.value; }\n';
+assert.match(hover(inferredPointer, 'p.value'), /type node\*/);
+const aggregateAlias = 'fn main() { [3]int a; a[1] = 7; let alias = a; return alias[1]; }\n';
+assert.match(hover(aggregateAlias, 'alias[1]'), /type int\*/);
+console.log('ok built-in and composite types, inferred signatures, and separate block docs');