[pitboss/grind] deferred session-0001 (20260522T043516Z-29b8)

This commit is contained in:
pitboss 2026-05-21 23:58:51 -05:00
parent 3d8f988453
commit 945836bf88
5 changed files with 229 additions and 13 deletions

View file

@ -19,8 +19,8 @@ use crate::symbol::Lang;
use tree_sitter::Node;
use super::go_routes::{
bind_go_path_params, find_go_function, find_route_for_callee, go_formal_names,
source_imports_chi,
bind_go_path_params, collect_use_middleware, find_go_function, find_route_for_callee,
go_formal_names, source_imports_chi,
};
pub struct GoChiAdapter;
@ -52,13 +52,14 @@ impl FrameworkAdapter for GoChiAdapter {
bind_go_path_params(&formals, &path)
})
.unwrap_or_default();
let middleware = collect_use_middleware(ast, file_bytes);
Some(FrameworkBinding {
adapter: ADAPTER_NAME.to_owned(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape { method, path }),
request_params,
response_writer: None,
middleware: Vec::new(),
middleware,
})
}
}
@ -115,6 +116,19 @@ mod tests {
assert!(matches!(id.source, ParamSource::PathSegment(_)));
}
#[test]
fn populates_middleware_from_with_chain() {
let src: &[u8] = b"package main\nimport (\"net/http\"; \"github.com/go-chi/chi/v5\")\n\
func init() { r := chi.NewRouter(); r.With(jwtauth.Verifier).Get(\"/users/{id}\", Show) }\n\
func Show(w http.ResponseWriter, r *http.Request) {}\n";
let tree = parse(src);
let binding = GoChiAdapter
.detect(&summary("Show"), tree.root_node(), src)
.expect("binding");
assert_eq!(binding.middleware.len(), 1);
assert_eq!(binding.middleware[0].name, "jwtauth.Verifier");
}
#[test]
fn skips_when_chi_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";

View file

@ -19,8 +19,8 @@ use crate::symbol::Lang;
use tree_sitter::Node;
use super::go_routes::{
bind_go_path_params, find_go_function, find_route_for_callee, go_formal_names,
source_imports_echo,
bind_go_path_params, collect_use_middleware, find_go_function, find_route_for_callee,
go_formal_names, source_imports_echo,
};
pub struct GoEchoAdapter;
@ -52,13 +52,14 @@ impl FrameworkAdapter for GoEchoAdapter {
bind_go_path_params(&formals, &path)
})
.unwrap_or_default();
let middleware = collect_use_middleware(ast, file_bytes);
Some(FrameworkBinding {
adapter: ADAPTER_NAME.to_owned(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape { method, path }),
request_params,
response_writer: None,
middleware: Vec::new(),
middleware,
})
}
}
@ -116,6 +117,19 @@ mod tests {
assert_eq!(binding.route.unwrap().method, HttpMethod::PUT);
}
#[test]
fn populates_middleware_from_use_calls() {
let src: &[u8] = b"package main\nimport \"github.com/labstack/echo/v4\"\n\
func init() { e := echo.New(); e.Use(middleware.JWT); e.GET(\"/u/:id\", Show) }\n\
func Show(c echo.Context, id string) error { return nil }\n";
let tree = parse(src);
let binding = GoEchoAdapter
.detect(&summary("Show"), tree.root_node(), src)
.expect("binding");
assert_eq!(binding.middleware.len(), 1);
assert_eq!(binding.middleware[0].name, "middleware.JWT");
}
#[test]
fn skips_when_echo_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";

View file

@ -20,8 +20,8 @@ use crate::symbol::Lang;
use tree_sitter::Node;
use super::go_routes::{
bind_go_path_params, find_go_function, find_route_for_callee, go_formal_names,
source_imports_fiber,
bind_go_path_params, collect_use_middleware, find_go_function, find_route_for_callee,
go_formal_names, source_imports_fiber,
};
pub struct GoFiberAdapter;
@ -53,13 +53,14 @@ impl FrameworkAdapter for GoFiberAdapter {
bind_go_path_params(&formals, &path)
})
.unwrap_or_default();
let middleware = collect_use_middleware(ast, file_bytes);
Some(FrameworkBinding {
adapter: ADAPTER_NAME.to_owned(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape { method, path }),
request_params,
response_writer: None,
middleware: Vec::new(),
middleware,
})
}
}
@ -122,6 +123,19 @@ mod tests {
assert!(matches!(rest.source, ParamSource::PathSegment(_)));
}
#[test]
fn populates_middleware_from_use_calls() {
let src: &[u8] = b"package main\nimport \"github.com/gofiber/fiber/v2\"\n\
func init() { app := fiber.New(); app.Use(csrf.New(secret)); app.Get(\"/u/:id\", Show) }\n\
func Show(c *fiber.Ctx, id string) error { return nil }\n";
let tree = parse(src);
let binding = GoFiberAdapter
.detect(&summary("Show"), tree.root_node(), src)
.expect("binding");
assert_eq!(binding.middleware.len(), 1);
assert_eq!(binding.middleware[0].name, "csrf.New");
}
#[test]
fn skips_when_fiber_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";

