tigerbeetle 0.0.36 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +1 -1
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,1448 @@
|
|
|
1
|
+
//! Checks for various non-functional properties of the code itself.
|
|
2
|
+
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const Allocator = std.mem.Allocator;
|
|
5
|
+
const assert = std.debug.assert;
|
|
6
|
+
const fs = std.fs;
|
|
7
|
+
const mem = std.mem;
|
|
8
|
+
const Ast = std.zig.Ast;
|
|
9
|
+
|
|
10
|
+
const stdx = @import("stdx");
|
|
11
|
+
const Shell = @import("./shell.zig");
|
|
12
|
+
|
|
13
|
+
const Snap = stdx.Snap;
|
|
14
|
+
const module_path = "src";
|
|
15
|
+
const snap = Snap.snap_fn(module_path);
|
|
16
|
+
|
|
17
|
+
const MiB = stdx.MiB;
|
|
18
|
+
|
|
19
|
+
test "tidy" {
|
|
20
|
+
const gpa = std.testing.allocator;
|
|
21
|
+
|
|
22
|
+
var errors: Errors = .{};
|
|
23
|
+
|
|
24
|
+
const shell = try Shell.create(gpa);
|
|
25
|
+
defer shell.destroy();
|
|
26
|
+
|
|
27
|
+
var counter: IdentifierCounter = try .init(gpa);
|
|
28
|
+
defer counter.deinit(gpa);
|
|
29
|
+
|
|
30
|
+
var dead_files_detector = DeadFilesDetector.init(gpa);
|
|
31
|
+
defer dead_files_detector.deinit(gpa);
|
|
32
|
+
|
|
33
|
+
// NB: all checks are intentionally implemented in a streaming fashion,
|
|
34
|
+
// such that we only need to read the files once.
|
|
35
|
+
const file_buffer = try gpa.alloc(u8, 1 * MiB);
|
|
36
|
+
defer gpa.free(file_buffer);
|
|
37
|
+
|
|
38
|
+
const paths = try list_file_paths(shell);
|
|
39
|
+
for (paths) |file_path| {
|
|
40
|
+
const source_file = try SourceFile.read(file_path, file_buffer);
|
|
41
|
+
try tidy_file(gpa, &counter, source_file, &errors);
|
|
42
|
+
|
|
43
|
+
if (source_file.has_extension(".zig")) {
|
|
44
|
+
try dead_files_detector.visit(source_file);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dead_files_detector.finish(&errors);
|
|
49
|
+
|
|
50
|
+
if (errors.count > 0) return error.Untidy;
|
|
51
|
+
assert(errors.count == 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const Errors = struct {
|
|
55
|
+
count: u32 = 0,
|
|
56
|
+
captured: ?std.ArrayListUnmanaged(u8) = null, // For tests.
|
|
57
|
+
|
|
58
|
+
pub fn add_control_character(
|
|
59
|
+
errors: *Errors,
|
|
60
|
+
file: SourceFile,
|
|
61
|
+
offset: usize,
|
|
62
|
+
character: u8,
|
|
63
|
+
) void {
|
|
64
|
+
errors.emit(
|
|
65
|
+
"{s}:{d}: error: control character code={}\n",
|
|
66
|
+
.{ file.path, file.line_number(offset), character },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn add_banned(
|
|
71
|
+
errors: *Errors,
|
|
72
|
+
file: SourceFile,
|
|
73
|
+
offset: usize,
|
|
74
|
+
banned_item: []const u8,
|
|
75
|
+
replacement: []const u8,
|
|
76
|
+
) void {
|
|
77
|
+
errors.emit(
|
|
78
|
+
"{s}:{d}: error: {s} is banned, use {s}\n",
|
|
79
|
+
.{ file.path, file.line_number(offset), banned_item, replacement },
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn add_banned_reminder(
|
|
84
|
+
errors: *Errors,
|
|
85
|
+
file: SourceFile,
|
|
86
|
+
offset: usize,
|
|
87
|
+
banned_item: []const u8,
|
|
88
|
+
) void {
|
|
89
|
+
errors.emit(
|
|
90
|
+
"{s}:{d}: error: leftover {s}, remove before merge\n",
|
|
91
|
+
.{ file.path, file.line_number(offset), banned_item },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub fn add_long_line(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
96
|
+
const line_number = line_index + 1;
|
|
97
|
+
errors.emit(
|
|
98
|
+
"{s}:{d}: error: line exceeds 100 columns\n",
|
|
99
|
+
.{ file.path, line_number },
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn add_trailing_whitespace(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
104
|
+
errors.emit(
|
|
105
|
+
"{s}:{d}: error: trailing whitespace\n",
|
|
106
|
+
.{ file.path, line_index + 1 },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
pub fn add_bad_type_function_name(
|
|
111
|
+
errors: *Errors,
|
|
112
|
+
file: SourceFile,
|
|
113
|
+
line_index: usize,
|
|
114
|
+
function_name: []const u8,
|
|
115
|
+
) void {
|
|
116
|
+
const line_number = line_index + 1;
|
|
117
|
+
errors.emit(
|
|
118
|
+
"{s}:{d}: error: type function name '{s}' should end in 'Type'\n",
|
|
119
|
+
.{ file.path, line_number, function_name },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pub fn add_long_function(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
124
|
+
const line_number = line_index + 1;
|
|
125
|
+
errors.emit(
|
|
126
|
+
"{s}:{d}: error: functions exceeds 70 lines\n",
|
|
127
|
+
.{ file.path, line_number },
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
pub fn add_ambiguous_precedence(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
132
|
+
const line_number = line_index + 1;
|
|
133
|
+
errors.emit(
|
|
134
|
+
"{s}:{d}: error: ambiguous operator precedence, add parenthesis\n",
|
|
135
|
+
.{ file.path, line_number },
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pub fn add_dead_declaration(errors: *Errors, file: SourceFile, declaration: []const u8) void {
|
|
140
|
+
errors.emit("{s}: error: '{s}' is dead code\n", .{ file.path, declaration });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn add_defer_newline(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
144
|
+
const line_number = line_index + 1;
|
|
145
|
+
errors.emit(
|
|
146
|
+
"{s}:{d}: error: defer must be followed by a blank line\n",
|
|
147
|
+
.{ file.path, line_number },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn add_invalid_markdown_title(errors: *Errors, file: SourceFile) void {
|
|
152
|
+
errors.emit(
|
|
153
|
+
"{s}: error: document should have exactly one top-level '# Title'\n",
|
|
154
|
+
.{file.path},
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
pub fn add_file_untracked(errors: *Errors, file: []const u8) void {
|
|
159
|
+
errors.emit(
|
|
160
|
+
"{s}: error: imported file untracked by git\n",
|
|
161
|
+
.{file},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub fn add_file_dead(errors: *Errors, file: []const u8) void {
|
|
166
|
+
errors.emit(
|
|
167
|
+
"{s}: error: file never imported\n",
|
|
168
|
+
.{file},
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
pub fn add_tracking(errors: *Errors, file: SourceFile, line_index: usize) void {
|
|
173
|
+
errors.emit(
|
|
174
|
+
"{s}:{d}: error: remove '?si=...' tracking parameter from URL\n",
|
|
175
|
+
.{ file.path, line_index },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn emit(errors: *Errors, comptime fmt: []const u8, args: anytype) void {
|
|
180
|
+
comptime assert(fmt[fmt.len - 1] == '\n');
|
|
181
|
+
errors.count += 1;
|
|
182
|
+
if (errors.captured) |*captured| {
|
|
183
|
+
captured.writer(std.testing.allocator).print(fmt, args) catch @panic("OOM");
|
|
184
|
+
} else {
|
|
185
|
+
std.debug.print(fmt, args);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const SourceFile = struct {
|
|
191
|
+
path: []const u8,
|
|
192
|
+
text: [:0]const u8,
|
|
193
|
+
|
|
194
|
+
// NB: The return value borrows both path and buffer.
|
|
195
|
+
fn read(path: []const u8, buffer: []u8) !SourceFile {
|
|
196
|
+
const bytes_read = (try std.fs.cwd().readFile(path, buffer)).len;
|
|
197
|
+
if (bytes_read >= buffer.len - 1) return error.FileTooLong;
|
|
198
|
+
buffer[bytes_read] = 0;
|
|
199
|
+
return .{
|
|
200
|
+
.path = path,
|
|
201
|
+
.text = buffer[0..bytes_read :0],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn has_extension(file: SourceFile, extension: []const u8) bool {
|
|
206
|
+
assert(extension.len > 0);
|
|
207
|
+
assert(extension[0] == '.');
|
|
208
|
+
return std.mem.endsWith(u8, file.path, extension);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// O(N), but only invoked on the cold path (when there are errors).
|
|
212
|
+
fn line_number(file: SourceFile, offset: usize) usize {
|
|
213
|
+
assert(offset <= file.text.len);
|
|
214
|
+
// +1: Line _index_ is zero-based, line _number_ is one-based.
|
|
215
|
+
return std.mem.count(u8, file.text[0..offset], "\n") + 1;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
fn tidy_file(
|
|
220
|
+
gpa: Allocator,
|
|
221
|
+
counter: *IdentifierCounter,
|
|
222
|
+
file: SourceFile,
|
|
223
|
+
errors: *Errors,
|
|
224
|
+
) Allocator.Error!void {
|
|
225
|
+
tidy_control_characters(file, errors);
|
|
226
|
+
if (file.has_extension(".zig")) {
|
|
227
|
+
tidy_banned(file, errors);
|
|
228
|
+
tidy_lines(file, errors);
|
|
229
|
+
tidy_type_functions(file, errors);
|
|
230
|
+
|
|
231
|
+
var tree = try std.zig.Ast.parse(gpa, file.text, .zig);
|
|
232
|
+
defer tree.deinit(gpa);
|
|
233
|
+
|
|
234
|
+
tidy_dead_declarations(file, &tree, counter, errors);
|
|
235
|
+
tidy_ast(file, &tree, errors);
|
|
236
|
+
}
|
|
237
|
+
if (file.has_extension(".md")) {
|
|
238
|
+
tidy_markdown_title(file, errors);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn check_tidy_file(file_path: []const u8, file_text: [:0]const u8, want: Snap) !void {
|
|
243
|
+
const gpa = std.testing.allocator;
|
|
244
|
+
|
|
245
|
+
var counter: IdentifierCounter = try .init(gpa);
|
|
246
|
+
defer counter.deinit(gpa);
|
|
247
|
+
|
|
248
|
+
var errors: Errors = .{ .captured = .{} };
|
|
249
|
+
defer errors.captured.?.deinit(std.testing.allocator);
|
|
250
|
+
|
|
251
|
+
try tidy_file(gpa, &counter, .{ .path = file_path, .text = file_text }, &errors);
|
|
252
|
+
const got = errors.captured.?.items;
|
|
253
|
+
|
|
254
|
+
try want.diff(got);
|
|
255
|
+
assert(errors.count == std.mem.count(u8, got, "\n"));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fn tidy_control_characters(file: SourceFile, errors: *Errors) void {
|
|
259
|
+
const binary_file_extensions: []const []const u8 = &.{ ".ico", ".png", ".webp" };
|
|
260
|
+
for (binary_file_extensions) |extension| {
|
|
261
|
+
if (file.has_extension(extension)) return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const allowed = .{
|
|
265
|
+
.@"\r" = file.has_extension(".bat"),
|
|
266
|
+
|
|
267
|
+
// Visual Studio insists on \t, taking the best from `make`.
|
|
268
|
+
// Go uses tabs.
|
|
269
|
+
.@"\t" = file.has_extension(".sln") or
|
|
270
|
+
(file.has_extension(".go") or
|
|
271
|
+
(file.has_extension(".md") and mem.indexOf(u8, file.text, "```go") != null)),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
var remaining = file.text;
|
|
275
|
+
while (mem.indexOfAny(u8, remaining, "\r\t")) |index| {
|
|
276
|
+
const offset = index + (file.text.len - remaining.len);
|
|
277
|
+
inline for (comptime std.meta.fieldNames(@TypeOf(allowed))) |field| {
|
|
278
|
+
if (remaining[index] == field[0]) {
|
|
279
|
+
if (!@field(allowed, field)) {
|
|
280
|
+
errors.add_control_character(file, offset, field[0]);
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
} else unreachable;
|
|
285
|
+
|
|
286
|
+
remaining = remaining[index + 1 ..];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
test tidy_control_characters {
|
|
291
|
+
try check_tidy_file(
|
|
292
|
+
"hello.txt",
|
|
293
|
+
"Hello\t\nWorld\r\n",
|
|
294
|
+
snap(@src(),
|
|
295
|
+
\\hello.txt:1: error: control character code=9
|
|
296
|
+
\\hello.txt:2: error: control character code=13
|
|
297
|
+
\\
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn tidy_banned(file: SourceFile, errors: *Errors) void {
|
|
303
|
+
// Vendored code is exempt from bans.
|
|
304
|
+
if (std.mem.eql(u8, file.path, "src/stdx/vendored/aegis.zig")) return;
|
|
305
|
+
// Don't ban ourselves!
|
|
306
|
+
if (std.mem.eql(u8, file.path, "src/tidy.zig")) return;
|
|
307
|
+
|
|
308
|
+
const ban_list: []const struct { []const u8, []const u8 } = &.{
|
|
309
|
+
// Functionality provided by stdx:
|
|
310
|
+
.{ "std.BoundedArray", "stdx.BoundedArrayType" },
|
|
311
|
+
.{ "StaticBitSet", "stdx.BitSetType" },
|
|
312
|
+
.{ "std.time.Duration", "stdx.Duration" },
|
|
313
|
+
.{ "std.time.Instant", "stdx.Instant" },
|
|
314
|
+
.{ "hasUniqueRepresentation", "stdx.has_unique_representation" },
|
|
315
|
+
.{ "@memcpy(", "stdx.copy_disjoint" },
|
|
316
|
+
.{ "mem.copyForwards(", "stdx.copy_left" },
|
|
317
|
+
.{ "mem.copyBackwards(", "stdx.copy_right" },
|
|
318
|
+
.{ "uintLessThan", "stdx.PRNG" },
|
|
319
|
+
.{ "intRangeLessThan", "stdx.PRNG" },
|
|
320
|
+
.{ "intRangeAtMost", "stdx.PRNG" },
|
|
321
|
+
.{ "intRangeAtMostBiased", "stdx.PRNG" },
|
|
322
|
+
|
|
323
|
+
// Library footguns:
|
|
324
|
+
.{ "unexpectedErrno", "stdx.unexpected_errno" },
|
|
325
|
+
.{ "posix.send(", "posix.sendto to avoid connection race condition" },
|
|
326
|
+
|
|
327
|
+
// Language footguns:
|
|
328
|
+
.{ "== error.", "switch to avoid silent anyerror upcast" },
|
|
329
|
+
.{ "!= error.", "switch to avoid silent anyerror upcast" },
|
|
330
|
+
|
|
331
|
+
// Everything else:
|
|
332
|
+
.{ "debug.assert(", "unqualified assert" },
|
|
333
|
+
.{ "Self = @This()", "proper type name" },
|
|
334
|
+
.{ "!comptime", "! inside comptime" },
|
|
335
|
+
.{ "usingnamespace", "something else" },
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
for (ban_list) |ban_item| {
|
|
339
|
+
const banned, const replacement = ban_item;
|
|
340
|
+
if (std.mem.indexOf(u8, file.text, banned)) |offset| {
|
|
341
|
+
errors.add_banned(file, offset, banned, replacement);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Reminders:
|
|
346
|
+
// Do use FIXME comments proactively while iterating on the code when you want to make sure
|
|
347
|
+
// something is revisited before getting into the main branch.
|
|
348
|
+
inline for (.{ "FIXME", "dbg(" }) |banned| {
|
|
349
|
+
if (std.mem.indexOf(u8, file.text, banned)) |offset| {
|
|
350
|
+
if (std.mem.startsWith(u8, file.text[offset..], "dbg(prefix: []const u8")) {
|
|
351
|
+
// Allow fn dbg( function definition.
|
|
352
|
+
|
|
353
|
+
} else {
|
|
354
|
+
errors.add_banned_reminder(file, offset, banned);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
test tidy_banned {
|
|
361
|
+
try check_tidy_file(
|
|
362
|
+
\\banned.zig
|
|
363
|
+
,
|
|
364
|
+
\\//FIXME: use copy_disjoint:
|
|
365
|
+
\\@memcpy(foo, bar)
|
|
366
|
+
,
|
|
367
|
+
snap(@src(),
|
|
368
|
+
\\banned.zig:2: error: @memcpy( is banned, use stdx.copy_disjoint
|
|
369
|
+
\\banned.zig:1: error: leftover FIXME, remove before merge
|
|
370
|
+
\\
|
|
371
|
+
),
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
fn tidy_lines(file: SourceFile, errors: *Errors) void {
|
|
376
|
+
if (std.mem.endsWith(u8, file.path, "low_level_hash_vectors.zig")) return;
|
|
377
|
+
|
|
378
|
+
var line_iterator = mem.splitScalar(u8, file.text, '\n');
|
|
379
|
+
var line_index: u32 = 0;
|
|
380
|
+
while (line_iterator.next()) |line| : (line_index += 1) {
|
|
381
|
+
tidy_line(file, line, line_index, errors);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fn tidy_line(file: SourceFile, line: []const u8, line_index: usize, errors: *Errors) void {
|
|
386
|
+
const line_length = tidy_line_length(line);
|
|
387
|
+
if (line_length <= 100) return;
|
|
388
|
+
|
|
389
|
+
if (tidy_line_link(line)) return;
|
|
390
|
+
|
|
391
|
+
// Journal recovery table
|
|
392
|
+
if (std.mem.indexOf(u8, line, "Case.init(") != null) return;
|
|
393
|
+
|
|
394
|
+
// For multiline strings, we care that the _result_ fits 100 characters,
|
|
395
|
+
// but we don't mind indentation in the source.
|
|
396
|
+
if (tidy_line_raw_literal(line)) |string_value| {
|
|
397
|
+
const string_value_length = tidy_line_length(string_value);
|
|
398
|
+
if (string_value_length <= 100) return;
|
|
399
|
+
|
|
400
|
+
if (std.mem.endsWith(u8, file.path, "state_machine_tests.zig") and
|
|
401
|
+
(std.mem.startsWith(u8, string_value, " account A") or
|
|
402
|
+
std.mem.startsWith(u8, string_value, " transfer T") or
|
|
403
|
+
std.mem.startsWith(u8, string_value, " transfer ")))
|
|
404
|
+
{
|
|
405
|
+
// Table tests from state_machine.zig. They are intentionally wide.
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// vsr.zig's Checkpoint ops diagram.
|
|
410
|
+
if (std.mem.endsWith(u8, file.path, "vsr.zig") and
|
|
411
|
+
std.mem.startsWith(u8, string_value, "OPS: ")) return;
|
|
412
|
+
|
|
413
|
+
// trace.zig's JSON snapshot test.
|
|
414
|
+
if (std.mem.endsWith(u8, file.path, "trace.zig") and
|
|
415
|
+
std.mem.startsWith(u8, string_value, "{\"pid\":1,\"tid\":")) return;
|
|
416
|
+
|
|
417
|
+
// AMQP JSON snapshot test.
|
|
418
|
+
if (std.mem.endsWith(u8, file.path, "cdc/runner.zig") and
|
|
419
|
+
std.mem.startsWith(u8, string_value, "{\"timestamp\":")) return;
|
|
420
|
+
|
|
421
|
+
// Message formatting tests.
|
|
422
|
+
if (std.mem.endsWith(u8, file.path, "message_header.zig") and
|
|
423
|
+
std.mem.startsWith(u8, string_value, "Prepare{")) return;
|
|
424
|
+
|
|
425
|
+
// Flag snapshot test.
|
|
426
|
+
if (std.mem.endsWith(u8, file.path, "flags.zig") and
|
|
427
|
+
std.mem.startsWith(u8, string_value, "error: subcommand required")) return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
errors.add_long_line(file, line_index);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
fn tidy_line_length(line: []const u8) usize {
|
|
434
|
+
// Count codepoints for simplicity, even if it is wrong.
|
|
435
|
+
return std.unicode.utf8CountCodepoints(line) catch @panic("invalid utf-8");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/// Heuristically checks if a `line` contains an URL.
|
|
439
|
+
fn tidy_line_link(line: []const u8) bool {
|
|
440
|
+
return std.mem.indexOf(u8, line, "https://") != null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// If a line is a `\\` string literal, extract its value.
|
|
444
|
+
fn tidy_line_raw_literal(line: []const u8) ?[]const u8 {
|
|
445
|
+
const indentation, const value = stdx.cut(line, "\\\\") orelse return null;
|
|
446
|
+
for (indentation) |c| if (c != ' ') return null;
|
|
447
|
+
return value;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
test tidy_lines {
|
|
451
|
+
try check_tidy_file(
|
|
452
|
+
\\lines.zig
|
|
453
|
+
,
|
|
454
|
+
"" ++
|
|
455
|
+
"pub const x = 92;\n" ++
|
|
456
|
+
"pub const x = " ++ ("9" ** 199) ++ ";\n" ++
|
|
457
|
+
"pub const url = \"https://example." ++ ("0" ** 199) ++ " \";\n" ++
|
|
458
|
+
" \\\\" ++ ("9" ** 99) ++ "\n" ++
|
|
459
|
+
" \"" ++ ("9" ** 99) ++ "\"\n",
|
|
460
|
+
snap(@src(),
|
|
461
|
+
\\lines.zig:2: error: line exceeds 100 columns
|
|
462
|
+
\\lines.zig:5: error: line exceeds 100 columns
|
|
463
|
+
\\
|
|
464
|
+
),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// All functions using the `CamelCase` naming convention return a type,
|
|
469
|
+
/// so we enforce that the function name also ends with the `Type` suffix.
|
|
470
|
+
fn tidy_type_functions(file: SourceFile, errors: *Errors) void {
|
|
471
|
+
var line_index: u32 = 0;
|
|
472
|
+
var it = std.mem.splitScalar(u8, file.text, '\n');
|
|
473
|
+
while (it.next()) |line| : (line_index += 1) {
|
|
474
|
+
// Zig fmt enforces that the pattern `fn Foo(` is not split across multiple lines.
|
|
475
|
+
|
|
476
|
+
const prefix, const suffix = stdx.cut(line, "fn ") orelse continue;
|
|
477
|
+
// Not all `fn ` tokens are functions, some may be `callback_fn` for example.
|
|
478
|
+
// Functions appear at the beginning of a line or after a whitespace.
|
|
479
|
+
if (prefix.len > 0 and prefix[prefix.len - 1] != ' ') continue;
|
|
480
|
+
const function_name, _ = stdx.cut(suffix, "(") orelse continue;
|
|
481
|
+
if (function_name.len == 0) continue; // E.g: `*const fn (*anyopaque) void`.
|
|
482
|
+
assert(function_name.len > 0);
|
|
483
|
+
|
|
484
|
+
// Skipping naming convention that requires upper-case functions.
|
|
485
|
+
if (std.mem.startsWith(u8, function_name, "JNI_")) continue;
|
|
486
|
+
// Windows use CamelCase functions.
|
|
487
|
+
if (std.mem.indexOf(u8, line, "extern \"kernel32\"") != null) continue;
|
|
488
|
+
|
|
489
|
+
if (std.ascii.isUpper(function_name[0])) {
|
|
490
|
+
if (!std.mem.endsWith(u8, function_name, "Type")) {
|
|
491
|
+
errors.add_bad_type_function_name(file, line_index, function_name);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
test tidy_type_functions {
|
|
498
|
+
try check_tidy_file(
|
|
499
|
+
\\type_functions.zig
|
|
500
|
+
,
|
|
501
|
+
\\pub fn MyArrayType() type { }
|
|
502
|
+
++ "\npub fn" ++ " MyArray() type { }" ++ "\n" ++
|
|
503
|
+
\\ pub const callback = *const fn (*anyopaque) void;
|
|
504
|
+
,
|
|
505
|
+
snap(@src(),
|
|
506
|
+
\\type_functions.zig:2: error: type function name 'MyArray' should end in 'Type'
|
|
507
|
+
\\
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const IdentifierCounter = struct {
|
|
513
|
+
const file_identifier_count_max = 100_000;
|
|
514
|
+
|
|
515
|
+
map: std.StringHashMapUnmanaged(struct { count: u32, offset: u32 }) = .{},
|
|
516
|
+
|
|
517
|
+
pub fn init(gpa: Allocator) !IdentifierCounter {
|
|
518
|
+
var counter: IdentifierCounter = .{};
|
|
519
|
+
try counter.map.ensureTotalCapacity(gpa, file_identifier_count_max + 1);
|
|
520
|
+
return counter;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
pub fn deinit(counter: *IdentifierCounter, gpa: Allocator) void {
|
|
524
|
+
counter.map.deinit(gpa);
|
|
525
|
+
counter.* = undefined;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
pub fn empty(counter: *const IdentifierCounter) bool {
|
|
529
|
+
return counter.map.count() == 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
pub fn clear(counter: *IdentifierCounter) void {
|
|
533
|
+
counter.map.clearRetainingCapacity();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
pub fn record(
|
|
537
|
+
counter: *IdentifierCounter,
|
|
538
|
+
tree: *const Ast,
|
|
539
|
+
token_text: []const u8,
|
|
540
|
+
token_offset: u32,
|
|
541
|
+
) void {
|
|
542
|
+
const gop = counter.map.getOrPutAssumeCapacity(token_text);
|
|
543
|
+
if (counter.map.count() > file_identifier_count_max) @panic("file too large");
|
|
544
|
+
|
|
545
|
+
if (gop.found_existing) {
|
|
546
|
+
// Count occurrences on a single line as one, as a special case for imports:
|
|
547
|
+
// const foo = std.foo;
|
|
548
|
+
const between_tokens_text = tree.source[gop.value_ptr.offset..token_offset];
|
|
549
|
+
const same_line_occurrence = mem.indexOfScalar(u8, between_tokens_text, '\n') == null;
|
|
550
|
+
if (same_line_occurrence) return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!gop.found_existing) gop.value_ptr.* = .{ .count = 0, .offset = 0 };
|
|
554
|
+
gop.value_ptr.count += 1;
|
|
555
|
+
gop.value_ptr.offset = token_offset;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
pub fn get(counter: *const IdentifierCounter, token_text: []const u8) u32 {
|
|
559
|
+
return counter.map.get(token_text).?.count;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
/// Detects unused constants and functions.
|
|
564
|
+
///
|
|
565
|
+
/// This is a one-side heuristic: there might be false negatives, but no false positives.
|
|
566
|
+
///
|
|
567
|
+
/// Current algorithm:
|
|
568
|
+
/// - Two passes.
|
|
569
|
+
/// - Pass 1: count how many times each identifier is mentioned in the file.
|
|
570
|
+
/// - Pass 2: warn about any unique identifier which is a non-public declaration.
|
|
571
|
+
///
|
|
572
|
+
/// At the moment, this is implemented using only the lexer, without looking at the AST, as that
|
|
573
|
+
/// seemed simpler.
|
|
574
|
+
fn tidy_dead_declarations(
|
|
575
|
+
file: SourceFile,
|
|
576
|
+
tree: *const Ast,
|
|
577
|
+
counter: *IdentifierCounter,
|
|
578
|
+
errors: *Errors,
|
|
579
|
+
) void {
|
|
580
|
+
assert(counter.empty());
|
|
581
|
+
defer counter.clear();
|
|
582
|
+
|
|
583
|
+
var identifier_start: ?Ast.ByteOffset = 0;
|
|
584
|
+
inline for (.{ .fill, .check }) |phase| {
|
|
585
|
+
next_token: for (
|
|
586
|
+
tree.tokens.items(.tag),
|
|
587
|
+
tree.tokens.items(.start),
|
|
588
|
+
0..,
|
|
589
|
+
) |tag, start, index_usize| {
|
|
590
|
+
const index: Ast.TokenIndex = @intCast(index_usize);
|
|
591
|
+
const identifier_start_previous = identifier_start;
|
|
592
|
+
identifier_start = switch (tag) {
|
|
593
|
+
.identifier => start,
|
|
594
|
+
else => null,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const start_previous = identifier_start_previous orelse continue :next_token;
|
|
598
|
+
const token_text = std.mem.trim(
|
|
599
|
+
u8,
|
|
600
|
+
tree.source[start_previous..start],
|
|
601
|
+
&std.ascii.whitespace,
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
switch (phase) {
|
|
605
|
+
.fill => counter.record(tree, token_text, start),
|
|
606
|
+
.check => {
|
|
607
|
+
const usages = counter.get(token_text);
|
|
608
|
+
assert(usages >= 1);
|
|
609
|
+
if (usages == 1) {
|
|
610
|
+
if (tidy_dead_declarations_is_private_declaration(tree, index - 1)) {
|
|
611
|
+
errors.add_dead_declaration(file, token_text);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
else => comptime unreachable,
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Checks if the given identifier token refers to non-public declaration.
|
|
622
|
+
fn tidy_dead_declarations_is_private_declaration(
|
|
623
|
+
tree: *const Ast,
|
|
624
|
+
token_index: Ast.TokenIndex,
|
|
625
|
+
) bool {
|
|
626
|
+
assert(tree.tokens.items(.tag)[token_index] == .identifier);
|
|
627
|
+
var declaration_keyword = false;
|
|
628
|
+
for (0..4) |context_offset| {
|
|
629
|
+
const context_tag = if (token_index - context_offset < 1)
|
|
630
|
+
.eof
|
|
631
|
+
else
|
|
632
|
+
tree.tokens.get(token_index - context_offset - 1).tag;
|
|
633
|
+
|
|
634
|
+
if (!declaration_keyword) {
|
|
635
|
+
switch (context_tag) {
|
|
636
|
+
.keyword_fn, .keyword_const => declaration_keyword = true,
|
|
637
|
+
// Not a declaration.
|
|
638
|
+
else => return false,
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
switch (context_tag) {
|
|
642
|
+
.keyword_inline, .keyword_extern, .string_literal => {},
|
|
643
|
+
// Public declaration can be used in a different file.
|
|
644
|
+
.keyword_pub, .keyword_export => return false,
|
|
645
|
+
// []const u8 or *const u8, not a declaration.
|
|
646
|
+
.r_bracket, .asterisk => return false,
|
|
647
|
+
// Non public declarations, never used.
|
|
648
|
+
else => return true,
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
} else unreachable;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
test tidy_dead_declarations {
|
|
655
|
+
try check_tidy_file(
|
|
656
|
+
\\dead.zig
|
|
657
|
+
,
|
|
658
|
+
\\ const std = @import("std");
|
|
659
|
+
\\ const import_unused = std.import_unused;
|
|
660
|
+
\\ pub fn public_used() void { private_used(); }
|
|
661
|
+
\\ fn private_used() void {}
|
|
662
|
+
\\ fn private_unused() void {}
|
|
663
|
+
,
|
|
664
|
+
snap(@src(),
|
|
665
|
+
\\dead.zig: error: 'import_unused' is dead code
|
|
666
|
+
\\dead.zig: error: 'private_unused' is dead code
|
|
667
|
+
\\
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
fn tidy_ast(
|
|
673
|
+
file: SourceFile,
|
|
674
|
+
tree: *const Ast,
|
|
675
|
+
errors: *Errors,
|
|
676
|
+
) void {
|
|
677
|
+
if (std.mem.eql(u8, file.path, "build.zig")) return;
|
|
678
|
+
if (std.mem.endsWith(u8, file.path, "build_multiversion.zig")) return;
|
|
679
|
+
if (std.mem.endsWith(u8, file.path, "bindings.zig")) return;
|
|
680
|
+
|
|
681
|
+
const tags = tree.nodes.items(.tag);
|
|
682
|
+
const datas = tree.nodes.items(.data);
|
|
683
|
+
// We can implement this in a streaming fashion, but its more convenient to materialize all
|
|
684
|
+
// functions. 1k functions per file should be enough even for TigerBeetle!
|
|
685
|
+
var functions: [1024]struct {
|
|
686
|
+
line_opening: usize,
|
|
687
|
+
line_closing: usize,
|
|
688
|
+
} = undefined;
|
|
689
|
+
var functions_count: u32 = 0;
|
|
690
|
+
|
|
691
|
+
for (tags, datas, 0..) |tag, data, node| {
|
|
692
|
+
if (tag == .fn_decl) { // Check function length.
|
|
693
|
+
const node_body = data.rhs;
|
|
694
|
+
|
|
695
|
+
const token_opening = tree.firstToken(@intCast(node));
|
|
696
|
+
const token_closing = tree.lastToken(@intCast(node_body));
|
|
697
|
+
|
|
698
|
+
const line_opening = tree.tokenLocation(0, token_opening).line;
|
|
699
|
+
const line_closing = tree.tokenLocation(0, token_closing).line;
|
|
700
|
+
|
|
701
|
+
functions[functions_count] = .{
|
|
702
|
+
.line_opening = line_opening,
|
|
703
|
+
.line_closing = line_closing,
|
|
704
|
+
};
|
|
705
|
+
functions_count += 1;
|
|
706
|
+
}
|
|
707
|
+
if (is_bin_op(tag)) { // Forbid mixing bitops and arithmetics without parentheses.
|
|
708
|
+
inline for (.{ data.lhs, data.rhs }) |child| {
|
|
709
|
+
const tag_child = tags[child];
|
|
710
|
+
if ((is_bin_op_bitwise(tag) and is_bin_op_arithmetic(tag_child)) or
|
|
711
|
+
(is_bin_op_arithmetic(tag) and is_bin_op_bitwise(tag_child)))
|
|
712
|
+
{
|
|
713
|
+
const token_opening = tree.firstToken(@intCast(node));
|
|
714
|
+
const line_opening = tree.tokenLocation(0, token_opening).line;
|
|
715
|
+
errors.add_ambiguous_precedence(file, line_opening);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
tidy_defer_newlines(file, tree, errors);
|
|
722
|
+
|
|
723
|
+
// We ratchet 70-lines-per-function TigerStyle rule from the bottom up. Some functions want
|
|
724
|
+
// to be really long, and that is big. The most values is in preventing originally small
|
|
725
|
+
// functions to grow over time.
|
|
726
|
+
const function_length_red_zone = .{
|
|
727
|
+
.min = 70, // NB: both are exclusive, so red zone is intentionally empty to start!
|
|
728
|
+
.max = 70,
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
for (functions[0..functions_count], 0..) |f, index| {
|
|
732
|
+
// Functions are sorted by the start line.
|
|
733
|
+
if (index > 0) assert(functions[index - 1].line_opening < f.line_opening);
|
|
734
|
+
|
|
735
|
+
if (index == functions_count - 1 or
|
|
736
|
+
functions[index + 1].line_opening > f.line_closing)
|
|
737
|
+
{
|
|
738
|
+
const function_length = f.line_closing - f.line_opening + 1;
|
|
739
|
+
if (function_length_red_zone.min < function_length and
|
|
740
|
+
function_length < function_length_red_zone.max)
|
|
741
|
+
{
|
|
742
|
+
errors.add_long_function(file, f.line_opening);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
fn tidy_defer_newlines(file: SourceFile, tree: *const Ast, errors: *Errors) void {
|
|
749
|
+
const tags = tree.tokens.items(.tag);
|
|
750
|
+
|
|
751
|
+
var index: usize = 0;
|
|
752
|
+
while (index < tags.len) : (index += 1) {
|
|
753
|
+
const tag = tags[index];
|
|
754
|
+
if (tag != .keyword_defer) continue;
|
|
755
|
+
|
|
756
|
+
const semicolon_index = tidy_defer_statement_end(tags, index) orelse continue;
|
|
757
|
+
const semicolon_line = tree.tokenLocation(0, @intCast(semicolon_index)).line;
|
|
758
|
+
|
|
759
|
+
var next_index = semicolon_index + 1;
|
|
760
|
+
while (next_index < tags.len and
|
|
761
|
+
tidy_is_comment_token(tags[next_index])) : (next_index += 1)
|
|
762
|
+
{}
|
|
763
|
+
|
|
764
|
+
if (next_index >= tags.len) break;
|
|
765
|
+
if (tags[next_index] == .eof) continue;
|
|
766
|
+
|
|
767
|
+
const next_line = tree.tokenLocation(0, @intCast(next_index)).line;
|
|
768
|
+
const next_tag = tags[next_index];
|
|
769
|
+
if (next_tag == .r_brace or next_tag == .keyword_errdefer or next_tag == .keyword_defer) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (next_line <= semicolon_line + 1) {
|
|
774
|
+
errors.add_defer_newline(file, semicolon_line);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
fn tidy_defer_statement_end(tags: []const std.zig.Token.Tag, defer_index: usize) ?usize {
|
|
780
|
+
var depth: i32 = 0;
|
|
781
|
+
|
|
782
|
+
var index: usize = defer_index + 1;
|
|
783
|
+
while (index < tags.len) : (index += 1) {
|
|
784
|
+
switch (tags[index]) {
|
|
785
|
+
.l_paren, .l_brace, .l_bracket => depth += 1,
|
|
786
|
+
.r_paren, .r_brace, .r_bracket => depth -= 1,
|
|
787
|
+
else => {},
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (depth == 0) {
|
|
791
|
+
switch (tags[index]) {
|
|
792
|
+
.semicolon => return index,
|
|
793
|
+
.r_brace => {
|
|
794
|
+
if (index + 1 < tags.len) {
|
|
795
|
+
if (tags[index + 1] == .semicolon) return index + 1;
|
|
796
|
+
if (tags[index + 1] == .keyword_else) continue;
|
|
797
|
+
}
|
|
798
|
+
return index; // defer { ... } without trailing semicolon.
|
|
799
|
+
},
|
|
800
|
+
else => {},
|
|
801
|
+
}
|
|
802
|
+
} else if (depth < 0) {
|
|
803
|
+
// Unterminated defer; bail out.
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
fn tidy_is_comment_token(tag: std.zig.Token.Tag) bool {
|
|
812
|
+
return switch (tag) {
|
|
813
|
+
.doc_comment, .container_doc_comment => true,
|
|
814
|
+
else => false,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
fn is_bin_op(tag: Ast.Node.Tag) bool {
|
|
819
|
+
return is_bin_op_bitwise(tag) or is_bin_op_arithmetic(tag);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
fn is_bin_op_bitwise(tag: Ast.Node.Tag) bool {
|
|
823
|
+
return switch (tag) {
|
|
824
|
+
.shl, .shl_sat => true,
|
|
825
|
+
.shr => true,
|
|
826
|
+
.bit_xor, .bit_or, .bit_and => true,
|
|
827
|
+
else => false,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
fn is_bin_op_arithmetic(tag: Ast.Node.Tag) bool {
|
|
832
|
+
return switch (tag) {
|
|
833
|
+
.add, .add_sat, .add_wrap => true,
|
|
834
|
+
.sub, .sub_sat, .sub_wrap => true,
|
|
835
|
+
.mul, .mul_sat, .mul_wrap => true,
|
|
836
|
+
.div, .mod => true,
|
|
837
|
+
else => false,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
test tidy_ast {
|
|
842
|
+
try check_tidy_file(
|
|
843
|
+
\\precedence.zig
|
|
844
|
+
,
|
|
845
|
+
\\ pub const confusing = 1 + foo << 3;
|
|
846
|
+
\\ pub const ok = 1 + (foo << 3);
|
|
847
|
+
,
|
|
848
|
+
snap(@src(),
|
|
849
|
+
\\precedence.zig:1: error: ambiguous operator precedence, add parenthesis
|
|
850
|
+
\\
|
|
851
|
+
),
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
test tidy_defer_newlines {
|
|
856
|
+
try check_tidy_file(
|
|
857
|
+
\\defer_newline.zig
|
|
858
|
+
,
|
|
859
|
+
\\pub fn foo() void {
|
|
860
|
+
\\ defer bar();
|
|
861
|
+
\\ baz();
|
|
862
|
+
\\}
|
|
863
|
+
\\pub fn bar() void {}
|
|
864
|
+
\\pub fn baz() void {}
|
|
865
|
+
,
|
|
866
|
+
snap(@src(),
|
|
867
|
+
\\defer_newline.zig:2: error: defer must be followed by a blank line
|
|
868
|
+
\\
|
|
869
|
+
),
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
try check_tidy_file(
|
|
873
|
+
\\defer_newline_ok.zig
|
|
874
|
+
,
|
|
875
|
+
\\pub fn foo() void {
|
|
876
|
+
\\ defer bar();
|
|
877
|
+
\\
|
|
878
|
+
\\ baz();
|
|
879
|
+
\\}
|
|
880
|
+
\\pub fn bar() void {}
|
|
881
|
+
\\pub fn baz() void {}
|
|
882
|
+
,
|
|
883
|
+
snap(@src(),
|
|
884
|
+
\\
|
|
885
|
+
),
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
try check_tidy_file(
|
|
889
|
+
\\defer_end_of_scope_ok.zig
|
|
890
|
+
,
|
|
891
|
+
\\pub fn foo() void {
|
|
892
|
+
\\ defer bar();
|
|
893
|
+
\\}
|
|
894
|
+
\\pub fn bar() void {}
|
|
895
|
+
,
|
|
896
|
+
snap(@src(),
|
|
897
|
+
\\
|
|
898
|
+
),
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
try check_tidy_file(
|
|
902
|
+
\\defer_block_ok.zig
|
|
903
|
+
,
|
|
904
|
+
\\pub fn foo() void {
|
|
905
|
+
\\ defer {
|
|
906
|
+
\\ _ = bar() catch {};
|
|
907
|
+
\\ }
|
|
908
|
+
\\
|
|
909
|
+
\\ baz();
|
|
910
|
+
\\}
|
|
911
|
+
\\pub fn bar() anyerror!void { return; }
|
|
912
|
+
\\pub fn baz() void {}
|
|
913
|
+
,
|
|
914
|
+
snap(@src(),
|
|
915
|
+
\\
|
|
916
|
+
),
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
try check_tidy_file(
|
|
920
|
+
\\defer_block_missing_blank_line.zig
|
|
921
|
+
,
|
|
922
|
+
\\pub fn foo() void {
|
|
923
|
+
\\ defer {
|
|
924
|
+
\\ _ = bar() catch {};
|
|
925
|
+
\\ }
|
|
926
|
+
\\ baz();
|
|
927
|
+
\\}
|
|
928
|
+
\\pub fn bar() anyerror!void { return; }
|
|
929
|
+
\\pub fn baz() void {}
|
|
930
|
+
,
|
|
931
|
+
snap(@src(),
|
|
932
|
+
\\defer_block_missing_blank_line.zig:4: error: defer must be followed by a blank line
|
|
933
|
+
\\
|
|
934
|
+
),
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
try check_tidy_file(
|
|
938
|
+
\\defer_followed_by_errdefer_ok.zig
|
|
939
|
+
,
|
|
940
|
+
\\pub fn foo() !void {
|
|
941
|
+
\\ var tmp: i32 = undefined;
|
|
942
|
+
\\ defer tmp_deinit();
|
|
943
|
+
\\ errdefer tmp_log();
|
|
944
|
+
\\ return tmp_use(tmp);
|
|
945
|
+
\\}
|
|
946
|
+
\\pub fn tmp_deinit() void {}
|
|
947
|
+
\\pub fn tmp_log() void {}
|
|
948
|
+
\\pub fn tmp_use(_: i32) !void { return; }
|
|
949
|
+
,
|
|
950
|
+
snap(@src(),
|
|
951
|
+
\\
|
|
952
|
+
),
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
try check_tidy_file(
|
|
956
|
+
\\defer_group_ok.zig
|
|
957
|
+
,
|
|
958
|
+
\\pub fn foo() void {
|
|
959
|
+
\\ defer stdout();
|
|
960
|
+
\\ defer stderr();
|
|
961
|
+
\\
|
|
962
|
+
\\ baz();
|
|
963
|
+
\\}
|
|
964
|
+
\\pub fn stdout() void {}
|
|
965
|
+
\\pub fn stderr() void {}
|
|
966
|
+
\\pub fn baz() void {}
|
|
967
|
+
,
|
|
968
|
+
snap(@src(),
|
|
969
|
+
\\
|
|
970
|
+
),
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
try check_tidy_file(
|
|
974
|
+
\\defer_inline_if_blank_line_ok.zig
|
|
975
|
+
,
|
|
976
|
+
\\pub fn foo() void {
|
|
977
|
+
\\ defer if (bar()) {
|
|
978
|
+
\\ baz();
|
|
979
|
+
\\ };
|
|
980
|
+
\\
|
|
981
|
+
\\ baz();
|
|
982
|
+
\\}
|
|
983
|
+
\\pub fn bar() bool { return true; }
|
|
984
|
+
\\pub fn baz() void {}
|
|
985
|
+
,
|
|
986
|
+
snap(@src(),
|
|
987
|
+
\\
|
|
988
|
+
),
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
try check_tidy_file(
|
|
992
|
+
\\defer_inline_if_else_no_blank_line_ok.zig
|
|
993
|
+
,
|
|
994
|
+
\\pub fn foo() void {
|
|
995
|
+
\\ defer if (bar()) {
|
|
996
|
+
\\ baz();
|
|
997
|
+
\\ } else {
|
|
998
|
+
\\ qux();
|
|
999
|
+
\\ };
|
|
1000
|
+
\\ // else must be part of the defer statement; no blank line required here.
|
|
1001
|
+
\\ zap();
|
|
1002
|
+
\\}
|
|
1003
|
+
\\pub fn bar() bool { return true; }
|
|
1004
|
+
\\pub fn baz() void {}
|
|
1005
|
+
\\pub fn qux() void {}
|
|
1006
|
+
\\pub fn zap() void {}
|
|
1007
|
+
,
|
|
1008
|
+
snap(@src(),
|
|
1009
|
+
\\
|
|
1010
|
+
),
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
try check_tidy_file(
|
|
1014
|
+
\\defer_inline_block_with_comment_ok.zig
|
|
1015
|
+
,
|
|
1016
|
+
\\pub fn foo() void {
|
|
1017
|
+
\\ defer if (bar()) {
|
|
1018
|
+
\\ baz();
|
|
1019
|
+
\\ };
|
|
1020
|
+
\\
|
|
1021
|
+
\\ // next statements
|
|
1022
|
+
\\ qux();
|
|
1023
|
+
\\}
|
|
1024
|
+
\\pub fn bar() bool { return true; }
|
|
1025
|
+
\\pub fn baz() void {}
|
|
1026
|
+
\\pub fn qux() void {}
|
|
1027
|
+
,
|
|
1028
|
+
snap(@src(),
|
|
1029
|
+
\\
|
|
1030
|
+
),
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
try check_tidy_file(
|
|
1034
|
+
\\defer_switch_ok.zig
|
|
1035
|
+
,
|
|
1036
|
+
\\pub fn foo() void {
|
|
1037
|
+
\\ defer switch (bar()) {
|
|
1038
|
+
\\ .ok => {},
|
|
1039
|
+
\\ .leak => {},
|
|
1040
|
+
\\ };
|
|
1041
|
+
\\
|
|
1042
|
+
\\ baz();
|
|
1043
|
+
\\}
|
|
1044
|
+
\\pub fn bar() enum { ok, leak } { return .ok; }
|
|
1045
|
+
\\pub fn baz() void {}
|
|
1046
|
+
,
|
|
1047
|
+
snap(@src(),
|
|
1048
|
+
\\
|
|
1049
|
+
),
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
try check_tidy_file(
|
|
1053
|
+
\\defer_inline_for_ok.zig
|
|
1054
|
+
,
|
|
1055
|
+
\\pub fn foo() void {
|
|
1056
|
+
\\ defer inline for (.{1, 2}) |s| {
|
|
1057
|
+
\\ _ = s;
|
|
1058
|
+
\\ };
|
|
1059
|
+
\\
|
|
1060
|
+
\\ baz();
|
|
1061
|
+
\\}
|
|
1062
|
+
\\pub fn baz() void {}
|
|
1063
|
+
,
|
|
1064
|
+
snap(@src(),
|
|
1065
|
+
\\
|
|
1066
|
+
),
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
try check_tidy_file(
|
|
1070
|
+
\\defer_with_catch_ok.zig
|
|
1071
|
+
,
|
|
1072
|
+
\\pub fn foo() void {
|
|
1073
|
+
\\ defer bar() catch {};
|
|
1074
|
+
\\
|
|
1075
|
+
\\ baz();
|
|
1076
|
+
\\}
|
|
1077
|
+
\\pub fn bar() anyerror!void { return; }
|
|
1078
|
+
\\pub fn baz() void {}
|
|
1079
|
+
,
|
|
1080
|
+
snap(@src(),
|
|
1081
|
+
\\
|
|
1082
|
+
),
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/// Checks that each markdown document has exactly one h1.
|
|
1087
|
+
///
|
|
1088
|
+
/// There are two schools of thought regarding largest (`# War and Peace`)
|
|
1089
|
+
/// headings in markdown. One school says that they are _section_ titles, so
|
|
1090
|
+
/// you could have multiple #'s in the document. But another option is to
|
|
1091
|
+
/// say that a single # signifies document _title_, and there should be only
|
|
1092
|
+
/// one in a document.
|
|
1093
|
+
///
|
|
1094
|
+
/// We use markdown to create HTML, so # turns into h1. MDN recommends that
|
|
1095
|
+
/// there's only a single h1 in a page:
|
|
1096
|
+
///
|
|
1097
|
+
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#avoid_using_multiple_h1_elements_on_one_page>
|
|
1098
|
+
///
|
|
1099
|
+
/// For this reason, we follow the second convention.
|
|
1100
|
+
fn tidy_markdown_title(file: SourceFile, errors: *Errors) void {
|
|
1101
|
+
var fenced_block = false; // Avoid interpreting `# ` shell comments as titles.
|
|
1102
|
+
var heading_count: u32 = 0;
|
|
1103
|
+
var line_count: u32 = 0;
|
|
1104
|
+
var it = std.mem.splitScalar(u8, file.text, '\n');
|
|
1105
|
+
while (it.next()) |line| {
|
|
1106
|
+
line_count += 1;
|
|
1107
|
+
if (mem.startsWith(u8, line, "```")) fenced_block = !fenced_block;
|
|
1108
|
+
if (!fenced_block and mem.startsWith(u8, line, "# ")) heading_count += 1;
|
|
1109
|
+
}
|
|
1110
|
+
assert(!fenced_block);
|
|
1111
|
+
switch (heading_count) {
|
|
1112
|
+
// No need for a title for a short note.
|
|
1113
|
+
0 => if (line_count > 2) errors.add_invalid_markdown_title(file),
|
|
1114
|
+
1 => {},
|
|
1115
|
+
else => errors.add_invalid_markdown_title(file),
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
test tidy_markdown_title {
|
|
1120
|
+
try check_tidy_file(
|
|
1121
|
+
\\ok.md
|
|
1122
|
+
,
|
|
1123
|
+
\\# TigerStyle
|
|
1124
|
+
\\
|
|
1125
|
+
\\Style applies everywhere!
|
|
1126
|
+
\\For example, we check that markdowns contains exactly one top-level title:
|
|
1127
|
+
\\
|
|
1128
|
+
\\```
|
|
1129
|
+
\\# Good Document
|
|
1130
|
+
\\```
|
|
1131
|
+
,
|
|
1132
|
+
snap(@src(),
|
|
1133
|
+
\\
|
|
1134
|
+
),
|
|
1135
|
+
);
|
|
1136
|
+
try check_tidy_file(
|
|
1137
|
+
\\bad.md
|
|
1138
|
+
,
|
|
1139
|
+
\\# Top Level Header
|
|
1140
|
+
\\
|
|
1141
|
+
\\Lorem Ipsum
|
|
1142
|
+
\\
|
|
1143
|
+
\\# And Another Top Level Header
|
|
1144
|
+
,
|
|
1145
|
+
snap(@src(),
|
|
1146
|
+
\\bad.md: error: document should have exactly one top-level '# Title'
|
|
1147
|
+
\\
|
|
1148
|
+
),
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Zig's lazy compilation model makes it too easy to forget to include a file into the build --- if
|
|
1153
|
+
// nothing imports a file, compiler just doesn't see it and can't flag it as unused.
|
|
1154
|
+
//
|
|
1155
|
+
// DeadFilesDetector implements heuristic detection of unused files, by "grepping" for import
|
|
1156
|
+
// statements and flagging file which are never imported. This gives false negatives for unreachable
|
|
1157
|
+
// cycles of files, as well as for identically-named files, but it should be good enough in
|
|
1158
|
+
// practice.
|
|
1159
|
+
const DeadFilesDetector = struct {
|
|
1160
|
+
const FileName = [64]u8;
|
|
1161
|
+
const FileState = struct { import_count: u32, definition_count: u32 };
|
|
1162
|
+
const FileMap = std.AutoArrayHashMap(FileName, FileState);
|
|
1163
|
+
|
|
1164
|
+
files: FileMap,
|
|
1165
|
+
|
|
1166
|
+
fn init(gpa: Allocator) DeadFilesDetector {
|
|
1167
|
+
return .{ .files = FileMap.init(gpa) };
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
fn deinit(detector: *DeadFilesDetector, _: Allocator) void {
|
|
1171
|
+
detector.files.deinit();
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
fn visit(detector: *DeadFilesDetector, file: SourceFile) Allocator.Error!void {
|
|
1175
|
+
assert(file.has_extension(".zig"));
|
|
1176
|
+
(try detector.file_state(file.path)).definition_count += 1;
|
|
1177
|
+
|
|
1178
|
+
var rest: []const u8 = file.text;
|
|
1179
|
+
for (0..1024) |_| {
|
|
1180
|
+
_, rest = stdx.cut(rest, "@import(\"") orelse break;
|
|
1181
|
+
const import_path, rest = stdx.cut(rest, "\")").?;
|
|
1182
|
+
if (std.mem.endsWith(u8, import_path, ".zig")) {
|
|
1183
|
+
(try detector.file_state(import_path)).import_count += 1;
|
|
1184
|
+
}
|
|
1185
|
+
} else {
|
|
1186
|
+
std.debug.panic("file with more than 1024 imports: {s}", .{file.path});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
fn finish(detector: *DeadFilesDetector, errors: *Errors) void {
|
|
1191
|
+
defer detector.files.clearRetainingCapacity();
|
|
1192
|
+
|
|
1193
|
+
for (detector.files.keys(), detector.files.values()) |name, state| {
|
|
1194
|
+
if (state.definition_count == 0) {
|
|
1195
|
+
errors.add_file_untracked(&name);
|
|
1196
|
+
}
|
|
1197
|
+
if (state.import_count == 0 and !is_entry_point(name)) {
|
|
1198
|
+
errors.add_file_dead(&name);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
fn file_state(detector: *DeadFilesDetector, path: []const u8) !*FileState {
|
|
1204
|
+
const gop = try detector.files.getOrPut(path_to_name(path));
|
|
1205
|
+
if (!gop.found_existing) gop.value_ptr.* = .{ .import_count = 0, .definition_count = 0 };
|
|
1206
|
+
return gop.value_ptr;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
fn path_to_name(path: []const u8) FileName {
|
|
1210
|
+
assert(std.mem.endsWith(u8, path, ".zig"));
|
|
1211
|
+
const basename = std.fs.path.basename(path);
|
|
1212
|
+
var file_name: FileName = @splat(0);
|
|
1213
|
+
assert(basename.len <= file_name.len);
|
|
1214
|
+
stdx.copy_disjoint(.inexact, u8, &file_name, basename);
|
|
1215
|
+
return file_name;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
fn is_entry_point(file: FileName) bool {
|
|
1219
|
+
const entry_points: []const []const u8 = &.{
|
|
1220
|
+
"build_multiversion.zig",
|
|
1221
|
+
"build.zig",
|
|
1222
|
+
"dotnet_bindings.zig",
|
|
1223
|
+
"file_checker.zig",
|
|
1224
|
+
"fuzz_tests.zig",
|
|
1225
|
+
"go_bindings.zig",
|
|
1226
|
+
"integration_tests.zig",
|
|
1227
|
+
"java_bindings.zig",
|
|
1228
|
+
"jni_tests.zig",
|
|
1229
|
+
"libtb_client.zig",
|
|
1230
|
+
"main.zig",
|
|
1231
|
+
"node_bindings.zig",
|
|
1232
|
+
"node.zig",
|
|
1233
|
+
"page_writer.zig",
|
|
1234
|
+
"python_bindings.zig",
|
|
1235
|
+
"scripts.zig",
|
|
1236
|
+
"search_index_writer.zig",
|
|
1237
|
+
"service_worker_writer.zig",
|
|
1238
|
+
"rust_bindings.zig",
|
|
1239
|
+
"single_page_writer.zig",
|
|
1240
|
+
"tb_client_header.zig",
|
|
1241
|
+
"unit_tests.zig",
|
|
1242
|
+
"vopr.zig",
|
|
1243
|
+
"vortex.zig",
|
|
1244
|
+
"zig_driver.zig",
|
|
1245
|
+
};
|
|
1246
|
+
for (entry_points) |entry_point| {
|
|
1247
|
+
if (std.mem.startsWith(u8, &file, entry_point)) return true;
|
|
1248
|
+
}
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
test "tidy changelog" {
|
|
1254
|
+
const gpa = std.testing.allocator;
|
|
1255
|
+
|
|
1256
|
+
var errors: Errors = .{};
|
|
1257
|
+
|
|
1258
|
+
const changelog_buffer = try gpa.alloc(u8, 1 * MiB);
|
|
1259
|
+
defer gpa.free(changelog_buffer);
|
|
1260
|
+
|
|
1261
|
+
const changelog = try SourceFile.read("CHANGELOG.md", changelog_buffer);
|
|
1262
|
+
|
|
1263
|
+
var line_iterator = mem.splitScalar(u8, changelog.text, '\n');
|
|
1264
|
+
var line_index: usize = 0;
|
|
1265
|
+
while (line_iterator.next()) |line| : (line_index += 1) {
|
|
1266
|
+
if (std.mem.endsWith(u8, line, " ")) {
|
|
1267
|
+
errors.add_trailing_whitespace(changelog, line_index);
|
|
1268
|
+
}
|
|
1269
|
+
const line_length = tidy_line_length(line);
|
|
1270
|
+
if (line_length > 100 and !tidy_line_link(line)) {
|
|
1271
|
+
errors.add_long_line(changelog, line_index);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (std.mem.indexOf(u8, line, "?si=") != null) {
|
|
1275
|
+
errors.add_tracking(changelog, line_index);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (errors.count > 0) return error.Untidy;
|
|
1279
|
+
assert(errors.count == 0);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
test "tidy no large blobs" {
|
|
1283
|
+
const allocator = std.testing.allocator;
|
|
1284
|
+
const shell = try Shell.create(allocator);
|
|
1285
|
+
defer shell.destroy();
|
|
1286
|
+
|
|
1287
|
+
// Run `git rev-list | git cat-file` to find large blobs. This is better than looking at the
|
|
1288
|
+
// files in the working tree, because it catches the cases where a large file is "removed" by
|
|
1289
|
+
// reverting the commit.
|
|
1290
|
+
//
|
|
1291
|
+
// Zig's std doesn't provide a cross platform abstraction for piping two commands together, so
|
|
1292
|
+
// we begrudgingly pass the data through this intermediary process.
|
|
1293
|
+
const shallow = try shell.exec_stdout("git rev-parse --is-shallow-repository", .{});
|
|
1294
|
+
if (!std.mem.eql(u8, shallow, "false")) {
|
|
1295
|
+
return error.ShallowRepository;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const rev_list = try shell.exec_stdout("git rev-list --objects HEAD", .{});
|
|
1299
|
+
const objects = try shell.exec_stdout_options(
|
|
1300
|
+
.{ .stdin_slice = rev_list },
|
|
1301
|
+
"git cat-file --batch-check={format}",
|
|
1302
|
+
.{ .format = "%(objecttype) %(objectsize) %(rest)" },
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
var has_large_blobs = false;
|
|
1306
|
+
var lines = std.mem.splitScalar(u8, objects, '\n');
|
|
1307
|
+
while (lines.next()) |line| {
|
|
1308
|
+
// Parsing lines like
|
|
1309
|
+
// blob 1032 client/package.json
|
|
1310
|
+
const blob = stdx.cut_prefix(line, "blob ") orelse continue;
|
|
1311
|
+
|
|
1312
|
+
const size_string, const path = stdx.cut(blob, " ").?;
|
|
1313
|
+
const size = try std.fmt.parseInt(u64, size_string, 10);
|
|
1314
|
+
|
|
1315
|
+
if (std.mem.eql(u8, path, "src/vsr/replica.zig")) continue; // :-)
|
|
1316
|
+
if (std.mem.eql(u8, path, "src/state_machine.zig")) continue; // :-|
|
|
1317
|
+
if (std.mem.eql(u8, path, "src/docs_website/package-lock.json")) continue; // :-(
|
|
1318
|
+
if (size > @divExact(MiB, 4)) {
|
|
1319
|
+
has_large_blobs = true;
|
|
1320
|
+
std.debug.print("{s}\n", .{line});
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (has_large_blobs) return error.HasLargeBlobs;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
test "tidy unix permissions" {
|
|
1327
|
+
const executable_files = [_][]const u8{
|
|
1328
|
+
"zig/download.ps1",
|
|
1329
|
+
"zig/download.sh",
|
|
1330
|
+
".github/ci/test_aof.sh",
|
|
1331
|
+
"src/scripts/cfo_supervisor.sh",
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
const allocator = std.testing.allocator;
|
|
1335
|
+
const shell = try Shell.create(allocator);
|
|
1336
|
+
defer shell.destroy();
|
|
1337
|
+
|
|
1338
|
+
const files = try shell.exec_stdout("git ls-files -z --format {format}", .{
|
|
1339
|
+
.format = "%(objectmode) %(path)",
|
|
1340
|
+
});
|
|
1341
|
+
assert(files[files.len - 1] == 0);
|
|
1342
|
+
var lines = std.mem.splitScalar(u8, files[0 .. files.len - 1], 0);
|
|
1343
|
+
while (lines.next()) |line| {
|
|
1344
|
+
const mode, const path = stdx.cut(line, " ").?;
|
|
1345
|
+
errdefer std.debug.print("{s}: error: unexpected mode={s}\n", .{ path, mode });
|
|
1346
|
+
|
|
1347
|
+
if (std.mem.eql(u8, mode, "100644")) {
|
|
1348
|
+
// Expected for most files.
|
|
1349
|
+
} else if (std.mem.eql(u8, mode, "100755")) {
|
|
1350
|
+
const expected = for (executable_files) |executable_file| {
|
|
1351
|
+
if (std.mem.eql(u8, path, executable_file)) break true;
|
|
1352
|
+
} else false;
|
|
1353
|
+
|
|
1354
|
+
if (!expected) return error.UnexpectedExecutable;
|
|
1355
|
+
} else {
|
|
1356
|
+
return error.UnexpectedMode;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// Sanity check for "unexpected" files in the repository.
|
|
1362
|
+
test "tidy extensions" {
|
|
1363
|
+
const allowed_extensions = std.StaticStringMap(void).initComptime(.{
|
|
1364
|
+
.{".c"}, .{".cs"}, .{".csproj"}, .{".css"}, .{".go"},
|
|
1365
|
+
.{".h"}, .{".hcl"}, .{".html"}, .{".java"}, .{".js"},
|
|
1366
|
+
.{".json"}, .{".md"}, .{".mod"}, .{".props"}, .{".py"},
|
|
1367
|
+
.{".rs"}, .{".service"}, .{".sln"}, .{".sum"}, .{".svg"},
|
|
1368
|
+
.{".toml"}, .{".ts"}, .{".txt"}, .{".xml"}, .{".yml"},
|
|
1369
|
+
.{".zig"}, .{".zon"},
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
const exceptions = std.StaticStringMap(void).initComptime(.{
|
|
1373
|
+
.{".editorconfig"},
|
|
1374
|
+
.{".gitignore"},
|
|
1375
|
+
.{".nojekyll"},
|
|
1376
|
+
.{"CNAME"},
|
|
1377
|
+
.{"exclude-pmd.properties"},
|
|
1378
|
+
.{"favicon.png"},
|
|
1379
|
+
.{"notfound-light.webp"},
|
|
1380
|
+
.{"notfound-dark.webp"},
|
|
1381
|
+
.{"preview.webp"},
|
|
1382
|
+
.{"LICENSE"},
|
|
1383
|
+
.{"module-info.test"},
|
|
1384
|
+
.{"anchor-links.lua"},
|
|
1385
|
+
.{"markdown-links.lua"},
|
|
1386
|
+
.{"table-wrapper.lua"},
|
|
1387
|
+
.{"code-block-buttons.lua"},
|
|
1388
|
+
.{"edit-link-footer.lua"},
|
|
1389
|
+
.{"src/docs_website/.vale.ini"},
|
|
1390
|
+
.{"zig/download.sh"},
|
|
1391
|
+
.{"zig/download.ps1"},
|
|
1392
|
+
.{"zig/download.win.ps1"},
|
|
1393
|
+
.{"src/scripts/cfo_supervisor.sh"},
|
|
1394
|
+
.{".github/ci/test_aof.sh"},
|
|
1395
|
+
.{"src/clients/python/pyproject.toml"},
|
|
1396
|
+
.{"src/clients/python/src/tigerbeetle/py.typed"},
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
const allocator = std.testing.allocator;
|
|
1400
|
+
const shell = try Shell.create(allocator);
|
|
1401
|
+
defer shell.destroy();
|
|
1402
|
+
|
|
1403
|
+
const paths = try list_file_paths(shell);
|
|
1404
|
+
|
|
1405
|
+
for (exceptions.keys()) |exception| {
|
|
1406
|
+
for (paths) |path| {
|
|
1407
|
+
const basename = std.fs.path.basename(path);
|
|
1408
|
+
if (std.mem.eql(u8, exception, basename) or std.mem.eql(u8, exception, path)) {
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
} else {
|
|
1412
|
+
std.debug.panic("exception (or basename) doesn't exist: {s} ({s})", .{
|
|
1413
|
+
exception,
|
|
1414
|
+
std.fs.path.basename(exception),
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
var bad_extension = false;
|
|
1420
|
+
for (paths) |path| {
|
|
1421
|
+
if (path.len == 0) continue;
|
|
1422
|
+
const extension = std.fs.path.extension(path);
|
|
1423
|
+
if (!allowed_extensions.has(extension)) {
|
|
1424
|
+
const basename = std.fs.path.basename(path);
|
|
1425
|
+
if (!exceptions.has(basename) and !exceptions.has(path)) {
|
|
1426
|
+
std.debug.print("bad extension: {s}\n", .{path});
|
|
1427
|
+
bad_extension = true;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
if (bad_extension) return error.BadExtension;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
/// Lists all files in the repository.
|
|
1435
|
+
fn list_file_paths(shell: *Shell) ![]const []const u8 {
|
|
1436
|
+
var result = std.ArrayList([]const u8).init(shell.arena.allocator());
|
|
1437
|
+
|
|
1438
|
+
const files = try shell.exec_stdout("git ls-files -z", .{});
|
|
1439
|
+
assert(files.len > 0);
|
|
1440
|
+
assert(files[files.len - 1] == 0);
|
|
1441
|
+
var lines = std.mem.splitScalar(u8, files[0 .. files.len - 1], 0);
|
|
1442
|
+
while (lines.next()) |line| {
|
|
1443
|
+
assert(line.len > 0);
|
|
1444
|
+
try result.append(line);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
return result.items;
|
|
1448
|
+
}
|