From b5a04827e147e8a2da0e66fdb15e417edffda19c Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Mon, 6 Nov 2023 21:17:27 -0800 Subject: [PATCH] `split('||')`, fix up tests --- src/install/dependency.zig | 5 +- src/install/semver.zig | 170 ++++++++++-------- .../registry/bun-install-registry.test.ts | 115 ++++++++---- 3 files changed, 176 insertions(+), 114 deletions(-) diff --git a/src/install/dependency.zig b/src/install/dependency.zig index 2aa04dea79..aa920b75a0 100644 --- a/src/install/dependency.zig +++ b/src/install/dependency.zig @@ -757,7 +757,7 @@ pub fn parseWithTag( input = input[1..]; } - const version = Semver.Query.parse( + const version = if (input.len > 0) Semver.Query.parse( allocator, input, sliced.sub(input), @@ -773,6 +773,9 @@ pub fn parseWithTag( }, ) catch unreachable; return null; + } else Semver.Query.Group{ + .allocator = allocator, + .input = input, }; const result = Version{ diff --git a/src/install/semver.zig b/src/install/semver.zig index 388e6dae67..1a5f0d40b1 100644 --- a/src/install/semver.zig +++ b/src/install/semver.zig @@ -1858,7 +1858,6 @@ pub const Query = struct { input: string, sliced: SlicedString, ) !Group { - var i: usize = 0; var list = Group{ .allocator = allocator, .input = input, @@ -1870,86 +1869,96 @@ pub const Query = struct { var count: u8 = 0; var skip_round = false; var is_or = false; + var only_tagged_versions: ?bool = null; - while (i < input.len) { - skip_round = false; + var itr = strings.split(input, "||"); + + while (itr.next()) |part| { + var i: usize = 0; + while (i < part.len and strings.containsChar(&std.ascii.whitespace, part[i])) : (i += 1) {} + if (i >= part.len) return Group{ + .allocator = allocator, + .input = input, + }; + if (i < part.len) { + skip_round = false; + + switch (part[i]) { + '>' => { + if (part.len > i + 1 and part[i + 1] == '=') { + token.tag = .gte; + i += 1; + } else { + token.tag = .gt; + } - switch (input[i]) { - '>' => { - if (input.len > i + 1 and input[i + 1] == '=') { - token.tag = .gte; i += 1; - } else { - token.tag = .gt; - } + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + '<' => { + if (part.len > i + 1 and part[i + 1] == '=') { + token.tag = .lte; + i += 1; + } else { + token.tag = .lt; + } - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - '<' => { - if (input.len > i + 1 and input[i + 1] == '=') { - token.tag = .lte; i += 1; - } else { - token.tag = .lt; - } + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + '=', 'v' => { + token.tag = .version; + is_or = true; + i += 1; + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + '~' => { + token.tag = .tilda; + i += 1; - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - '=', 'v' => { - token.tag = .version; - is_or = true; - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - '~' => { - token.tag = .tilda; - i += 1; + if (i < part.len and part[i] == '>') i += 1; - if (i < input.len and input[i] == '>') i += 1; + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + '^' => { + token.tag = .caret; + i += 1; + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + '0'...'9', 'X', 'x', '*' => { + token.tag = .version; + is_or = true; + }, + '|' => { + i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - '^' => { - token.tag = .caret; - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - '0'...'9', 'X', 'x', '*' => { - token.tag = .version; - is_or = true; - }, - '|' => { - i += 1; + while (i < part.len and part[i] == '|') : (i += 1) {} + while (i < part.len and part[i] == ' ') : (i += 1) {} + is_or = true; + token.tag = Token.Tag.none; + continue; + }, + '-' => { + i += 1; + while (i < part.len and part[i] == ' ') : (i += 1) {} + }, + ' ' => { + i += 1; + while (i < part.len and part[i] == ' ') : (i += 1) {} + continue; + }, + else => { + i += 1; + token.tag = Token.Tag.none; - while (i < input.len and input[i] == '|') : (i += 1) {} - while (i < input.len and input[i] == ' ') : (i += 1) {} - is_or = true; - token.tag = Token.Tag.none; - skip_round = true; - }, - '-' => { - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - }, - ' ' => { - i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - skip_round = true; - }, - else => { - i += 1; - token.tag = Token.Tag.none; + // skip tagged versions + while (i < part.len and part[i] != ' ' and part[i] != '|') : (i += 1) {} + if (only_tagged_versions == null) only_tagged_versions = true; + continue; + }, + } - // skip tagged versions - while (i < input.len and input[i] != ' ' and input[i] != '|') : (i += 1) {} - skip_round = true; - }, - } - - if (!skip_round) { - const parse_result = Version.parse(sliced.sub(input[i..])); + const parse_result = Version.parse(sliced.sub(part[i..])); const version = parse_result.version.fill(); if (version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true); if (version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true); @@ -1959,21 +1968,21 @@ pub const Query = struct { i += parse_result.stopped_at; const rollback = i; - const had_space = i < input.len and input[i] == ' '; + const had_space = i < part.len and part[i] == ' '; // TODO: can we do this without rolling back? const hyphenate: bool = had_space and possibly_hyphenate: { i += 1; - while (i < input.len and input[i] == ' ') : (i += 1) {} - if (!(i < input.len and input[i] == '-')) break :possibly_hyphenate false; + while (i < part.len and part[i] == ' ') : (i += 1) {} + if (!(i < part.len and part[i] == '-')) break :possibly_hyphenate false; i += 1; - if (!(i < input.len and input[i] == ' ')) break :possibly_hyphenate false; + if (!(i < part.len and part[i] == ' ')) break :possibly_hyphenate false; i += 1; - while (i < input.len and switch (input[i]) { + while (i < part.len and switch (part[i]) { ' ', 'v', '=' => true, else => false, }) : (i += 1) {} - if (!(i < input.len and switch (input[i]) { + if (!(i < part.len and switch (part[i]) { '0'...'9', 'X', 'x', '*' => true, else => false, })) break :possibly_hyphenate false; @@ -1985,7 +1994,7 @@ pub const Query = struct { i += @as(usize, @intFromBool(!hyphenate)); if (hyphenate) { - const second_parsed = Version.parse(sliced.sub(input[i..])); + const second_parsed = Version.parse(sliced.sub(part[i..])); var second_version = second_parsed.version.fill(); if (second_version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true); if (second_version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true); @@ -2060,12 +2069,17 @@ pub const Query = struct { } is_or = false; + only_tagged_versions = false; count += 1; token.wildcard = .none; prev_token.tag = token.tag; } } + if (count == 0 and only_tagged_versions != null and only_tagged_versions.?) { + return error.InvalidDependencyVersion; + } + return list; } }; diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index 790f45bb98..6a161fae58 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -70,7 +70,6 @@ test("basic 1", async () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -83,6 +82,7 @@ test("basic 1", async () => { name: "basic-1", version: "1.0.0", } as any); + expect(await exited).toBe(0); }); test("dependency from root satisfies range from dependency", async () => { @@ -111,7 +111,6 @@ test("dependency from root satisfies range from dependency", async () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -125,6 +124,7 @@ test("dependency from root satisfies range from dependency", async () => { name: "no-deps", version: "1.0.0", } as any); + expect(await exited).toBe(0); }); test("package added after install", async () => { @@ -152,7 +152,6 @@ test("package added after install", async () => { var err = await new Response(stderr).text(); expect(stdout).toBeDefined(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -165,6 +164,7 @@ test("package added after install", async () => { name: "no-deps", version: "1.1.0", } as any); + expect(await exited).toBe(0); // add `no-deps` to root package.json with a smaller but still compatible // version for `one-range-dep`. @@ -193,7 +193,6 @@ test("package added after install", async () => { err = await new Response(stderr).text(); expect(stdout).toBeDefined(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -212,6 +211,7 @@ test("package added after install", async () => { name: "no-deps", version: "1.1.0", } as any); + expect(await exited).toBe(0); }); describe("semver", () => { @@ -239,12 +239,12 @@ describe("semver", () => { { title: "start with ||", depVersion: "|| 1", - expected: "1.0.1", + expected: "3.0.0", }, { title: "start with || no space", depVersion: "||2", - expected: "2.0.1", + expected: "3.0.0", }, { title: "|| with no space on both sides", @@ -266,6 +266,51 @@ describe("semver", () => { depVersion: "pre-3", expected: "3.0.1", }, + { + title: "'||'", + depVersion: "||", + expected: "3.0.0", + }, + { + title: "'|'", + depVersion: "|", + expected: "3.0.0", + }, + { + title: "'|||'", + depVersion: "|||", + expected: "3.0.0", + }, + { + title: "'|| ||'", + depVersion: "|| ||", + expected: "3.0.0", + }, + { + title: "'|| 1 ||'", + depVersion: "|| 1 ||", + expected: "3.0.0", + }, + { + title: "'| | |'", + depVersion: "| | |", + expected: "3.0.0", + }, + { + title: "'|||||||||||||||||||||||||'", + depVersion: "|||||||||||||||||||||||||", + expected: "3.0.0", + }, + { + title: "'2 ||| 1'", + depVersion: "2 ||| 1", + expected: "2.0.1", + }, + { + title: "'2 |||| 1'", + depVersion: "2 |||| 1", + expected: "3.0.0", + }, ]; for (const { title, depVersion, expected } of taggedVersionTests) { @@ -294,7 +339,6 @@ describe("semver", () => { var err = await new Response(stderr).text(); expect(stdout).toBeDefined(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -303,6 +347,7 @@ describe("semver", () => { "", " 1 package installed", ]); + expect(await exited).toBe(0); }); } @@ -331,8 +376,8 @@ describe("semver", () => { var err = await new Response(stderr).text(); expect(stdout).toBeDefined(); var out = await new Response(stdout).text(); + expect(err).toContain('InvalidDependencyVersion parsing version "pre-1 || pre-2"'); expect(await exited).toBe(1); - expect(err).toContain('InvalidDependencyVersion parsing dependency "dep-with-tags" with version "pre-1 || pre-2"'); expect(out).toBeEmpty(); }); }); @@ -386,7 +431,6 @@ describe("prereleases", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -399,6 +443,7 @@ describe("prereleases", () => { name: depName, version: expected, } as any); + expect(await exited).toBe(0); }); } }); @@ -432,7 +477,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -468,6 +512,7 @@ describe("yarn tests", () => { "dragon-test-1-a": "1.0.0", }, } as any); + expect(await exited).toBe(0); }); test("dragon test 2", async () => { @@ -522,7 +567,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -547,6 +591,7 @@ describe("yarn tests", () => { name: "no-deps", version: "1.0.0", } as any); + expect(await exited).toBe(0); }); test("dragon test 3", async () => { @@ -574,7 +619,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -599,6 +643,7 @@ describe("yarn tests", () => { "no-deps": "*", }, } as any); + expect(await exited).toBe(0); }); test("dragon test 4", async () => { @@ -641,7 +686,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -667,6 +711,7 @@ describe("yarn tests", () => { "no-deps": "*", }, } as any); + expect(await exited).toBe(0); }); test("dragon test 5", async () => { @@ -720,7 +765,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -753,6 +797,7 @@ describe("yarn tests", () => { name: "various-requires", version: "1.0.0", } as any); + expect(await exited).toBe(0); }); test.todo("dragon test 6", async () => { @@ -854,7 +899,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -869,6 +913,7 @@ describe("yarn tests", () => { "", " 7 packages installed", ]); + expect(await exited).toBe(0); }); test.todo("dragon test 7", async () => { @@ -899,7 +944,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); expect(stdout).toBeDefined(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -911,6 +955,7 @@ describe("yarn tests", () => { "", " 7 packages installed", ]); + expect(await exited).toBe(0); await writeFile( join(packageDir, "test.js"), @@ -930,7 +975,6 @@ describe("yarn tests", () => { err = await new Response(stderr).text(); expect(stdout).toBeDefined(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toBeEmpty(); expect(out).toBe("1.0.0 1.0.0\n"); @@ -952,6 +996,7 @@ describe("yarn tests", () => { join(packageDir, "node_modules", "dragon-test-7-d", "node_modules", "dragon-test-7-b", "node_modules"), ), ).toBeFalse(); + expect(await exited).toBe(0); }); test("dragon test 8", async () => { @@ -982,7 +1027,6 @@ describe("yarn tests", () => { const err = await new Response(stderr).text(); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -994,6 +1038,7 @@ describe("yarn tests", () => { "", " 4 packages installed", ]); + expect(await exited).toBe(0); }); test("dragon test 9", async () => { @@ -1022,7 +1067,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); expect(stdout).toBeDefined(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("not found"); expect(err).not.toContain("error:"); @@ -1036,6 +1080,7 @@ describe("yarn tests", () => { expect(await file(join(packageDir, "node_modules", "first", "package.json")).json()).toEqual( await file(join(packageDir, "node_modules", "second", "package.json")).json(), ); + expect(await exited).toBe(0); }); test.todo("dragon test 10", async () => { @@ -1099,7 +1144,6 @@ describe("yarn tests", () => { const out = await new Response(stdout).text(); expect(stderr).toBeDefined(); const err = await new Response(stderr).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1110,6 +1154,7 @@ describe("yarn tests", () => { "", " 3 packages installed", ]); + expect(await exited).toBe(0); }); test("dragon test 12", async () => { @@ -1161,7 +1206,6 @@ describe("yarn tests", () => { const out = await new Response(stdout).text(); expect(stderr).toBeDefined(); const err = await new Response(stderr).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1185,6 +1229,7 @@ describe("yarn tests", () => { "no-deps": "*", }, } as any); + expect(await exited).toBe(0); }); test("it should not warn when the peer dependency resolution is compatible", async () => { @@ -1213,7 +1258,6 @@ describe("yarn tests", () => { const out = await new Response(stdout).text(); expect(stderr).toBeDefined(); const err = await new Response(stderr).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1225,6 +1269,7 @@ describe("yarn tests", () => { " 2 packages installed", ]); expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".cache", "no-deps", "peer-deps-fixed"]); + expect(await exited).toBe(0); }); test("it should warn when the peer dependency resolution is incompatible", async () => { @@ -1253,7 +1298,6 @@ describe("yarn tests", () => { const out = await new Response(stdout).text(); expect(stderr).toBeDefined(); const err = await new Response(stderr).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1265,6 +1309,7 @@ describe("yarn tests", () => { " 2 packages installed", ]); expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".cache", "no-deps", "peer-deps-fixed"]); + expect(await exited).toBe(0); }); test.todo( @@ -1293,7 +1338,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1304,6 +1348,7 @@ describe("yarn tests", () => { "", " 5 packages installed", ]); + expect(await exited).toBe(0); await writeFile( join(packageDir, "test.js"), @@ -1368,9 +1413,9 @@ describe("yarn tests", () => { err = await new Response(stderr).text(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); - expect(err).toBeEmpty(); expect(out).toBe("true\ntrue\ntrue"); + expect(err).toBeEmpty(); + expect(await exited).toBe(0); }, ); @@ -1398,7 +1443,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1409,6 +1453,7 @@ describe("yarn tests", () => { "", " 4 packages installed", ]); + expect(await exited).toBe(0); await writeFile( join(packageDir, "test.js"), @@ -1429,9 +1474,9 @@ describe("yarn tests", () => { err = await new Response(stderr).text(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); - expect(err).toBeEmpty(); expect(out).toBe("true\n"); + expect(err).toBeEmpty(); + expect(await exited).toBe(0); }); test("it should install in such a way that two identical packages with the same peer dependencies are the same instances (complex)", async () => { await writeFile( @@ -1458,7 +1503,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1470,6 +1514,7 @@ describe("yarn tests", () => { "", " 4 packages installed", ]); + expect(await exited).toBe(0); await writeFile( join(packageDir, "test.js"), @@ -1490,9 +1535,9 @@ describe("yarn tests", () => { err = await new Response(stderr).text(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); - expect(err).toBeEmpty(); expect(out).toBe("true\n"); + expect(err).toBeEmpty(); + expect(await exited).toBe(0); }); test("it shouldn't deduplicate two packages with similar peer dependencies but different names", async () => { @@ -1520,7 +1565,6 @@ describe("yarn tests", () => { var err = await new Response(stderr).text(); var out = await new Response(stdout).text(); - expect(await exited).toBe(0); expect(err).toContain("Saved lockfile"); expect(err).not.toContain("error:"); expect(err).not.toContain("not found"); @@ -1532,6 +1576,7 @@ describe("yarn tests", () => { "", " 3 packages installed", ]); + expect(await exited).toBe(0); await writeFile(join(packageDir, "test.js"), `console.log(require('peer-deps') === require('peer-deps-too'));`); @@ -1546,8 +1591,8 @@ describe("yarn tests", () => { err = await new Response(stderr).text(); out = await new Response(stdout).text(); - expect(await exited).toBe(0); - expect(err).toBeEmpty(); expect(out).toBe("false\n"); + expect(err).toBeEmpty(); + expect(await exited).toBe(0); }); });