aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/lsp_test.js
diff options
context:
space:
mode:
Diffstat (limited to 'tests/drivers/lsp_test.js')
-rw-r--r--tests/drivers/lsp_test.js52
1 files changed, 29 insertions, 23 deletions
diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js
index a783e1e..ab43c41 100644
--- a/tests/drivers/lsp_test.js
+++ b/tests/drivers/lsp_test.js
@@ -51,7 +51,7 @@ check(nf && nf.error && nf.error.code === -32601, 'unknown request gets MethodNo
check(r.msgs.length === 2, 'an unknown notification is ignored');
// a clean program has no diagnostics
-const good = 'fn main() {\n\tput_n(1 + 2);\n\treturn 0;\n}\n';
+const good = 'fn main() {\n\treturn 1 + 2;\n}\n';
r = run([init(1), open(A, good)]);
let d = diags(r.msgs, A);
check(d.length === 1 && d[0].params.diagnostics.length === 0, 'a clean program publishes an empty list', d);
@@ -64,7 +64,7 @@ const e1 = d.length && d[0].params.diagnostics[0];
check(e1 && e1.severity === 1 && e1.range.start.line === 1 && /expected ';', found 'return'/.test(e1.message), 'a missing semicolon is reported on its line', e1);
// utf-16 columns: an emoji (two code units) and an accented letter before the error
-const uni = '// café 😀\nfn main() { put_s("é😀"); return undefined_name; }\n';
+const uni = '// café 😀\nfn main() { let text = "é😀"; return text[0] + undefined_name; }\n';
r = run([init(1), open(A, uni)]);
d = diags(r.msgs, A);
const e2 = d.length && d[0].params.diagnostics[0];
@@ -82,7 +82,7 @@ const da = diags(r.msgs, A), db = diags(r.msgs, B2);
check(da.length === 2 && JSON.stringify(da[0].params.diagnostics) === JSON.stringify(da[1].params.diagnostics) && db[0].params.diagnostics.length === 0, 'documents are checked in isolation');
// syntax modifications made by the program itself apply to the check, like the compiler
-const pipeDef = 'immediate fn pipe_op() { next(); add_binop("|>", 1, "c"); return 0; }\npipe_op\n';
+const pipeDef = 'include run; immediate fn pipe_op() { run.next(); run.add_binop("|>", 1, "c"); return 0; }\npipe_op\n';
const pipeUse = 'fn double(x) { return x * 2; }\nfn main() { return 4 |> double; }\n';
r = run([init(1), open(A, pipeDef + pipeUse), open(B2, pipeUse)]);
check(diags(r.msgs, A)[0].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a parse word that adds an operator is understood');
@@ -108,11 +108,11 @@ function tokens(text) {
}
return out;
}
-const meth = 'struct point {\n\tlet x, y;\n\tfn sum() { return this.x + this.y; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum();\n}\n';
+const meth = 'struct point {\n\tlet x, y;\n\tfn sum() { return this.x + this.y; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum();\n}\n';
const tk = tokens(meth);
check(tk.filter(t => t === 'sum:function').length === 2, 'a method is a function where it is declared and where it is called', tk);
check(tk.includes('this:parameter') && tk.includes('x:property'), 'this is a parameter and a field is a property', tk);
-check(tk.filter(t => t === 'point:type').length >= 3, 'a user type is a type, also before a postfix star', tk);
+check(tk.filter(t => t === 'point:type').length >= 2, 'a user type is a type, also before a postfix star', tk);
// a large document goes through the streaming reader
let big = '';
@@ -120,7 +120,8 @@ for (let i = 0; i < 2500; i++) big += 'fn f' + i + '(a, b) { return a * b + ' +
big += 'fn main() { return f0(1, 2); }\n';
r = run([init(1), open(A, big)]);
d = diags(r.msgs, A);
-check(d.length === 1 && d[0].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a document of ' + big.length + ' bytes is checked', d.length);
+// a large check streams what it has found, so there may be several lists; the last is the whole answer
+check(d.length >= 1 && d[d.length - 1].params.diagnostics.filter(x => x.severity === 1).length === 0, 'a document of ' + big.length + ' bytes is checked', d.length);
// local tables grow past their initial reservation, and the next document still checks
let many = 'fn main() {\n';
@@ -141,54 +142,58 @@ const hsrc = '// adds two numbers\nint add(int a, int b) { return a + b; }\nint
let hv = hover(hsrc, 4, at(hsrc, 4, 'add'));
check(hv && hv.includes('int add(int a, int b)') && hv.includes('adds two numbers'), 'hover on a call shows the signature and the doc', hv);
check(hv && /^```zc\n/.test(hv) && /\n```\n\nadds two numbers/.test(hv), 'hover separates the code fence from Markdown documentation', hv);
-const unicodeHover = 'fn main() { put_s("é😀"); put_c(10); return 0; }\n';
-hv = hover(unicodeHover, 0, at(unicodeHover, 0, 'put_c'));
-check(hv && hv.includes('put_c('), 'hover converts UTF-16 columns after BMP and astral characters', hv);
+const unicodeHover = 'fn main() { let text = "é😀"; int value = 1; return value; }\n';
+hv = hover(unicodeHover, 0, at(unicodeHover, 0, 'value'));
+check(hv && hv.includes('(int) let value'), 'hover converts UTF-16 columns after BMP and astral characters', hv);
hv = hover(hsrc, 0, hsrc.split('\n')[0].length + 10);
check(hv === null, 'an out-of-range hover column cannot select a name on the following line', hv);
hv = hover(hsrc, 6, at(hsrc, 6, 'twice'));
check(hv && hv.includes('int twice(int a)') && !hv.includes('adds two numbers'), 'a function with no doc still shows its signature', hv);
hv = hover(hsrc, 1, at(hsrc, 1, 'a', 2));
-check(hv && hv.includes('parameter') && hv.includes('int a'), 'hover on a parameter shows its declaration', hv);
+check(hv && hv.includes('(int) let a // parameter;'), 'hover on a parameter shows its declaration', hv);
hv = hover(hsrc, 5, at(hsrc, 5, 'i'));
check(hv && hv.includes('let i') && hv.includes('local'), 'hover on a local shows its declaration', hv);
hv = hover(hsrc, 6, at(hsrc, 6, 'i', 1));
check(hv && hv.includes('let i') && hv.includes('local'), 'hover on a use of a local shows its declaration', hv);
-const tsrc = 'fn main() {\n\tlet i = 3;\n\tlet p = new(int);\n\treturn i;\n}\n';
+const tsrc = 'fn main() {\n\tlet i = 3;\n\tint* p;\n\treturn i;\n}\n';
// an untyped parameter and an untyped function show the types annotate gives them
const isrc = 'fn twice(x) { return x * 2; }\nfn main() {\n\tlet n = twice(4);\n\treturn n;\n}\n';
let hi = hover(isrc, 0, 9);
-check(hi && hi.includes('x // parameter; type int'), 'an untyped parameter shows its inferred type', hi);
+check(hi && hi.includes('(int) let x // parameter;'), 'an untyped parameter shows its inferred type', hi);
hi = hover(isrc, 0, 3);
check(hi && hi.includes('int twice(int x)'), 'an untyped function shows its inferred parameter and return types', hi);
hv = hover(tsrc, 1, 5);
-check(hv && hv.includes('type int'), 'an untyped number local shows the type annotate gives it', hv);
-hv = hover(tsrc, 2, 5);
-check(hv && hv.includes('type int*'), 'a pointer local shows its pointer type on its declaration line', hv);
+check(hv && hv.includes('(int) let i'), 'an untyped number local shows the type annotate gives it', hv);
+hv = hover(tsrc, 2, 6);
+check(hv && hv.includes('(int*) let p; // local'), 'a pointer local shows its pointer type on its declaration line', hv);
hv = hover('fn main() {\n\tlet i = 3;\n\treturn i;\n}\n', 2, 8);
check(hv && hv.includes('let i'), 'a return of a local is a use, not a declaration', hv);
hv = hover(hsrc, 6, at(hsrc, 6, 'return'));
check(hv && hv.includes('leaves the function with a value'), 'a keyword shows what it does', hv);
-hv = hover('fn main() {\n\tput_c(32);\n\treturn 0;\n}\n', 1, 1);
+const builtinSource = 'include rt; fn main() {\n\trt.put_c(32);\n\treturn 0;\n}\n';
+hv = hover(builtinSource, 1, at(builtinSource, 1, 'put_c'));
check(hv && hv.includes('put_c('), 'a built-in function shows its signature', hv);
-const kwsrc = 'fn main() {\n\tint* p = new(int);\n\tfree(p);\n\treturn 0;\n}\n';
+const kwsrc = 'include rt; fn main() {\n\tint* p = new(int);\n\trt.free(p);\n\treturn 0;\n}\n';
hv = hover(kwsrc, 1, at(kwsrc, 1, 'new'));
-check(hv && hv.includes('new(T)') && hv.includes('fresh object'), 'hover on new shows its form and what it does', hv);
+check(hv && hv.includes('new(T)') && hv.includes('fresh heap object') && hv.includes('owns it'), 'hover on new shows its form and ownership behavior', hv);
hv = hover(kwsrc, 2, at(kwsrc, 2, 'free'));
-check(hv && hv.includes('frees a heap object'), 'hover on free shows its doc', hv);
+check(hv && hv.includes('releases a heap object') && hv.includes('consumes an owned object automatically'), 'hover on free shows inferred consumption', hv);
const kdoc = '// the number of lines\nfn main() {\n\treturn 0;\n}\n';
hv = hover(kdoc, 2, 1);
check(hv === null || !hv.includes('number of lines'), 'a doc comment is not attached to a keyword in a body', hv);
// a method of a struct is indented in the body, and its hover is still its line and doc
-const msrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tfn sum() { return this.x; }\n\tstatic fn zero() { return 0; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum() + point.zero();\n}\n';
+const msrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tfn sum() { return this.x; }\n\tstatic fn zero() { return 0; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum() + point.zero();\n}\n';
hv = hover(msrc, 8, at(msrc, 8, 'sum'));
check(hv && hv.includes('fn sum()') && hv.includes('the sum of the parts'), 'a method called through a pointer shows its signature and doc', hv);
hv = hover(msrc, 8, at(msrc, 8, 'zero'));
check(hv && hv.includes('static fn zero()'), 'a static function of a struct shows its signature', hv);
+hv = hover(msrc, 4, at(msrc, 4, 'static'));
+check(hv && hv.includes('static fn name') && hv.includes('hidden this'), 'static keyword hover explains the declaration form', hv);
+check(tokens(msrc).includes('static:keyword'), 'static is highlighted as a keyword', tokens(msrc));
// a typed method shows its return type and parameters
-const tmsrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tint sum(int k) { return this.x + k; }\n}\nfn main() {\n\tpoint* p = new(point);\n\treturn p.sum(1);\n}\n';
+const tmsrc = 'struct point {\n\tlet x;\n\t// the sum of the parts\n\tint sum(int k) { return this.x + k; }\n}\nfn main() {\n\tpoint p;\n\treturn p.sum(1);\n}\n';
hv = hover(tmsrc, 7, at(tmsrc, 7, 'sum'));
check(hv && hv.includes('int sum(int k)') && hv.includes('the sum of the parts'), 'a typed method shows its return type and parameters', hv);
@@ -219,7 +224,8 @@ check(dirty.length === 0, 'every example and test program checks clean (' + clea
let missed = [], n = 0;
for (const dir of ['errors', 'typeerrors', 'langerrors']) {
for (const f of fs.readdirSync('tests/' + dir).filter(x => x.endsWith('.zc'))) {
- const want = fs.readFileSync('tests/' + dir + '/' + f, 'utf8').split(/\r?\n/)[0].replace('// expect: ', '');
+ const wantLine = fs.readFileSync('tests/' + dir + '/' + f, 'utf8').split(/\r?\n/).find(line => line.startsWith('// expect: '));
+ const want = wantLine ? wantLine.slice('// expect: '.length) : '';
const out = cli('tests/' + dir + '/' + f);
n++;
if (!out.includes(want)) missed.push(dir + '/' + f + ' wanted [' + want + '] got [' + out.split('\n')[0] + ']');
@@ -236,7 +242,7 @@ const sct = tokens(scp);
check(sct.includes('cap:parameter') && sct.includes('cap:variable') && !sct.includes('cap:keyword') && !sct.includes('cap:function'), 'a name like a built-in is a variable or parameter where the scope says', sct);
check(sct.includes('len:keyword'), 'a built-in that is a keyword in the compiler is still a keyword', sct);
let hcv = hover(scp, 4, at(scp, 4, 'cap'));
-check(hcv && hcv.includes('int cap') && hcv.includes('local'), 'hover on a use of a local named like a built-in shows the local', hcv);
+check(hcv && hcv.includes('(int) let cap') && hcv.includes('local'), 'hover on a use of a local named like a built-in shows the local', hcv);
hcv = hover(scp, 1, at(scp, 1, 'cap', 1));
check(hcv && hcv.includes('parameter'), 'hover on a use of a parameter named like a built-in shows the parameter', hcv);
hcv = hover(scp, 4, at(scp, 4, 'len'));