Skip to content

Commit

Permalink
Revert "outsource linting to ziglint"
Browse files Browse the repository at this point in the history
This reverts commit 4f90d91.

Windows is failing due to not properly respective the gitignore,
likely due to the directory separator?
  • Loading branch information
scheibo committed Jul 9, 2023
1 parent 4f90d91 commit 2c13d3e
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 2 deletions.
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
# if: ${{ matrix.zig != 'local' }}
# with:
# version: ${{matrix.zig}}
- uses: AnnikaCodes/install-ziglint@v0.1
- run: npm install
- run: echo "${GITHUB_WORKSPACE}/build/bin/zig" >> $GITHUB_PATH
if: ${{ matrix.zig == 'local' && matrix.os != 'windows-latest' }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ generate:

.PHONY: zig-lint
zig-lint:
ziglint --exclude src/examples/zig/example.zig,src/lib/gen1/calc.zig
zig build lint

.PHONY: js-lint
js-lint: node_modules
Expand Down
6 changes: 6 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,15 @@ pub fn build(b: *std.Build) !void {
const serde = tool(b, "src/tools/serde.zig", tools);
const transitions = tool(b, "src/tools/transitions.zig", tools);

const lint_exe =
b.addExecutable(.{ .name = "lint", .root_source_file = .{ .path = "src/tools/lint.zig" } });
if (tests.build) tests.step.dependOn(&lint_exe.step);
const lint = b.addRunArtifact(lint_exe);

b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
b.step("lint", "Lint source files").dependOn(&lint.step);
b.step("serde", "Run serialization/deserialization tool").dependOn(&serde.step);
b.step("test", "Run all tests").dependOn(&tests.step);
b.step("tools", "Install tools").dependOn(&ToolsStep.create(b, &exes).step);
Expand Down
5 changes: 5 additions & 0 deletions src/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ This directory contains miscellaneous scripts and tools useful for working on th
The `--force` flag can be used to ensure that the data is refetched from the source instead of
from a local `.cache` directory.

- [`lint.zig`](lint.zig): Implements a linter, combining `zig fmt --check` with a custom linter
that ensures the maximum line length is 100 characters:

$ zig build lint

- [`dump.zig`](dump.zig): Print out offsets and constants required to properly encode and
decode the pkmn [protocol](../../docs/PROTOCOL.md):

Expand Down
160 changes: 160 additions & 0 deletions src/tools/lint.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
const std = @import("std");

const fs = std.fs;
const Allocator = std.mem.Allocator;

const PATH = "src";
const LINE_LENGTH = 100;

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

const build = try checkBuild(allocator);
const format = try checkFormat(PATH, allocator);
const lint = try lintDir(PATH, fs.cwd(), PATH, allocator);
std.process.exit(@intFromBool(build or format or lint));
}

fn checkBuild(allocator: Allocator) !bool {
if (try checkFormat("build.zig", allocator)) return true;
if (try lintFile("build.zig", fs.cwd(), "build.zig", allocator)) return true;
return false;
}

fn checkFormat(file_path: []const u8, allocator: Allocator) !bool {
const argv = &.{ "zig", "fmt", "--check", file_path };

var child = std.ChildProcess.init(argv, allocator);
const term = child.spawnAndWait() catch |err| {
const stderr = std.io.getStdErr().writer();
try stderr.print("Unable to spawn 'zig fmt': {s}\n", .{@errorName(err)});
return true;
};

switch (term) {
.Exited => |code| {
if (code != 0) {
const stderr = std.io.getStdErr().writer();
try stderr.print("'zig fmt' exited with error code {}:\n", .{code});
return true;
}
},
else => {
const stderr = std.io.getStdErr().writer();
try stderr.print("'zig fmt' exited unexpectedly\n", .{});
return true;
},
}
return false;
}

// Line length linting logic forked from coilhq/tigerbeetle's Apache-2 licensed scripts/lint.zig.
// The code has been modified specifically for the structure of this pkmn engine project with a
// hardcoded symlink skip, though also includes support for Windows paths and skipping entire files.
// The full license can be found at https://github.com/coilhq/tigerbeetle/blob/main/LICENSE

const Ignored = union(enum) { lines: []const u32, all };
const ignore = std.ComptimeStringMap(Ignored, .{
.{ "src/examples/zig/example.zig", .all },
.{ "src/lib/gen2/test.zig", .all },
.{ "src/lib/gen1/calc.zig", .all },
});

// Windows has trouble with the symlink so we need to hardcode the logic to skip it
const LIBS = "src\\examples\\zig\\lib";
const MODULES = "src\\examples\\js\\node_modules";

fn ignored(raw_path: []const u8, line: u32, allocator: Allocator) !bool {
var path = try allocator.dupe(u8, raw_path);
std.mem.replaceScalar(u8, path, fs.path.sep_windows, fs.path.sep_posix);
defer allocator.free(path);

const value = ignore.get(path) orelse return false;
switch (value) {
.lines => |lines| return std.mem.indexOfScalar(u32, lines, line) != null,
.all => return true,
}
}

var seen = std.AutoArrayHashMapUnmanaged(fs.File.INode, void){};

const LintError =
error{ OutOfMemory, NotUtf8 } || fs.File.OpenError || fs.File.ReadError || fs.File.WriteError;

fn lintDir(
file_path: []const u8,
parent_dir: fs.Dir,
parent_sub_path: []const u8,
allocator: Allocator,
) LintError!bool {
var err = false;

var iterable_dir = try parent_dir.openIterableDir(parent_sub_path, .{});
defer iterable_dir.close();

var dir = iterable_dir.dir;

const stat = try dir.stat();
if (try seen.fetchPut(allocator, stat.inode, {})) |_| return err;

var dir_it = iterable_dir.iterate();
while (try dir_it.next()) |entry| {
if (entry.kind == .sym_link) continue;

const is_dir = entry.kind == .directory;
if (is_dir and std.mem.eql(u8, entry.name, "zig-cache")) continue;

if (is_dir or std.mem.endsWith(u8, entry.name, ".zig")) {
const full_path = try fs.path.join(allocator, &[_][]const u8{ file_path, entry.name });
defer allocator.free(full_path);
if (std.mem.eql(u8, full_path, LIBS) or std.mem.eql(u8, full_path, MODULES)) continue;

var e = false;
if (is_dir) {
e = try lintDir(full_path, dir, entry.name, allocator);
} else {
e = try lintFile(full_path, dir, entry.name, allocator);
}
err = err or e;
}
}

return err;
}

fn lintFile(file_path: []const u8, dir: fs.Dir, sub_path: []const u8, allocator: Allocator) !bool {
const source_file = try dir.openFile(sub_path, .{});
defer source_file.close();

const source = try source_file.readToEndAllocOptions(
allocator,
std.math.maxInt(usize),
null,
@alignOf(u8),
0,
);

return lintLineLength(source, file_path, allocator);
}

fn lintLineLength(source: []const u8, path: []const u8, allocator: Allocator) !bool {
var err = false;
var i: usize = 0;
var line: u32 = 1;
while (std.mem.indexOfScalar(u8, source[i..], '\n')) |newline| : (line += 1) {
const line_length =
std.unicode.utf8CountCodepoints(source[i..][0..newline]) catch return error.NotUtf8;
if (line_length > LINE_LENGTH and !try ignored(path, line, allocator)) {
const stderr = std.io.getStdErr().writer();
try stderr.print(
"{s}:{d} has a length of {d}. Maximum allowed is 100\n",
.{ path, line, line_length },
);
err = true;
}
i += newline + 1;
}
return err;
}

0 comments on commit 2c13d3e

Please sign in to comment.