@@ -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+
608809const session = @import ("session.zig" );
609810const shims = @import ("shims.zig" );
610811const std = @import ("std" );
0 commit comments