View file

@ -22,8 +22,8 @@ use crate::symbol::Lang;
use tree_sitter::Node;
use super::go_routes::{
bind_go_path_params, find_go_function, find_route_for_callee, go_formal_names,
source_imports_gin,
bind_go_path_params, collect_use_middleware, find_go_function, find_route_for_callee,
go_formal_names, source_imports_gin,
};
pub struct GoGinAdapter;
@ -55,13 +55,14 @@ impl FrameworkAdapter for GoGinAdapter {
bind_go_path_params(&formals, &path)
})
.unwrap_or_default();
let middleware = collect_use_middleware(ast, file_bytes);
Some(FrameworkBinding {
adapter: ADAPTER_NAME.to_owned(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape { method, path }),
request_params,
response_writer: None,
middleware: Vec::new(),
middleware,
})
}
}
@ -143,6 +144,19 @@ mod tests {
);
}
#[test]
fn populates_middleware_from_use_calls() {
let src: &[u8] = b"package main\nimport \"github.com/gin-gonic/gin\"\n\
func init() { r := gin.Default(); r.Use(AuthMiddleware); r.GET(\"/u/:id\", Show) }\n\
func Show(c *gin.Context, id string) {}\n";
let tree = parse(src);
let binding = GoGinAdapter
.detect(&summary("Show"), tree.root_node(), src)
.expect("binding");
assert_eq!(binding.middleware.len(), 1);
assert_eq!(binding.middleware[0].name, "AuthMiddleware");
}
#[test]
fn fires_on_marker_comment() {
let src: &[u8] =

View file

@ -16,7 +16,9 @@
//!
//! [`extract_go_path_placeholders`] supports both syntaxes.
use crate::dynamic::framework::{HttpMethod, ParamBinding, ParamSource};
use crate::dynamic::framework::auth_markers;
use crate::dynamic::framework::{HttpMethod, MiddlewareShape, ParamBinding, ParamSource};
use crate::symbol::Lang;
use tree_sitter::Node;
/// True when `bytes` carries any of the well-known gin markers.
@ -245,6 +247,98 @@ pub fn verb_from_method(method: &str) -> Option<HttpMethod> {
}
}
/// Walk every `receiver.Use(...)` / `receiver.With(...)` call in the
/// file and collect the argument expressions whose names match a
/// known Go middleware marker (see
/// [`crate::dynamic::framework::auth_markers::is_protective`]).
///
/// gin / echo / fiber attach middleware via `r.Use(mw1, mw2, ...)`;
/// chi attaches middleware inline via `r.Use(...)` or
/// `r.With(...).Get(...)`. Both verbs are accepted; mid-chain
/// `.With(...)` calls that follow a verb-method call (`.Get(...)`
/// returns no router so chained `.With` is impossible) are
/// conservatively still collected because tree-sitter has already
/// flattened the chain into the same `call_expression` shape.
///
/// Argument rendering:
/// - bare identifier (`r.Use(authMiddleware)`) → `"authMiddleware"`
/// - selector expression (`r.Use(middleware.JWT)`) →
/// `"middleware.JWT"`
/// - call expression (`r.Use(csrf.New(secret))`) → `"csrf.New"`
/// (callee text, args dropped — the auth-markers table is keyed
/// on the factory function, not the constructed instance)
///
/// De-duplicates within a single file; preserves declaration order.
/// Names the registry does not recognise are dropped silently —
/// callers can re-walk with a wider predicate if they need broader
/// inclusion.
pub fn collect_use_middleware(root: Node<'_>, bytes: &[u8]) -> Vec<MiddlewareShape> {
let mut raw: Vec<String> = Vec::new();
walk_use_calls(root, bytes, &mut raw);
let mut out: Vec<MiddlewareShape> = Vec::new();
for name in raw {
if auth_markers::is_protective(Lang::Go, &name)
&& !out.iter().any(|m| m.name == name)
{
out.push(MiddlewareShape { name });
}
}
out
}
fn walk_use_calls(node: Node<'_>, bytes: &[u8], out: &mut Vec<String>) {
if node.kind() == "call_expression" {
try_collect_use_call(node, bytes, out);
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk_use_calls(child, bytes, out);
}
}
fn try_collect_use_call(call: Node<'_>, bytes: &[u8], out: &mut Vec<String>) {
let Some(callee) = call.child_by_field_name("function") else {
return;
};
if callee.kind() != "selector_expression" {
return;
}
let Some(field) = callee.child_by_field_name("field") else {
return;
};
let Ok(verb) = field.utf8_text(bytes) else {
return;
};
if verb != "Use" && verb != "With" {
return;
}
let Some(args) = call.child_by_field_name("arguments") else {
return;
};
let mut cur = args.walk();
for arg in args.named_children(&mut cur) {
if arg.kind() == "comment" {
continue;
}
if let Some(name) = middleware_arg_name(arg, bytes) {
out.push(name);
}
}
}
fn middleware_arg_name(node: Node<'_>, bytes: &[u8]) -> Option<String> {
match node.kind() {
"identifier" | "selector_expression" => {
node.utf8_text(bytes).ok().map(|s| s.trim().to_owned())
}
"call_expression" => {
let callee = node.child_by_field_name("function")?;
callee.utf8_text(bytes).ok().map(|s| s.trim().to_owned())
}
_ => None,
}
}
/// Locate the `(method, path)` of a `receiver.Verb("/path", target)`
/// call expression registered against `target` in the file. Walks
/// every `call_expression` in `root` and inspects each one whose
@ -443,4 +537,70 @@ mod tests {
let names = go_formal_names(f, src);
assert_eq!(names, vec!["c", "id"]);
}
#[test]
fn collect_use_middleware_picks_bare_identifier() {
let src: &[u8] = b"package main\nfunc init() { r := gin.Default(); r.Use(AuthMiddleware) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert_eq!(mw.len(), 1);
assert_eq!(mw[0].name, "AuthMiddleware");
}
#[test]
fn collect_use_middleware_picks_selector_marker() {
let src: &[u8] =
b"package main\nfunc init() { e := echo.New(); e.Use(middleware.JWT) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert_eq!(mw.len(), 1);
assert_eq!(mw[0].name, "middleware.JWT");
}
#[test]
fn collect_use_middleware_picks_call_factory() {
let src: &[u8] =
b"package main\nfunc init() { r := chi.NewRouter(); r.Use(csrf.New(secret)) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert_eq!(mw.len(), 1);
assert_eq!(mw[0].name, "csrf.New");
}
#[test]
fn collect_use_middleware_accepts_chi_with() {
let src: &[u8] =
b"package main\nfunc init() { r := chi.NewRouter(); r.With(jwtauth.Verifier).Get(\"/x\", Show) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert_eq!(mw.len(), 1);
assert_eq!(mw[0].name, "jwtauth.Verifier");
}
#[test]
fn collect_use_middleware_drops_unknown_names() {
let src: &[u8] =
b"package main\nfunc init() { r := gin.Default(); r.Use(loggingHandler) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert!(mw.is_empty(), "loggingHandler is not a recognised marker");
}
#[test]
fn collect_use_middleware_dedupes_and_collects_multiple() {
let src: &[u8] = b"package main\nfunc init() { r := gin.Default(); r.Use(AuthMiddleware, csrf.New(s)); r.Use(AuthMiddleware) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
let names: Vec<&str> = mw.iter().map(|m| m.name.as_str()).collect();
assert_eq!(names, vec!["AuthMiddleware", "csrf.New"]);
}
#[test]
fn collect_use_middleware_returns_empty_when_none_recognised() {
let src: &[u8] =
b"package main\nfunc init() { r := gin.Default(); r.GET(\"/x\", Show) }\n";
let tree = parse(src);
let mw = collect_use_middleware(tree.root_node(), src);
assert!(mw.is_empty());
}
}