move away from deprecated thingies/bump srf
All checks were successful
Generic zig build / build (push) Successful in 22s
All checks were successful
Generic zig build / build (push) Successful in 22s
This commit is contained in:
parent
da49d843ca
commit
eb6cc1f388
7 changed files with 128 additions and 80 deletions
|
|
@ -5,8 +5,8 @@
|
|||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{
|
||||
.srf = .{
|
||||
.url = "git+https://git.lerch.org/lobo/srf.git#7f3a43e838fc472bdb1af6c1b692f2255dd19f48",
|
||||
.hash = "srf-0.0.0-qZj57-17AgAXn2at0WH1xV75oJhr3vTFeU5FxU7kAhtf",
|
||||
.url = "git+https://git.lerch.org/lobo/srf.git#af8b998d3fd3f240277d7abf19343d3ddc25ed3a",
|
||||
.hash = "srf-0.0.0-qZj57wqMAgAUtX8nN3ZyshqlUGDNjsyZqCUcguVqI2Og",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ stdenv.mkDerivation (finalAttrs: {
|
|||
zigDeps = zig.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
fetchAll = true;
|
||||
hash = "sha256-0A2IlywPDkgX8+oiZ1Xd2zF0o07T4moBRngddwl1GBk=";
|
||||
hash = "sha256-yWUmqKxop3uCqvHBrb16pgAgA2O+0kholkY+M7Zftbk=";
|
||||
};
|
||||
|
||||
postConfigure = ''
|
||||
|
|
|
|||
|
|
@ -430,14 +430,14 @@ const Harness = struct {
|
|||
/// Asserts the transcript contains `needle`, which keeps the tests readable
|
||||
/// without reimplementing a JSON-RPC client here.
|
||||
fn expectSent(self: *Harness, needle: []const u8) !void {
|
||||
if (std.mem.indexOf(u8, self.transcript(), needle) == null) {
|
||||
if (std.mem.find(u8, self.transcript(), needle) == null) {
|
||||
std.debug.print("\nexpected to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
||||
return error.NotSent;
|
||||
}
|
||||
}
|
||||
|
||||
fn expectNotSent(self: *Harness, needle: []const u8) !void {
|
||||
if (std.mem.indexOf(u8, self.transcript(), needle) != null) {
|
||||
if (std.mem.find(u8, self.transcript(), needle) != null) {
|
||||
std.debug.print("\nexpected NOT to find: {s}\nin transcript:\n{s}\n", .{ needle, self.transcript() });
|
||||
return error.UnexpectedlySent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,14 +442,14 @@ test "missing magic header is reported on line 0" {
|
|||
try testing.expect(diags.len > 0);
|
||||
try testing.expectEqual(@as(u32, 0), diags[0].range.start.line);
|
||||
try testing.expectEqual(Severity.err, diags[0].severity);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "Magic header") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "Magic header") != null);
|
||||
}
|
||||
|
||||
test "duplicate magic header is reported" {
|
||||
const diags = try analyzeForTest("#!srfv1\n#!srfv1\nname::alice\n");
|
||||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expect(diags.len > 0);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "duplicate magic") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "duplicate magic") != null);
|
||||
}
|
||||
|
||||
test "empty document reports the missing header" {
|
||||
|
|
@ -458,8 +458,8 @@ test "empty document reports the missing header" {
|
|||
const diags = try analyzeForTest("");
|
||||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "empty") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "#!srfv1") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "empty") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "#!srfv1") != null);
|
||||
}
|
||||
|
||||
test "whitespace-only document is treated as empty" {
|
||||
|
|
@ -569,8 +569,8 @@ test "a long-format overrun is left to srf, which reports it as of 4bec474" {
|
|||
const all = try analyzeForTest("#!srfv1\n#!long\nk:3:hello\n");
|
||||
defer freeDiagnostics(testing.allocator, all);
|
||||
try testing.expectEqual(@as(usize, 1), all.len);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "additional bytes after field value") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 5") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "additional bytes after field value") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "restated as 5") != null);
|
||||
try testing.expectEqual(@as(u32, 2), all[0].range.start.line);
|
||||
}
|
||||
|
||||
|
|
@ -579,7 +579,7 @@ test "a length prefix past the end of the file is an error" {
|
|||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expectEqual(Severity.err, diags[0].severity);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||
}
|
||||
|
||||
test "a compact-format overrun is left to srf, which reports it" {
|
||||
|
|
@ -593,8 +593,8 @@ test "a compact-format overrun is left to srf, which reports it" {
|
|||
try testing.expectEqual(@as(usize, 1), all.len);
|
||||
try testing.expectEqual(Severity.err, all[0].severity);
|
||||
try testing.expectEqual(@as(u32, 1), all[0].range.start.line);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "2 additional bytes") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 5") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "2 additional bytes") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "restated as 5") != null);
|
||||
}
|
||||
|
||||
test "a compact overrun with no following delimiter still names the real problem" {
|
||||
|
|
@ -605,8 +605,8 @@ test "a compact overrun with no following delimiter still names the real problem
|
|||
const all = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
||||
defer freeDiagnostics(testing.allocator, all);
|
||||
try testing.expect(all.len >= 1);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "1 additional bytes") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "restated as 3") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "1 additional bytes") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "restated as 3") != null);
|
||||
}
|
||||
test "a correct length prefix produces no diagnostic" {
|
||||
const diags = try analyzeForTest("#!srfv1\n#!long\nk:5:hello\n");
|
||||
|
|
@ -640,8 +640,8 @@ test "length diagnostics coexist with srf's own diagnostics" {
|
|||
var from_srf = false;
|
||||
var from_us = false;
|
||||
for (diags) |d| {
|
||||
if (std.mem.indexOf(u8, d.message, "duplicate magic") != null) from_srf = true;
|
||||
if (std.mem.indexOf(u8, d.message, "length prefix") != null) from_us = true;
|
||||
if (std.mem.find(u8, d.message, "duplicate magic") != null) from_srf = true;
|
||||
if (std.mem.find(u8, d.message, "length prefix") != null) from_us = true;
|
||||
}
|
||||
try testing.expect(from_srf);
|
||||
try testing.expect(from_us);
|
||||
|
|
@ -653,7 +653,7 @@ test "a compact overrun does not produce the unlocated fallback" {
|
|||
const diags = try analyzeForTest("#!srfv1\nk:2:abc\n");
|
||||
defer freeDiagnostics(testing.allocator, diags);
|
||||
for (diags) |d| {
|
||||
try testing.expect(std.mem.indexOf(u8, d.message, "reported no location") == null);
|
||||
try testing.expect(std.mem.find(u8, d.message, "reported no location") == null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -669,20 +669,20 @@ test "a located length problem replaces the unlocated fallback" {
|
|||
const diags = try analyzeForTest("#!srfv1\n#!long\nk:99:hello\n");
|
||||
defer freeDiagnostics(testing.allocator, diags);
|
||||
for (diags) |d| {
|
||||
if (std.mem.indexOf(u8, d.message, "reported no location") != null) {
|
||||
if (std.mem.find(u8, d.message, "reported no location") != null) {
|
||||
std.debug.print("\nunlocated fallback should have been suppressed\n", .{});
|
||||
return error.UnexpectedFallback;
|
||||
}
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "declares 99 bytes but only 6 remain") != null);
|
||||
}
|
||||
|
||||
test "the unlocated fallback still fires when nothing else explains the failure" {
|
||||
const diags = try analyzeForTest("");
|
||||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "empty") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "empty") != null);
|
||||
}
|
||||
|
||||
/// Only the type-consistency warnings, without srf's own diagnostics.
|
||||
|
|
@ -804,7 +804,7 @@ test "a one against one tie flags the later occurrence" {
|
|||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expectEqual(@as(u32, 4), diags[0].range.start.line);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "is `num` in 1 other") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "is `num` in 1 other") != null);
|
||||
}
|
||||
|
||||
test "the three string spellings are one kind, not three" {
|
||||
|
|
@ -825,12 +825,12 @@ test "bool and binary inconsistencies are reported too" {
|
|||
const bools = try inconsistenciesForTest("#!srfv1\n#!long\nk:bool:true\n\nk:bool:false\n\nk::true\n");
|
||||
defer freeDiagnostics(testing.allocator, bools);
|
||||
try testing.expectEqual(@as(usize, 1), bools.len);
|
||||
try testing.expect(std.mem.indexOf(u8, bools[0].message, "`bool`") != null);
|
||||
try testing.expect(std.mem.find(u8, bools[0].message, "`bool`") != null);
|
||||
|
||||
const bins = try inconsistenciesForTest("#!srfv1\n#!long\nk:binary:aGk=\n\nk:binary:aGk=\n\nk::aGk=\n");
|
||||
defer freeDiagnostics(testing.allocator, bins);
|
||||
try testing.expectEqual(@as(usize, 1), bins.len);
|
||||
try testing.expect(std.mem.indexOf(u8, bins[0].message, "`binary`") != null);
|
||||
try testing.expect(std.mem.find(u8, bins[0].message, "`binary`") != null);
|
||||
}
|
||||
|
||||
test "keys are compared byte exactly, matching srf" {
|
||||
|
|
@ -853,8 +853,8 @@ test "type warnings coexist with length warnings without duplication" {
|
|||
var type_warnings: usize = 0;
|
||||
var length_warnings: usize = 0;
|
||||
for (diags) |d| {
|
||||
if (std.mem.indexOf(u8, d.message, "other field(s) in this file") != null) type_warnings += 1;
|
||||
if (std.mem.indexOf(u8, d.message, "length prefix") != null) length_warnings += 1;
|
||||
if (std.mem.find(u8, d.message, "other field(s) in this file") != null) type_warnings += 1;
|
||||
if (std.mem.find(u8, d.message, "length prefix") != null) length_warnings += 1;
|
||||
}
|
||||
try testing.expectEqual(@as(usize, 1), type_warnings);
|
||||
try testing.expectEqual(@as(usize, 1), length_warnings);
|
||||
|
|
@ -910,7 +910,7 @@ test "compact format does not allow trailing whitespace after a length prefix" {
|
|||
const all = try analyzeForTest("#!srfv1\nk:5:hello \n");
|
||||
defer freeDiagnostics(testing.allocator, all);
|
||||
try testing.expect(all.len > 0);
|
||||
try testing.expect(std.mem.indexOf(u8, all[0].message, "additional bytes") != null);
|
||||
try testing.expect(std.mem.find(u8, all[0].message, "additional bytes") != null);
|
||||
}
|
||||
|
||||
test "a bad length prefix followed by more lines does not crash the parser" {
|
||||
|
|
@ -947,7 +947,7 @@ test "a recovered short prefix reports once when the rest of the line is valid"
|
|||
defer freeDiagnostics(testing.allocator, diags);
|
||||
try testing.expectEqual(@as(usize, 1), diags.len);
|
||||
try testing.expectEqual(@as(u32, 1), diags[0].range.start.line);
|
||||
try testing.expect(std.mem.indexOf(u8, diags[0].message, "restated as 5") != null);
|
||||
try testing.expect(std.mem.find(u8, diags[0].message, "restated as 5") != null);
|
||||
}
|
||||
|
||||
test "each bad length prefix in a document is reported once, on its own line" {
|
||||
|
|
@ -956,7 +956,7 @@ test "each bad length prefix in a document is reported once, on its own line" {
|
|||
try testing.expectEqual(@as(usize, 3), diags.len);
|
||||
for (diags, 1..) |d, line| {
|
||||
try testing.expectEqual(@as(u32, @intCast(line)), d.range.start.line);
|
||||
try testing.expect(std.mem.indexOf(u8, d.message, "additional bytes") != null);
|
||||
try testing.expect(std.mem.find(u8, d.message, "additional bytes") != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ const Scanner = struct {
|
|||
while (end < line_end and !isSpace(self.text[end])) end += 1;
|
||||
|
||||
const decl = self.text[body + 2 .. end];
|
||||
const eq = std.mem.indexOfScalar(u8, decl, '=');
|
||||
const eq = std.mem.findScalar(u8, decl, '=');
|
||||
const name = if (eq) |i| decl[0..i] else decl;
|
||||
|
||||
// Mode directives take effect for everything below them.
|
||||
|
|
@ -283,7 +283,7 @@ const Scanner = struct {
|
|||
/// another field follows it on the same line.
|
||||
fn scanField(self: *Scanner, field_start: usize) ?Item {
|
||||
// Key runs to the first ':'.
|
||||
const colon = std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], field_start, ':') orelse
|
||||
const colon = std.mem.findScalarPos(u8, self.text[0..self.line_end], field_start, ':') orelse
|
||||
return self.abandonLine();
|
||||
const head = self.splitHead(colon) orelse return self.abandonLine();
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ const Scanner = struct {
|
|||
}
|
||||
|
||||
// `key:hint:value`, where the hint may be padded with whitespace.
|
||||
const second = std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], colon + 1, ':') orelse
|
||||
const second = std.mem.findScalarPos(u8, self.text[0..self.line_end], colon + 1, ':') orelse
|
||||
return null;
|
||||
var start = colon + 1;
|
||||
var end = second;
|
||||
|
|
@ -380,7 +380,7 @@ const Scanner = struct {
|
|||
else if (end < new_line_end and self.text[end] == ',')
|
||||
end + 1
|
||||
else if (problem != null)
|
||||
if (std.mem.indexOfScalarPos(u8, self.text[0..new_line_end], end, ',')) |comma|
|
||||
if (std.mem.findScalarPos(u8, self.text[0..new_line_end], end, ',')) |comma|
|
||||
comma + 1
|
||||
else
|
||||
null
|
||||
|
|
@ -406,7 +406,7 @@ const Scanner = struct {
|
|||
}
|
||||
|
||||
// Compact format: the next comma ends the value and starts a field.
|
||||
if (std.mem.indexOfScalarPos(u8, self.text[0..self.line_end], value_start, ',')) |comma| {
|
||||
if (std.mem.findScalarPos(u8, self.text[0..self.line_end], value_start, ',')) |comma| {
|
||||
return .{
|
||||
.end = comma,
|
||||
.next_field = comma + 1,
|
||||
|
|
@ -455,17 +455,9 @@ const Scanner = struct {
|
|||
// and a range built from the first line's end would come out inverted.
|
||||
//
|
||||
// How far the surplus runs has to match how srf counts it, or hover ends
|
||||
// up suggesting a different corrected length than the diagnostic does. srf
|
||||
// stops at the next delimiter in compact format (`extra_bytes` in
|
||||
// `checkShortPrefix`) and runs to end of line in long format, where a
|
||||
// comma is ordinary text.
|
||||
// up suggesting a different corrected length than the diagnostic does.
|
||||
const line_end = self.lineEnd(after);
|
||||
const surplus_end = if (self.long_mode)
|
||||
line_end
|
||||
else if (std.mem.indexOfScalarPos(u8, self.text[0..line_end], after, ',')) |comma|
|
||||
comma
|
||||
else
|
||||
line_end;
|
||||
const surplus_end = self.likelySurplusEnd(after, line_end);
|
||||
|
||||
return .{ .overrun = .{
|
||||
.declared = declared,
|
||||
|
|
@ -474,8 +466,30 @@ const Scanner = struct {
|
|||
} };
|
||||
}
|
||||
|
||||
/// Where the value most likely actually ends, mirroring srf's
|
||||
/// `likelyExtraBytes`.
|
||||
///
|
||||
/// A comma is ambiguous in compact format: field separator, or content inside
|
||||
/// a length-prefixed value, or even part of a key. The byte count normally
|
||||
/// tells them apart, so with the count wrong the next ':' is the best anchor
|
||||
/// left, being where the following field's key ends; the comma just before it
|
||||
/// is therefore the separator. Falling back to the first comma covers a value
|
||||
/// that contains a colon itself, such as a URL.
|
||||
///
|
||||
/// Long format has no comma delimiter, so the whole remainder of the line is
|
||||
/// surplus there.
|
||||
fn likelySurplusEnd(self: *const Scanner, after: usize, line_end: usize) usize {
|
||||
if (self.long_mode) return line_end;
|
||||
|
||||
const remainder = self.text[after..line_end];
|
||||
if (std.mem.findScalar(u8, remainder, ':')) |colon|
|
||||
if (std.mem.findScalarLast(u8, remainder[0..colon], ',')) |separator|
|
||||
return after + separator;
|
||||
return after + (std.mem.findScalar(u8, remainder, ',') orelse remainder.len);
|
||||
}
|
||||
|
||||
fn lineEnd(self: *const Scanner, from: usize) usize {
|
||||
return std.mem.indexOfScalarPos(u8, self.text, from, '\n') orelse self.text.len;
|
||||
return std.mem.findScalarPos(u8, self.text, from, '\n') orelse self.text.len;
|
||||
}
|
||||
|
||||
fn nextLine(self: *const Scanner, line_end: usize) usize {
|
||||
|
|
@ -502,7 +516,7 @@ const testing = std.testing;
|
|||
/// Finds `needle` in `text` and returns the offset of its midpoint, which is a
|
||||
/// stable way to say "hover here" without hand-counting byte offsets.
|
||||
fn midpointOf(text: []const u8, needle: []const u8) usize {
|
||||
const at = std.mem.indexOf(u8, text, needle).?;
|
||||
const at = std.mem.find(u8, text, needle).?;
|
||||
return at + needle.len / 2;
|
||||
}
|
||||
|
||||
|
|
@ -706,7 +720,7 @@ test "a length prefix longer than the document swallows the rest of it" {
|
|||
|
||||
test "a blank line outside any value has nothing to hover" {
|
||||
const text = "#!srfv1\n#!long\nname::alice\n\nname::bob\n";
|
||||
const blank_at = std.mem.indexOf(u8, text, "alice\n\n").? + 6;
|
||||
const blank_at = std.mem.find(u8, text, "alice\n\n").? + 6;
|
||||
try testing.expectEqual(@as(?Token, null), tokenAt(text, blank_at));
|
||||
}
|
||||
|
||||
|
|
@ -938,14 +952,27 @@ test "value kinds describe themselves for a message" {
|
|||
try testing.expectEqualStrings("`binary`", ValueKind.bytes.describe());
|
||||
}
|
||||
|
||||
test "in compact format the surplus stops at the next delimiter, as srf counts it" {
|
||||
// srf's `checkShortPrefix` reports `indexOfScalar(past_val, ',')` bytes, so
|
||||
// measuring to end of line here would make hover suggest a different
|
||||
// corrected length than the diagnostic does.
|
||||
test "in compact format the surplus runs to the delimiter before the next colon" {
|
||||
// Mirrors srf's `likelyExtraBytes`. The nearest comma would give 2, making the
|
||||
// value "23,000" and the next key "000,000,really" -- a comma in a key is the
|
||||
// signature of a wrong guess. The colon after "really" is where the next
|
||||
// field's key ends, so the comma just before it is the separator: 4 + 10 = 14.
|
||||
const problem = problemAt("#!srfv1\nim_worth:4:23,000,000,000,really:bool:false\n", "im_worth").?;
|
||||
try testing.expectEqual(@as(usize, 4), problem.overrun.declared);
|
||||
// "23,0" then "00" then a comma: two surplus bytes, so 4 + 2 = 6.
|
||||
try testing.expectEqual(@as(usize, 2), problem.overrun.surplus);
|
||||
try testing.expectEqual(@as(usize, 10), problem.overrun.surplus);
|
||||
}
|
||||
|
||||
test "in compact format a value holding both a delimiter and a colon" {
|
||||
// Value "a:" declared, "a:b,c" most likely meant: 2 + 3 = 5.
|
||||
const problem = problemAt("#!srfv1\nk:2:a:b,c,next::v\n", "k:2:").?;
|
||||
try testing.expectEqual(@as(usize, 3), problem.overrun.surplus);
|
||||
}
|
||||
|
||||
test "in compact format a colon with no delimiter before it falls back" {
|
||||
// The first colon sits inside the value's URL, so there is no comma before it
|
||||
// and the fallback to the first comma is what gets this right: 3 + 5 = 8.
|
||||
const problem = problemAt("#!srfv1\nk:3:http://x,next::v\n", "k:3:").?;
|
||||
try testing.expectEqual(@as(usize, 5), problem.overrun.surplus);
|
||||
}
|
||||
|
||||
test "in compact format a surplus with no delimiter runs to end of line" {
|
||||
|
|
|
|||
|
|
@ -312,11 +312,13 @@ fn writeLengthProblem(
|
|||
// Hover is still where the detail belongs, so state the byte arithmetic
|
||||
// and the rule rather than just repeating that something is wrong.
|
||||
.overrun => |o| {
|
||||
// Worded so that one verb form reads correctly for both a single
|
||||
// surplus byte and many.
|
||||
try w.print(
|
||||
"**Length mismatch:** declares {d} byte{s}, but {d} more byte{s} follow before the end of the field.\n\n",
|
||||
"**Length mismatch:** declares {d} byte{s}, but the field runs {d} byte{s} longer.\n\n",
|
||||
.{ o.declared, plural(o.declared), o.surplus, plural(o.surplus) },
|
||||
);
|
||||
try w.print("Restating the length as {d} would cover them. ", .{o.declared + o.surplus});
|
||||
try w.print("Restating the length as {d} would cover it. ", .{o.declared + o.surplus});
|
||||
if (f.long_mode) {
|
||||
try w.writeAll(
|
||||
"A length-prefixed value must be followed by end of line, optionally after whitespace or a comment.\n\n",
|
||||
|
|
@ -442,7 +444,7 @@ const testing = std.testing;
|
|||
|
||||
/// Hovers at the midpoint of `needle` and returns the markdown.
|
||||
fn hoverOn(text: []const u8, needle: []const u8) ![]const u8 {
|
||||
const at = std.mem.indexOf(u8, text, needle) orelse return error.NeedleNotInText;
|
||||
const at = std.mem.find(u8, text, needle) orelse return error.NeedleNotInText;
|
||||
const position = positions.positionFromByte(text, at + needle.len / 2, .@"utf-8");
|
||||
// An error beats a panic here: hovering punctuation legitimately returns
|
||||
// null, and a test that lands there should say so rather than abort.
|
||||
|
|
@ -454,7 +456,7 @@ fn hoverOn(text: []const u8, needle: []const u8) ![]const u8 {
|
|||
fn expectHoverContains(text: []const u8, needle: []const u8, expected: []const u8) !void {
|
||||
const markdown = try hoverOn(text, needle);
|
||||
defer testing.allocator.free(markdown);
|
||||
if (std.mem.indexOf(u8, markdown, expected) == null) {
|
||||
if (std.mem.find(u8, markdown, expected) == null) {
|
||||
std.debug.print("\nhovering \"{s}\"\nexpected to contain: {s}\ngot:\n{s}\n", .{ needle, expected, markdown });
|
||||
return error.MissingFromHover;
|
||||
}
|
||||
|
|
@ -566,8 +568,8 @@ test "currency in a num is judged by srf, not by us" {
|
|||
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
||||
const markdown = try hoverOn(text, "15,000");
|
||||
defer testing.allocator.free(markdown);
|
||||
const parsed = std.mem.indexOf(u8, markdown, "Parsed as a number") != null;
|
||||
const rejected = std.mem.indexOf(u8, markdown, "Does not parse") != null;
|
||||
const parsed = std.mem.find(u8, markdown, "Parsed as a number") != null;
|
||||
const rejected = std.mem.find(u8, markdown, "Does not parse") != null;
|
||||
try testing.expect(parsed or rejected);
|
||||
}
|
||||
|
||||
|
|
@ -578,7 +580,7 @@ test "long mode is preserved when re-parsing, so commas stay in the value" {
|
|||
|
||||
test "hovering a comment gives nothing" {
|
||||
const text = "#!srfv1\n# a comment\nk::v\n";
|
||||
const at = std.mem.indexOf(u8, text, "comment").?;
|
||||
const at = std.mem.find(u8, text, "comment").?;
|
||||
const position = positions.positionFromByte(text, at, .@"utf-8");
|
||||
try testing.expectEqual(
|
||||
@as(?Hover, null),
|
||||
|
|
@ -605,7 +607,7 @@ test "hover reports the range of the thing it described" {
|
|||
test "hover ranges use the negotiated encoding" {
|
||||
const text = "#!srfv1\n#!long\ncost:num:¥15,000\n";
|
||||
// Hover the value, whose line contains a two-byte character before it.
|
||||
const at = std.mem.indexOf(u8, text, "15,000").?;
|
||||
const at = std.mem.find(u8, text, "15,000").?;
|
||||
|
||||
const utf8 = (try hoverAt(
|
||||
testing.allocator,
|
||||
|
|
@ -684,14 +686,14 @@ test "a long base64 text preview is truncated" {
|
|||
|
||||
const markdown = try hoverOn(text, b64);
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "decoding to 60 bytes") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "...") != null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "decoding to 60 bytes") != null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "...") != null);
|
||||
}
|
||||
|
||||
test "empty base64 decodes to nothing without a preview" {
|
||||
const markdown = try hoverOn("#!srfv1\nblob:binary:\n", "binary");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "No value") != null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "No value") != null);
|
||||
}
|
||||
|
||||
test "looksLikeText accepts text and rejects control bytes" {
|
||||
|
|
@ -705,7 +707,7 @@ test "looksLikeText accepts text and rejects control bytes" {
|
|||
|
||||
test "an overrun length in long format suggests the corrected count" {
|
||||
const text = "#!srfv1\n#!long\nk:3:hello\n";
|
||||
try expectHoverContains(text, "k:3:", "declares 3 bytes, but 2 more bytes follow");
|
||||
try expectHoverContains(text, "k:3:", "declares 3 bytes, but the field runs 2 bytes longer");
|
||||
try expectHoverContains(text, "k:3:", "Restating the length as 5");
|
||||
try expectHoverContains(text, "k:3:", "optionally after whitespace or a comment");
|
||||
}
|
||||
|
|
@ -724,20 +726,20 @@ test "an overrun length in compact format explains the terminator rule" {
|
|||
test "a trailing comment in long format is not reported as a mismatch" {
|
||||
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello # fine\n", "hello");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||
}
|
||||
|
||||
test "a correct length prefix reports no mismatch" {
|
||||
const markdown = try hoverOn("#!srfv1\n#!long\nk:5:hello\n", "hello");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||
}
|
||||
|
||||
test "a correct length prefix with commas in compact format reports no mismatch" {
|
||||
const markdown = try hoverOn("#!srfv1\nk:5:a,b,c,next::x\n", "a,b,c");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "Length mismatch") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "a,b,c") != null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "Length mismatch") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "a,b,c") != null);
|
||||
}
|
||||
|
||||
test "an untyped numeric-looking value carries the coercion caveat" {
|
||||
|
|
@ -752,13 +754,13 @@ test "the caveat fires on the shape that broke a real consumer" {
|
|||
test "a non-numeric string carries no caveat" {
|
||||
const markdown = try hoverOn("#!srfv1\nname::alice\n", "alice");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||
}
|
||||
|
||||
test "an explicitly typed number carries no caveat" {
|
||||
const markdown = try hoverOn("#!srfv1\nage:num:30\n", "30");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||
}
|
||||
|
||||
test "an explicit string hint gets the declared wording" {
|
||||
|
|
@ -773,7 +775,7 @@ test "a length-prefixed numeric value gets the caveat too" {
|
|||
test "a bool value carries no caveat" {
|
||||
const markdown = try hoverOn("#!srfv1\nk:bool:true\n", "true");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||
}
|
||||
|
||||
test "the caveat covers negative and floating point values" {
|
||||
|
|
@ -784,7 +786,7 @@ test "the caveat covers negative and floating point values" {
|
|||
test "a multi-line length-prefixed value carries no caveat" {
|
||||
const markdown = try hoverOn("#!srfv1\n#!long\nbio:7:foo\nbar\n", "foo");
|
||||
defer testing.allocator.free(markdown);
|
||||
try testing.expect(std.mem.indexOf(u8, markdown, "strings_to_numbers") == null);
|
||||
try testing.expect(std.mem.find(u8, markdown, "strings_to_numbers") == null);
|
||||
}
|
||||
|
||||
test "the caveat appears on the key as well as the value" {
|
||||
|
|
@ -803,9 +805,28 @@ test "hovering the punctuation between key and value gives nothing" {
|
|||
test "the caveat is one line, not a paragraph" {
|
||||
const markdown = try hoverOn("#!srfv1\nk::32\n", "k::");
|
||||
defer testing.allocator.free(markdown);
|
||||
const at = std.mem.indexOf(u8, markdown, "Untyped, so this").?;
|
||||
const at = std.mem.find(u8, markdown, "Untyped, so this").?;
|
||||
const rest = markdown[at..];
|
||||
const line_end = std.mem.indexOfScalar(u8, rest, '\n').?;
|
||||
const line_end = std.mem.findScalar(u8, rest, '\n').?;
|
||||
// A single sentence on a single line.
|
||||
try testing.expect(line_end < 120);
|
||||
}
|
||||
|
||||
test "hover's suggested length matches srf's diagnostic exactly" {
|
||||
// The two are computed independently -- srf's `likelyExtraBytes` and our
|
||||
// `likelySurplusEnd` -- so they can drift. These are the numbers srf emits for
|
||||
// the same documents; if either side changes its rule, this fails.
|
||||
try expectHoverContains(
|
||||
"#!srfv1\nim_worth:4:23,000,000,000,really:bool:false\n",
|
||||
"im_worth",
|
||||
"Restating the length as 14",
|
||||
);
|
||||
try expectHoverContains("#!srfv1\nk:2:a:b,c,next::v\n", "k:2:", "Restating the length as 5");
|
||||
try expectHoverContains("#!srfv1\nk:3:http://x,next::v\n", "k:3:", "Restating the length as 8");
|
||||
try expectHoverContains("#!srfv1\nk:2:abc\n", "k:2:", "Restating the length as 3");
|
||||
try expectHoverContains("#!srfv1\n#!long\nim_worth:4:23,000,000\n", "im_worth", "Restating the length as 10");
|
||||
}
|
||||
|
||||
test "a single surplus byte reads grammatically" {
|
||||
try expectHoverContains("#!srfv1\nk:2:abc\n", "k:2:", "the field runs 1 byte longer");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ pub fn lineAt(text: []const u8, index: u32) []const u8 {
|
|||
var remaining = text;
|
||||
var i: u32 = 0;
|
||||
while (true) {
|
||||
const nl = std.mem.indexOfScalar(u8, remaining, '\n');
|
||||
const nl = std.mem.findScalar(u8, remaining, '\n');
|
||||
if (i == index) {
|
||||
const line = if (nl) |n| remaining[0..n] else remaining;
|
||||
return std.mem.trimEnd(u8, line, "\r");
|
||||
|
|
@ -61,7 +61,7 @@ pub fn lineStart(text: []const u8, index: u32) usize {
|
|||
var offset: usize = 0;
|
||||
var i: u32 = 0;
|
||||
while (i < index) : (i += 1) {
|
||||
const nl = std.mem.indexOfScalarPos(u8, text, offset, '\n') orelse return text.len;
|
||||
const nl = std.mem.findScalarPos(u8, text, offset, '\n') orelse return text.len;
|
||||
offset = nl + 1;
|
||||
}
|
||||
return @min(offset, text.len);
|
||||
|
|
@ -131,7 +131,7 @@ pub fn positionFromByte(text: []const u8, offset: usize, encoding: Encoding) Pos
|
|||
const limit = @min(offset, text.len);
|
||||
var line: u32 = 0;
|
||||
var i: usize = 0;
|
||||
while (std.mem.indexOfScalarPos(u8, text, i, '\n')) |nl| {
|
||||
while (std.mem.findScalarPos(u8, text, i, '\n')) |nl| {
|
||||
if (nl >= limit) break;
|
||||
line += 1;
|
||||
i = nl + 1;
|
||||
|
|
@ -269,7 +269,7 @@ test "positionFromByte reports the right line" {
|
|||
|
||||
test "rangeFromSpan converts both ends" {
|
||||
const text = "#!srfv1\nname::alice\n";
|
||||
const at = std.mem.indexOf(u8, text, "alice").?;
|
||||
const at = std.mem.find(u8, text, "alice").?;
|
||||
const range = rangeFromSpan(text, at, at + 5, .@"utf-8");
|
||||
try testing.expectEqual(@as(u32, 1), range.start.line);
|
||||
try testing.expectEqual(@as(u32, 6), range.start.character);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue