Skip to content

Commit 3ab06a9

Browse files
committed
Move some methods from require_errdefer_dealloc into ast module
1 parent 2330af0 commit 3ab06a9

File tree

3 files changed

+250
-170
lines changed

3 files changed

+250
-170
lines changed

src/lib/ast.zig

Lines changed: 203 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ test "deferBlock - has expected children" {
213213
defer _ = arena.reset(.retain_capacity);
214214

215215
var ctx: session.LintContext = undefined;
216-
try ctx.init(.{}, std.testing.allocator);
216+
try ctx.init(.{}, arena.allocator());
217217
defer ctx.deinit();
218218

219219
var tmp = std.testing.tmpDir(.{});
@@ -226,7 +226,7 @@ test "deferBlock - has expected children" {
226226
"fn main() void {\n" ++ source ++ "\n}",
227227
arena.allocator(),
228228
)).?;
229-
defer doc.deinit(ctx.gpa);
229+
defer doc.deinit(arena.allocator());
230230

231231
const decl_ref = try deferBlock(
232232
doc,
@@ -605,6 +605,207 @@ test "isEnumLiteral" {
605605
}
606606
}
607607

608+
/// Checks whether the current node is a function call or contains one in its
609+
/// children.
610+
pub fn findFnCall(
611+
doc: session.LintDocument,
612+
node: Ast.Node.Index,
613+
call_buffer: *[1]Ast.Node.Index,
614+
comptime names: []const []const u8,
615+
) ?FnCall {
616+
if (fnCall(
617+
doc,
618+
node,
619+
call_buffer,
620+
names,
621+
)) |call| return call;
622+
623+
for (doc.lineage.items(.children)[shims.NodeIndexShim.init(node).index] orelse &.{}) |child| {
624+
if (findFnCall(
625+
doc,
626+
child,
627+
call_buffer,
628+
names,
629+
)) |call| return call;
630+
}
631+
return null;
632+
}
633+
634+
pub const FnCall = struct {
635+
params: []const Ast.Node.Index,
636+
637+
kind: union(enum) {
638+
/// e.g., `parent.call()` not `parent.child.call()`
639+
single_field: struct {
640+
/// e.g., `parent.call()` would have `parent` as the main token here.
641+
field_main_token: Ast.TokenIndex,
642+
/// e.g., `parent.call()` would have `call` as the identifier token here.
643+
call_identifier_token: Ast.TokenIndex,
644+
},
645+
/// array_access, unwrap_optional, nested field_access
646+
///
647+
/// e.g., `parent.child.call()`, `optional.?.call()` and `array[0].call()`
648+
///
649+
/// If there's value this can be broken up in the future but for now we do
650+
/// not need the separation.
651+
other: struct {
652+
/// e.g., `parent.child.call()` would have `call` as the identifier token here.
653+
call_identifier_token: Ast.TokenIndex,
654+
},
655+
/// e.g., `.init()`
656+
enum_literal: struct {
657+
/// e.g., `.init()` would have `init` here
658+
call_identifier_token: Ast.TokenIndex,
659+
},
660+
/// e.g., `doSomething()`
661+
direct: struct {
662+
/// e.g., `doSomething()` would have `doSomething` here
663+
call_identifier_token: Ast.TokenIndex,
664+
},
665+
},
666+
};
667+
668+
/// Returns call information for cases handled by the `require_errdefer_dealloc`
669+
/// Not all calls are handled so this method is not generally useful
670+
///
671+
/// If names is empty, then it'll match all function names.
672+
pub fn fnCall(
673+
doc: session.LintDocument,
674+
node: Ast.Node.Index,
675+
buffer: *[1]Ast.Node.Index,
676+
comptime names: []const []const u8,
677+
) ?FnCall {
678+
const tree = doc.handle.tree;
679+
680+
const call = tree.fullCall(buffer, node) orelse return null;
681+
682+
const fn_expr_node = call.ast.fn_expr;
683+
const fn_expr_node_data = shims.nodeData(tree, fn_expr_node);
684+
const fn_expr_node_tag = shims.nodeTag(tree, fn_expr_node);
685+
686+
switch (fn_expr_node_tag) {
687+
// e.g., `parent.*`
688+
.field_access => {
689+
const field_node, const fn_name = switch (version.zig) {
690+
.@"0.14" => .{ fn_expr_node_data.lhs, fn_expr_node_data.rhs },
691+
.@"0.15", .@"0.16" => .{ fn_expr_node_data.node_and_token[0], fn_expr_node_data.node_and_token[1] },
692+
};
693+
std.debug.assert(shims.tokenTag(tree, fn_name) == .identifier);
694+
695+
if (names.len > 0) {
696+
var match: bool = false;
697+
const fn_name_slice = tree.tokenSlice(fn_name);
698+
for (names) |name| {
699+
if (std.mem.eql(u8, name, fn_name_slice)) {
700+
match = true;
701+
break;
702+
}
703+
}
704+
if (!match) return null;
705+
}
706+
707+
const field_node_tag = shims.nodeTag(tree, field_node);
708+
if (field_node_tag != .identifier) {
709+
// e.g, array_access, unwrap_optional, field_access
710+
return .{
711+
.params = call.ast.params,
712+
.kind = .{
713+
.other = .{
714+
.call_identifier_token = fn_name,
715+
},
716+
},
717+
};
718+
}
719+
// e.g., `parent.call()` not `parent.child.call()`
720+
return .{
721+
.params = call.ast.params,
722+
.kind = .{
723+
.single_field = .{
724+
.field_main_token = shims.nodeMainToken(tree, field_node),
725+
.call_identifier_token = fn_name,
726+
},
727+
},
728+
};
729+
},
730+
// e.g., `.init()`
731+
.enum_literal => {
732+
const fn_name = shims.nodeMainToken(tree, fn_expr_node);
733+
std.debug.assert(shims.tokenTag(tree, fn_name) == .identifier);
734+
735+
const identfier_slice = tree.tokenSlice(fn_name);
736+
737+
if (names.len > 0) {
738+
for (names) |name| {
739+
if (std.mem.eql(u8, name, identfier_slice)) {
740+
return .{
741+
.params = call.ast.params,
742+
.kind = .{
743+
.enum_literal = .{
744+
.call_identifier_token = fn_name,
745+
},
746+
},
747+
};
748+
}
749+
}
750+
} else {
751+
return .{
752+
.params = call.ast.params,
753+
.kind = .{
754+
.enum_literal = .{
755+
.call_identifier_token = fn_name,
756+
},
757+
},
758+
};
759+
}
760+
},
761+
.identifier => {
762+
return .{
763+
.params = call.ast.params,
764+
.kind = .{
765+
.direct = .{
766+
.call_identifier_token = shims.nodeMainToken(tree, fn_expr_node),
767+
},
768+
},
769+
};
770+
},
771+
else => std.log.debug("fnCall does not handle fn_expr of tag {s}", .{@tagName(fn_expr_node_tag)}),
772+
}
773+
774+
return null;
775+
}
776+
777+
// test "fnCall - direct call without params" {
778+
// var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
779+
// defer arena.deinit();
780+
781+
// const doc = (try testing.initDocForTesting(
782+
// \\fn main() void {
783+
// \\ call();
784+
// \\}
785+
// ,
786+
// arena.allocator(),
787+
// .{},
788+
// )).*;
789+
790+
// const fn_node = try testing.expectSingleNodeOfTag(
791+
// doc.handle.tree,
792+
// &.{ .call, .call_comma, .call_one, .call_one_comma },
793+
// );
794+
// var buffer: [1]Ast.Node.Index = undefined;
795+
// const call = fnCall(
796+
// doc,
797+
// fn_node,
798+
// &buffer,
799+
// &.{},
800+
// ).?;
801+
802+
// try std.testing.expectEqualDeep(&.{}, call.params);
803+
// try std.testing.expectEqualStrings(
804+
// "call",
805+
// doc.handle.tree.tokenSlice(call.kind.direct.call_identifier_token),
806+
// );
807+
// }
808+
608809
const session = @import("session.zig");
609810
const shims = @import("shims.zig");
610811
const std = @import("std");

src/lib/testing.zig

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
//! Test only utilities
22

33
/// See `runRule` for example (test only)
4-
pub fn loadFakeDocument(ctx: *LintContext, dir: std.fs.Dir, file_name: []const u8, contents: [:0]const u8, arena: std.mem.Allocator) !?LintDocument {
4+
pub fn loadFakeDocument(
5+
ctx: *LintContext,
6+
dir: std.fs.Dir,
7+
file_name: []const u8,
8+
contents: [:0]const u8,
9+
arena: std.mem.Allocator,
10+
) !?LintDocument {
511
assertTestOnly();
612

713
if (std.fs.path.dirname(file_name)) |dir_name|
@@ -342,10 +348,43 @@ pub fn testRunRule(
342348
try expectDeepEquals(LintProblemExpectation, expected, actual.items);
343349
}
344350

351+
/// WIP: Initializes a context and document for testing only.
352+
pub fn initDocForTesting(
353+
source: [:0]const u8,
354+
arena: std.mem.Allocator,
355+
options: struct {},
356+
) !*LintDocument {
357+
_ = options;
358+
359+
var ctx: *LintContext = try arena.create(LintContext);
360+
errdefer arena.destroy(ctx);
361+
362+
ctx.* = undefined;
363+
try ctx.init(.{}, arena);
364+
errdefer ctx.deinit();
365+
366+
var tmp = std.testing.tmpDir(.{});
367+
defer tmp.cleanup();
368+
369+
const doc = try arena.create(LintDocument);
370+
errdefer arena.destroy(doc);
371+
372+
doc.* = (try loadFakeDocument(
373+
ctx,
374+
tmp.dir,
375+
"test.zig",
376+
source,
377+
arena,
378+
)).?;
379+
380+
return doc;
381+
}
382+
345383
const builtin = @import("builtin");
346384
const std = @import("std");
347-
const LintContext = @import("session.zig").LintContext;
348-
const LintDocument = @import("session.zig").LintDocument;
385+
const session = @import("session.zig");
386+
const LintContext = session.LintContext;
387+
const LintDocument = session.LintDocument;
349388
const LintRule = @import("rules.zig").LintRule;
350389
const LintProblemSeverity = @import("rules.zig").LintProblemSeverity;
351390
const LintProblem = @import("results.zig").LintProblem;

0 commit comments

Comments
 (0)