From ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Mon, 12 Oct 2026 08:46:23 +0330 Subject: a --- tests/drivers/hover_features_test.js | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/drivers/hover_features_test.js (limited to 'tests/drivers/hover_features_test.js') 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'); -- cgit v1.2.3