Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const std = @import("std");
const Io = std.Io;

const rules = @import("rules/_list.zig");
pub const rules = @import("rules/_list.zig");

var io: Io = undefined;
var allocator: std.mem.Allocator = undefined;
pub var allocator: std.mem.Allocator = undefined;
Comment thread
IntegratedQuantum marked this conversation as resolved.

var failed: bool = false;

Expand Down
1 change: 1 addition & 0 deletions src/rules/_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub const imports = @import("imports.zig");
pub const tab_indentation = @import("tab_indentation.zig");
pub const trailing_space = @import("trailing_space.zig");
pub const orelse_unreachable = @import("orelse_unreachable.zig");
pub const unused_private_decls = @import("unused_private_decls.zig");
83 changes: 42 additions & 41 deletions src/rules/imports.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,53 +29,54 @@ fn isAliasAllowed(_importName: []const u8, _aliasName: []const u8) bool {
return std.mem.eql(u8, importName, aliasName);
}

pub fn isImport(ctx: main.Context, ast: std.zig.Ast, node: std.zig.Ast.Node.Index, mode: enum { errorOnAliasNameMismatch, ignoreAliasNameMismatch }) bool {
if (ast.nodeTag(node) != .simple_var_decl) return false;
const varDec = ast.simpleVarDecl(node);
const aliasName = ast.tokenSlice(varDec.ast.mut_token + 1);
const rhsNode = varDec.ast.init_node.unwrap() orelse return false;

switch (ast.nodeTag(rhsNode)) {
.builtin_call_two_comma => { // @import("x",)
const token = ast.nodeMainToken(rhsNode);
const importKeyword = ast.tokenSlice(token);
if (!std.mem.eql(u8, importKeyword, "@import")) return false;
ctx.printError("@import should not have a trailing comma.", ast.tokenStart(token));
return true;
},
.builtin_call_two => { // @import("x")
const importKeyword = ast.tokenSlice(ast.nodeMainToken(rhsNode));
if (!std.mem.eql(u8, importKeyword, "@import")) return false;

const token = ast.nodeMainToken(ast.nodeData(rhsNode).opt_node_and_opt_node[0].unwrap().?);
var importName = ast.tokenSlice(token);
importName = importName[1 .. importName.len - 1];

if (!isAliasAllowed(importName, aliasName)) {
ctx.printError("Encountered import with mismatched name", ast.tokenStart(token));
}
return true;
},
.field_access => { // alias
const token = ast.nodeData(rhsNode).node_and_token[1];
const importName = ast.tokenSlice(token);

if (!isAliasAllowed(importName, aliasName)) {
if (mode == .ignoreAliasNameMismatch) return false;
ctx.printError("Encountered alias with mismatched name", ast.tokenStart(token));
}
return true;
},
else => return false,
}
}

pub fn check(ctx: main.Context) void {
const ast = ctx.ast orelse return;
const root = ast.rootDecls();
var firstNonImportNode: ?std.zig.Ast.Node.Index = null;

for (root) |node| {
const isImport: bool = blk: {
if (ast.nodeTag(node) != .simple_var_decl) break :blk false;
const varDec = ast.simpleVarDecl(node);
const aliasName = ast.tokenSlice(varDec.ast.mut_token + 1);
const rhsNode = varDec.ast.init_node.unwrap().?;

switch (ast.nodeTag(rhsNode)) {
.builtin_call_two_comma => { // @import("x",)
const token = ast.nodeMainToken(rhsNode);
const importKeyword = ast.tokenSlice(token);
if (!std.mem.eql(u8, importKeyword, "@import")) break :blk false;
ctx.printError("@import should not have a trailing comma.", ast.tokenStart(token));
break :blk true;
},
.builtin_call_two => { // @import("x")
const importKeyword = ast.tokenSlice(ast.nodeMainToken(rhsNode));
if (!std.mem.eql(u8, importKeyword, "@import")) break :blk false;

const token = ast.nodeMainToken(ast.nodeData(rhsNode).opt_node_and_opt_node[0].unwrap().?);
var importName = ast.tokenSlice(token);
importName = importName[1 .. importName.len - 1];

if (!isAliasAllowed(importName, aliasName)) {
ctx.printError("Encountered import with mismatched name", ast.tokenStart(token));
}
break :blk true;
},
.field_access => { // alias
const token = ast.nodeData(rhsNode).node_and_token[1];
const importName = ast.tokenSlice(token);

if (!isAliasAllowed(importName, aliasName)) {
if (firstNonImportNode != null) break :blk false;
ctx.printError("Encountered alias with mismatched name", ast.tokenStart(token));
}
break :blk true;
},
else => break :blk false,
}
};
if (isImport) {
if (isImport(ctx, ast, node, if (firstNonImportNode != null) .ignoreAliasNameMismatch else .errorOnAliasNameMismatch)) {
if (firstNonImportNode) |nonImportNode| {
ctx.printError("Encountered import/alias after import section", ast.tokenStart(ast.firstToken(node)));
ctx.printInfo("determined end of import section", ast.tokenStart(ast.firstToken(nonImportNode)));
Expand Down
63 changes: 63 additions & 0 deletions src/rules/unused_private_decls.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const std = @import("std");

const main = @import("main");

pub fn check(ctx: main.Context) void {
const ast = ctx.ast orelse return;

var identifiers: std.StringHashMapUnmanaged(void) = .empty;
defer identifiers.deinit(main.allocator);

identifiers.put(main.allocator, "std", {}) catch @panic("OOM");
identifiers.put(main.allocator, "main", {}) catch @panic("OOM");

@Wunka Wunka Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
identifiers.put(main.allocator, "std", {}) catch @panic("OOM");
identifiers.put(main.allocator, "main", {}) catch @panic("OOM");

Without this, this also works.
Else you would have to add every import we ever do. Like c etc

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, now that I ignore all imports anyways this is no longer needed


for (0..ast.nodes.len) |nodeIndex| {
const node: std.zig.Ast.Node.Index = @enumFromInt(nodeIndex);
switch (ast.nodeTag(node)) {
.identifier => {
_ = identifiers.getOrPut(main.allocator, ast.getNodeSource(node)) catch @panic("OOM");
},
.field_access => {
const token = ast.nodeData(node).node_and_token[1];
const name = ast.tokenSlice(token);
_ = identifiers.getOrPut(main.allocator, name) catch @panic("OOM");
},
.enum_literal => {
const name = ast.tokenSlice(ast.nodeMainToken(node));
_ = identifiers.getOrPut(main.allocator, name) catch @panic("OOM");
},
else => continue,
}
}

for (0..ast.nodes.len) |nodeIndex| {
const node: std.zig.Ast.Node.Index = @enumFromInt(nodeIndex);
switch (ast.nodeTag(node)) {
.simple_var_decl => {
if (main.rules.imports.isImport(ctx, ast, node, .ignoreAliasNameMismatch)) continue;
const varDec = ast.simpleVarDecl(node);
if (varDec.visib_token != null) continue;
const name = ast.tokenSlice(varDec.ast.mut_token + 1);
if (!identifiers.contains(name)) {
ctx.printError("Unused declaration", ast.tokenStart(varDec.firstToken()));
}
},
.fn_proto, .fn_proto_simple, .fn_proto_one, .fn_proto_multi => |tag| {
var buffer: [1]std.zig.Ast.Node.Index = undefined;
const functionProto = switch (tag) {
.fn_proto => ast.fnProto(node),
.fn_proto_simple => ast.fnProtoSimple(&buffer, node),
.fn_proto_one => ast.fnProtoOne(&buffer, node),
.fn_proto_multi => ast.fnProtoMulti(node),
else => unreachable,
};
if (functionProto.visib_token != null) continue;
const name = ast.tokenSlice(functionProto.name_token orelse continue);
if (!identifiers.contains(name)) {
ctx.printError("Unused declaration", ast.tokenStart(functionProto.firstToken()));
}
},
else => continue,
}
}
}
Loading