cargo fmt

This commit is contained in:
elipeter 2026-05-21 14:35:42 -05:00
parent bec7bbf96c
commit 3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions

View file

@ -119,8 +119,10 @@ mod tests {
fn skips_when_chi_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoChiAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoChiAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -120,8 +120,10 @@ mod tests {
fn skips_when_echo_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoEchoAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoEchoAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -126,8 +126,10 @@ mod tests {
fn skips_when_fiber_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoFiberAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoFiberAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -124,9 +124,11 @@ mod tests {
fn skips_when_gin_not_imported() {
let src: &[u8] = b"package main\nfunc Show(id string) {}\n";
let tree = parse(src);
assert!(GoGinAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoGinAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -134,9 +136,11 @@ mod tests {
let src: &[u8] =
b"package main\nimport \"github.com/gin-gonic/gin\"\nfunc init() { r := gin.Default(); r.GET(\"/users\", Show) }\nfunc Helper(x string) {}\n";
let tree = parse(src);
assert!(GoGinAdapter
.detect(&summary("Helper"), tree.root_node(), src)
.is_none());
assert!(
GoGinAdapter
.detect(&summary("Helper"), tree.root_node(), src)
.is_none()
);
}
#[test]

View file

@ -83,22 +83,13 @@ fn contains_any(haystack: &[u8], needles: &[&[u8]]) -> bool {
/// Find a top-level `function_declaration` or a `method_declaration`
/// whose name equals `target`. Returns the matching node.
pub fn find_go_function<'a>(
root: Node<'a>,
bytes: &'a [u8],
target: &str,
) -> Option<Node<'a>> {
pub fn find_go_function<'a>(root: Node<'a>, bytes: &'a [u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_go(root, bytes, target, &mut hit);
hit
}
fn walk_go<'a>(
node: Node<'a>,
bytes: &'a [u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_go<'a>(node: Node<'a>, bytes: &'a [u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -136,9 +127,10 @@ pub fn go_formal_names(func: Node<'_>, bytes: &[u8]) -> Vec<String> {
let mut pc = p.walk();
for c in p.named_children(&mut pc) {
if c.kind() == "identifier"
&& let Ok(text) = c.utf8_text(bytes) {
out.push(text.to_owned());
}
&& let Ok(text) = c.utf8_text(bytes)
{
out.push(text.to_owned());
}
}
}
out
@ -428,8 +420,7 @@ mod tests {
let src: &[u8] =
b"package main\nfunc init() { r := gin.New(); r.GET(\"/u/:id\", Show) }\nfunc Show(c interface{}) {}\n";
let tree = parse(src);
let (method, path) =
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
assert_eq!(method, HttpMethod::GET);
assert_eq!(path, "/u/:id");
}
@ -439,8 +430,7 @@ mod tests {
let src: &[u8] =
b"package main\nfunc init() { r := chi.NewRouter(); r.Get(\"/x\", controllers.Show) }\n";
let tree = parse(src);
let (method, path) =
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
assert_eq!(method, HttpMethod::GET);
assert_eq!(path, "/x");
}

View file

@ -133,9 +133,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Set")],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -146,9 +148,11 @@ mod tests {
name: "Add".into(),
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -174,9 +178,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -195,9 +201,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -213,8 +221,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -17,7 +17,15 @@ const ADAPTER_NAME: &str = "header-java";
fn callee_is_header_setter(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "setHeader" | "addHeader" | "setDateHeader" | "addDateHeader" | "setIntHeader" | "addIntHeader")
matches!(
last,
"setHeader"
| "addHeader"
| "setDateHeader"
| "addDateHeader"
| "setIntHeader"
| "addIntHeader"
)
}
fn source_imports_servlet(file_bytes: &[u8]) -> bool {
@ -110,9 +118,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -123,9 +133,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -143,8 +155,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -18,7 +18,10 @@ const ADAPTER_NAME: &str = "header-js";
fn callee_is_header_setter(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "setHeader" | "header" | "set" | "writeHead" | "append")
matches!(
last,
"setHeader" | "header" | "set" | "writeHead" | "append"
)
}
fn source_uses_node_http(file_bytes: &[u8]) -> bool {
@ -115,9 +118,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -128,9 +133,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -146,8 +153,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -106,9 +106,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("header")],
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -119,15 +121,16 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_value_url_encoded() {
let src: &[u8] =
b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
let src: &[u8] = b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
let tree = parse_php(src);
let summary = FuncSummary {
name: "run".into(),
@ -137,8 +140,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -21,7 +21,10 @@ fn callee_is_header_setter(name: &str) -> bool {
matches!(
last,
"__setitem__" | "set_header" | "setdefault" | "add_header" | "append"
) || matches!(name, "Response.headers.__setitem__" | "make_response" | "Response.headers.add")
) || matches!(
name,
"Response.headers.__setitem__" | "make_response" | "Response.headers.add"
)
}
fn source_imports_python_web(file_bytes: &[u8]) -> bool {
@ -116,9 +119,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("__setitem__")],
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -129,9 +134,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -149,8 +156,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -132,9 +132,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("set_header")],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -145,9 +147,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,9 +172,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -188,9 +194,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -207,8 +215,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -132,9 +132,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("insert")],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -145,9 +147,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -173,9 +177,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -193,9 +199,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -215,8 +223,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -90,8 +90,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(JavaDeserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaDeserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -45,10 +45,7 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> Option<String> {
hit
}
fn method_verb_and_path(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut hit: Option<(HttpMethod, String)> = None;
iter_annotations(method, bytes, |ann, name| {
if hit.is_some() {
@ -155,17 +152,21 @@ mod tests {
fn skips_non_micronaut_file() {
let src: &[u8] = b"@Controller\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaMicronautAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaMicronautAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_method_without_micronaut_verb() {
let src: &[u8] = b"import io.micronaut.http.annotation.Controller;\n@Controller(\"/api\")\npublic class V {\n public String helper() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaMicronautAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
JavaMicronautAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -39,17 +39,15 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> String {
let mut prefix = String::new();
iter_annotations(class, bytes, |ann, name| {
if name == "Path"
&& let Some(p) = annotation_string_arg(ann, bytes) {
prefix = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
prefix = p;
}
});
prefix
}
fn method_verb_and_path(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut verb: Option<HttpMethod> = None;
let mut path = String::new();
iter_annotations(method, bytes, |ann, name| {
@ -57,9 +55,10 @@ fn method_verb_and_path(
verb = Some(v);
}
if name == "Path"
&& let Some(p) = annotation_string_arg(ann, bytes) {
path = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
path = p;
}
});
Some((verb?, path))
}
@ -157,17 +156,21 @@ mod tests {
fn skips_non_quarkus_file() {
let src: &[u8] = b"@RestController\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaQuarkusAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaQuarkusAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_method_without_verb_annotation() {
let src: &[u8] = b"import jakarta.ws.rs.Path;\n@Path(\"/api\")\npublic class V {\n public String helper() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaQuarkusAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
JavaQuarkusAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -77,11 +77,7 @@ pub fn source_imports_micronaut(bytes: &[u8]) -> bool {
pub fn source_imports_servlet(bytes: &[u8]) -> bool {
let has_canonical = contains_any(
bytes,
&[
b"javax.servlet",
b"jakarta.servlet",
b"extends HttpServlet",
],
&[b"javax.servlet", b"jakarta.servlet", b"extends HttpServlet"],
);
if has_canonical {
return true;
@ -113,12 +109,7 @@ pub fn find_class_with_method<'a>(
hit
}
fn walk<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<(Node<'a>, Node<'a>)>,
) {
fn walk<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<(Node<'a>, Node<'a>)>) {
if out.is_some() {
return;
}
@ -126,21 +117,22 @@ fn walk<'a>(
&& let Some(body) = node
.child_by_field_name("body")
.or_else(|| named_child_of_kind(node, "class_body"))
{
let mut cur = body.walk();
for member in body.children(&mut cur) {
if member.kind() != "method_declaration" {
continue;
}
if let Some(name) = member
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target {
*out = Some((node, member));
return;
}
{
let mut cur = body.walk();
for member in body.children(&mut cur) {
if member.kind() != "method_declaration" {
continue;
}
if let Some(name) = member
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target
{
*out = Some((node, member));
return;
}
}
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, bytes, target, out);
@ -173,7 +165,10 @@ pub fn annotation_string_arg(ann: Node<'_>, bytes: &[u8]) -> Option<String> {
// Try `value = "…"` / `path = "…"` first so the keyword form is
// not accidentally captured by the bare-string scan.
for key in ["value", "path"] {
if let Some(start) = raw.find(&format!("{key} = ")).or_else(|| raw.find(&format!("{key}="))) {
if let Some(start) = raw
.find(&format!("{key} = "))
.or_else(|| raw.find(&format!("{key}=")))
{
let after = &raw[start..];
if let Some(open) = after.find('"') {
let rest = &after[open + 1..];
@ -300,16 +295,17 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'{'
&& let Some(end) = bytes[i + 1..].iter().position(|&b| b == b'}') {
let inner = &path[i + 1..i + 1 + end];
let inner_name = inner.split(':').next().unwrap_or(inner).trim();
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
if !name.is_empty() && !out.iter().any(|n| n == name) {
out.push(name.to_owned());
}
i += end + 2;
continue;
&& let Some(end) = bytes[i + 1..].iter().position(|&b| b == b'}')
{
let inner = &path[i + 1..i + 1 + end];
let inner_name = inner.split(':').next().unwrap_or(inner).trim();
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
if !name.is_empty() && !out.iter().any(|n| n == name) {
out.push(name.to_owned());
}
i += end + 2;
continue;
}
i += 1;
}
out
@ -469,8 +465,7 @@ mod tests {
#[test]
fn class_extends_detects_servlet() {
let src: &[u8] =
b"public class V extends HttpServlet { public void doGet() {} }\n";
let src: &[u8] = b"public class V extends HttpServlet { public void doGet() {} }\n";
let tree = parse(src);
let (class, _) = find_class_with_method(tree.root_node(), src, "doGet").unwrap();
assert!(class_extends(class, src, "HttpServlet"));

View file

@ -126,10 +126,12 @@ mod tests {
let route = binding.route.unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/admin");
assert!(binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -157,19 +159,24 @@ mod tests {
#[test]
fn skips_when_method_name_is_not_a_servlet_verb() {
let src: &[u8] = b"public class V extends HttpServlet { public void run(HttpServletRequest req) {} }\n";
let src: &[u8] =
b"public class V extends HttpServlet { public void run(HttpServletRequest req) {} }\n";
let tree = parse(src);
assert!(JavaServletAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
JavaServletAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_no_servlet_signature_markers() {
let src: &[u8] = b"public class V {\n public void doGet(String x) {}\n}\n";
let tree = parse(src);
assert!(JavaServletAdapter
.detect(&summary("doGet"), tree.root_node(), src)
.is_none());
assert!(
JavaServletAdapter
.detect(&summary("doGet"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -49,17 +49,15 @@ fn class_route_prefix(class: Node<'_>, bytes: &[u8]) -> String {
let mut prefix = String::new();
iter_annotations(class, bytes, |ann, name| {
if name == "RequestMapping"
&& let Some(p) = annotation_string_arg(ann, bytes) {
prefix = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
prefix = p;
}
});
prefix
}
fn method_route(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_route(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut hit: Option<(HttpMethod, String)> = None;
iter_annotations(method, bytes, |ann, name| {
if hit.is_some() {
@ -100,7 +98,10 @@ impl FrameworkAdapter for JavaSpringAdapter {
// Quarkus / JAX-RS files often re-use `@Path` but the brief
// routes those through `java-quarkus`; skip when the file
// looks like Quarkus and is not also a Spring controller.
if source_imports_quarkus(file_bytes) && !file_bytes.windows(15).any(|w| w == b"@RestController") && !file_bytes.windows(11).any(|w| w == b"@Controller") {
if source_imports_quarkus(file_bytes)
&& !file_bytes.windows(15).any(|w| w == b"@RestController")
&& !file_bytes.windows(11).any(|w| w == b"@Controller")
{
return None;
}
let (class, method) = find_class_with_method(ast, file_bytes, &summary.name)?;
@ -210,26 +211,32 @@ mod tests {
let src: &[u8] =
b"@RequestMapping(\"/api\")\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_quarkus_file() {
let src: &[u8] = b"import io.quarkus.runtime.Quarkus;\nimport jakarta.ws.rs.GET;\nimport jakarta.ws.rs.Path;\n@Path(\"/run\")\npublic class Q {\n @GET\n public String run() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_plain_function() {
let src: &[u8] = b"public class C { public int add(int a, int b) { return a + b; } }\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -123,9 +123,11 @@ mod tests {
let src: &[u8] = b"import org.thymeleaf.TemplateEngine;\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(body, null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[0]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -137,9 +139,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -149,9 +153,11 @@ mod tests {
let src: &[u8] = b"// org.thymeleaf.TemplateEngine is great\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(\"static\", null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[0]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -159,8 +165,10 @@ mod tests {
let src: &[u8] = b"import org.thymeleaf.TemplateEngine;\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(body, null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -107,10 +107,18 @@ mod tests {
let route = binding.route.as_ref().unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/users/:id");
assert!(binding.request_params.iter().any(|p| p.name == "req"
&& matches!(p.source, ParamSource::Implicit)));
assert!(binding.request_params.iter().any(|p| p.name == "res"
&& matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "req" && matches!(p.source, ParamSource::Implicit))
);
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "res" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -147,9 +155,11 @@ mod tests {
function handler(ctx) { ctx.body = 'ok'; }\n\
app.get('/x', handler);\n";
let tree = parse_js(src);
assert!(JsExpressAdapter
.detect(&summary("handler"), tree.root_node(), src)
.is_none());
assert!(
JsExpressAdapter
.detect(&summary("handler"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -159,8 +169,10 @@ mod tests {
function other(req, res) { res.send('x'); }\n\
app.get('/x', other);\n";
let tree = parse_js(src);
assert!(JsExpressAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none());
assert!(
JsExpressAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -148,8 +148,10 @@ mod tests {
function h(req, res) {}\n\
app.get('/x', h);\n";
let tree = parse_js(src);
assert!(JsFastifyAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none());
assert!(
JsFastifyAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -139,9 +139,11 @@ mod tests {
let src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -152,9 +154,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -162,9 +166,11 @@ mod tests {
let src: &[u8] = b"// uses Handlebars\nfunction render(body) {\n return Handlebars.compile(\"static\")({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -172,8 +178,10 @@ mod tests {
let src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -39,22 +39,13 @@ fn receiver_looks_like_koa(name: &str) -> bool {
/// that reference `target`. Returns the matched call node so callers
/// can stamp a middleware-shape binding when the verb-based dispatch
/// fails to fire.
fn find_use_middleware<'a>(
root: Node<'a>,
bytes: &[u8],
target: &str,
) -> Option<Node<'a>> {
fn find_use_middleware<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_for_use(root, bytes, target, &mut hit);
hit
}
fn walk_for_use<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_for_use<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -108,8 +99,7 @@ impl FrameworkAdapter for JsKoaAdapter {
.unwrap_or_default();
bind_path_params(&formals, path)
};
if let Some((method, path)) =
find_route_registration(ast, file_bytes, &summary.name, &recv)
if let Some((method, path)) = find_route_registration(ast, file_bytes, &summary.name, &recv)
{
let request_params = formals_for(&path);
return Some(FrameworkBinding {
@ -180,8 +170,12 @@ mod tests {
let route = binding.route.as_ref().unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/users/:id");
assert!(binding.request_params.iter().any(|p| p.name == "ctx"
&& matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "ctx" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -205,8 +199,10 @@ mod tests {
function h(req, res) {}\n\
router.get('/x', h);\n";
let tree = parse_js(src);
assert!(JsKoaAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none());
assert!(
JsKoaAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -84,8 +84,7 @@ fn detect_nest(
if !source_imports_nest(file_bytes) {
return None;
}
let (class_node, method_node) =
find_class_method(ast, file_bytes, &summary.name)?;
let (class_node, method_node) = find_class_method(ast, file_bytes, &summary.name)?;
let prefix = class_controller_prefix(class_node, file_bytes)?;
let (method, sub_path) = method_verb_and_path(method_node, file_bytes)?;
let full_path = join_paths(&prefix, &sub_path);
@ -213,10 +212,7 @@ fn class_controller_prefix(class_node: Node<'_>, bytes: &[u8]) -> Option<String>
/// with one of the Nest verb decorators (`@Get`, `@Post`, ...). The
/// `sub_path` is `""` when the decorator carries no argument
/// (`@Get()` mounts at the controller prefix root).
fn method_verb_and_path(
method_node: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method_node: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
const VERBS: &[&str] = &[
"Get", "Head", "Post", "Put", "Patch", "Delete", "Options", "All",
];
@ -461,8 +457,7 @@ mod tests {
fn parse_ts(src: &[u8]) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
let lang =
tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
let lang = tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
parser.set_language(&lang).unwrap();
parser.parse(src, None).unwrap()
}
@ -562,8 +557,10 @@ mod tests {
compute(x: number) { return x + 1; }\n\
}\n";
let tree = parse_ts(src);
assert!(TsNestAdapter
.detect(&summary("compute", "typescript"), tree.root_node(), src)
.is_none());
assert!(
TsNestAdapter
.detect(&summary("compute", "typescript"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -140,22 +140,13 @@ pub fn strip_quotes(raw: &str) -> &str {
/// arrow function whose binding name equals `target`. Returns the
/// `formal_parameters` (or `formal_parameter` for shorthand arrows)
/// node so callers can enumerate parameter names.
pub fn find_function_params<'a>(
root: Node<'a>,
bytes: &[u8],
target: &str,
) -> Option<Node<'a>> {
pub fn find_function_params<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_for_params(root, bytes, target, &mut hit);
hit
}
fn walk_for_params<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_for_params<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -311,15 +302,7 @@ pub fn bind_path_params(formals: &[String], path: &str) -> Vec<ParamBinding> {
fn is_implicit_formal(name: &str) -> bool {
matches!(
name,
"req"
| "request"
| "res"
| "response"
| "reply"
| "ctx"
| "context"
| "next"
| "done"
"req" | "request" | "res" | "response" | "reply" | "ctx" | "context" | "next" | "done"
)
}
@ -349,9 +332,7 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
b':' => {
let start = i + 1;
let mut j = start;
while j < bytes.len()
&& (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_')
{
while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
j += 1;
}
if j > start {
@ -456,10 +437,11 @@ fn walk_for_registration<'a>(
&& receiver_accepts(last_segment(object_text))
&& let Some(args) = node.child_by_field_name("arguments")
&& call_args_reference_target(args, bytes, target)
&& let Some(path) = first_string_arg(args, bytes) {
*out = Some((method, path));
return;
}
&& let Some(path) = first_string_arg(args, bytes)
{
*out = Some((method, path));
return;
}
// Fastify options-object: `fastify.route({ method, url, handler })`.
if prop_text == "route"
&& receiver_accepts(last_segment(object_text))
@ -507,11 +489,7 @@ pub fn first_string_arg(args: Node<'_>, bytes: &[u8]) -> Option<String> {
/// Parse a Fastify options-object call `fastify.route({ method, url,
/// handler })` returning the bound `(method, url)` when the
/// `handler:` property references `target`.
fn parse_options_route(
args: Node<'_>,
bytes: &[u8],
target: &str,
) -> Option<(HttpMethod, String)> {
fn parse_options_route(args: Node<'_>, bytes: &[u8], target: &str) -> Option<(HttpMethod, String)> {
let mut cur = args.walk();
for c in args.named_children(&mut cur) {
if c.kind() != "object" {
@ -525,7 +503,9 @@ fn parse_options_route(
if pair.kind() != "pair" {
continue;
}
let Some(key) = pair.child_by_field_name("key").and_then(|n| n.utf8_text(bytes).ok())
let Some(key) = pair
.child_by_field_name("key")
.and_then(|n| n.utf8_text(bytes).ok())
else {
continue;
};

View file

@ -35,7 +35,12 @@ fn source_imports_kafka(file_bytes: &[u8]) -> bool {
fn extract_topic(file_bytes: &[u8]) -> String {
let text = std::str::from_utf8(file_bytes).unwrap_or("");
for needle in ["topics = \"", "topics=\"", "topics = {\"", "subscribe(Arrays.asList(\""] {
for needle in [
"topics = \"",
"topics=\"",
"topics = {\"",
"subscribe(Arrays.asList(\"",
] {
if let Some(idx) = text.find(needle) {
let after = &text[idx + needle.len()..];
if let Some(end) = after.find('"') {

View file

@ -129,8 +129,10 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(KafkaPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
KafkaPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -173,9 +173,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -186,9 +188,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -203,8 +207,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -168,9 +168,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search_s")],
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -181,9 +183,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -198,8 +202,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search_s")],
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -205,9 +205,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -225,8 +227,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search")],
..Default::default()
};
assert!(LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -17,11 +17,7 @@ fn callee_is_django_middleware(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"process_request"
| "process_response"
| "process_view"
| "process_exception"
| "__call__"
"process_request" | "process_response" | "process_view" | "process_exception" | "__call__"
)
}

View file

@ -15,10 +15,7 @@ const ADAPTER_NAME: &str = "middleware-express";
fn callee_is_express(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"use" | "next" | "json" | "urlencoded" | "static"
)
matches!(last, "use" | "next" | "json" | "urlencoded" | "static")
}
fn source_imports_express(file_bytes: &[u8]) -> bool {
@ -27,11 +24,7 @@ fn source_imports_express(file_bytes: &[u8]) -> bool {
// import. Many non-middleware Express fixtures import the framework
// but never declare middleware; gating on the registration shape
// keeps the adapter focused on the function the brief targets.
const NEEDLES: &[&[u8]] = &[
b"app.use(",
b"router.use(",
b"express.Router()",
];
const NEEDLES: &[&[u8]] = &[b"app.use(", b"router.use(", b"express.Router()"];
NEEDLES
.iter()
.any(|n| file_bytes.windows(n.len()).any(|w| w == *n))

View file

@ -17,8 +17,7 @@ fn callee_is_rails_migration(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"up"
| "down"
"up" | "down"
| "change"
| "create_table"
| "add_column"

View file

@ -17,13 +17,7 @@ fn callee_is_sequelize_migration(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"up"
| "down"
| "createTable"
| "addColumn"
| "dropTable"
| "removeColumn"
| "addIndex"
"up" | "down" | "createTable" | "addColumn" | "dropTable" | "removeColumn" | "addIndex"
)
}

View file

@ -11,6 +11,16 @@
//! the route / framework adapters; the per-cap sink adapters live
//! here so the per-language verticals can ship independently.
pub mod go_chi;
pub mod go_echo;
pub mod go_fiber;
pub mod go_gin;
pub mod go_routes;
pub mod graphql_apollo;
pub mod graphql_gqlgen;
pub mod graphql_graphene;
pub mod graphql_juniper;
pub mod graphql_relay;
pub mod header_go;
pub mod header_java;
pub mod header_js;
@ -18,11 +28,6 @@ pub mod header_php;
pub mod header_python;
pub mod header_ruby;
pub mod header_rust;
pub mod go_chi;
pub mod go_echo;
pub mod go_fiber;
pub mod go_gin;
pub mod go_routes;
pub mod java_deserialize;
pub mod java_micronaut;
pub mod java_quarkus;
@ -36,11 +41,6 @@ pub mod js_handlebars;
pub mod js_koa;
pub mod js_nest;
pub mod js_routes;
pub mod graphql_apollo;
pub mod graphql_gqlgen;
pub mod graphql_graphene;
pub mod graphql_juniper;
pub mod graphql_relay;
pub mod kafka_java;
pub mod kafka_python;
pub mod ldap_php;
@ -117,6 +117,15 @@ pub mod xxe_php;
pub mod xxe_python;
pub mod xxe_ruby;
pub use go_chi::GoChiAdapter;
pub use go_echo::GoEchoAdapter;
pub use go_fiber::GoFiberAdapter;
pub use go_gin::GoGinAdapter;
pub use graphql_apollo::GraphqlApolloAdapter;
pub use graphql_gqlgen::GraphqlGqlgenAdapter;
pub use graphql_graphene::GraphqlGrapheneAdapter;
pub use graphql_juniper::GraphqlJuniperAdapter;
pub use graphql_relay::GraphqlRelayAdapter;
pub use header_go::HeaderGoAdapter;
pub use header_java::HeaderJavaAdapter;
pub use header_js::HeaderJsAdapter;
@ -124,10 +133,6 @@ pub use header_php::HeaderPhpAdapter;
pub use header_python::HeaderPythonAdapter;
pub use header_ruby::HeaderRubyAdapter;
pub use header_rust::HeaderRustAdapter;
pub use go_chi::GoChiAdapter;
pub use go_echo::GoEchoAdapter;
pub use go_fiber::GoFiberAdapter;
pub use go_gin::GoGinAdapter;
pub use java_deserialize::JavaDeserializeAdapter;
pub use java_micronaut::JavaMicronautAdapter;
pub use java_quarkus::JavaQuarkusAdapter;
@ -139,11 +144,6 @@ pub use js_fastify::JsFastifyAdapter;
pub use js_handlebars::JsHandlebarsAdapter;
pub use js_koa::JsKoaAdapter;
pub use js_nest::{JsNestAdapter, TsNestAdapter};
pub use graphql_apollo::GraphqlApolloAdapter;
pub use graphql_gqlgen::GraphqlGqlgenAdapter;
pub use graphql_graphene::GraphqlGrapheneAdapter;
pub use graphql_juniper::GraphqlJuniperAdapter;
pub use graphql_relay::GraphqlRelayAdapter;
pub use kafka_java::KafkaJavaAdapter;
pub use kafka_python::KafkaPythonAdapter;
pub use ldap_php::LdapPhpAdapter;
@ -221,10 +221,7 @@ fn any_callee_matches(
summary: &crate::summary::FuncSummary,
predicate: impl Fn(&str) -> bool,
) -> bool {
summary
.callees
.iter()
.any(|c| predicate(c.name.as_str()))
summary.callees.iter().any(|c| predicate(c.name.as_str()))
}
/// True when any callee in `summary.callees` matches `name_pred` AND
@ -270,10 +267,7 @@ fn any_callee_matches_with_receiver(
/// Per-language sigil stripping covers PHP (`$x`), Ruby (`@x`), and
/// Java/Python/JS (no sigil). Leading whitespace is also trimmed so
/// adapters can pass the raw `utf8_text` of the argument node.
pub(super) fn arg_is_tainted_param(
summary: &crate::summary::FuncSummary,
arg_text: &str,
) -> bool {
pub(super) fn arg_is_tainted_param(summary: &crate::summary::FuncSummary, arg_text: &str) -> bool {
fn strip(s: &str) -> &str {
s.trim()
.trim_start_matches('$')
@ -281,15 +275,10 @@ pub(super) fn arg_is_tainted_param(
.trim_start_matches('&')
}
let needle = strip(arg_text);
let Some(idx) = summary
.param_names
.iter()
.position(|p| strip(p) == needle)
else {
let Some(idx) = summary.param_names.iter().position(|p| strip(p) == needle) else {
return false;
};
summary.tainted_sink_params.contains(&idx)
|| summary.propagating_params.contains(&idx)
summary.tainted_sink_params.contains(&idx) || summary.propagating_params.contains(&idx)
}
/// True when any descendant identifier in `node`'s subtree resolves to

View file

@ -18,11 +18,7 @@ fn callee_is_nats(name: &str) -> bool {
}
fn source_imports_nats(file_bytes: &[u8]) -> bool {
const NEEDLES: &[&[u8]] = &[
b"github.com/nats-io/nats.go",
b"nats.Connect",
b"nats.Msg",
];
const NEEDLES: &[&[u8]] = &[b"github.com/nats-io/nats.go", b"nats.Connect", b"nats.Msg"];
NEEDLES
.iter()
.any(|n| file_bytes.windows(n.len()).any(|w| w == *n))

View file

@ -11,9 +11,9 @@
//! inner name (after the `:`) for each so a `$id` formal whose name
//! matches the placeholder binds as [`super::super::ParamSource::PathSegment`].
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
#[cfg(test)]
use crate::dynamic::framework::HttpMethod;
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
@ -49,8 +49,7 @@ impl FrameworkAdapter for PhpCodeIgniterAdapter {
let (func_node, class) = find_php_function(ast, file_bytes, &summary.name)?;
let controller = class.and_then(|c| php_class_name(c, file_bytes));
let (method, path) =
find_codeigniter_route(ast, file_bytes, &summary.name, controller)?;
let (method, path) = find_codeigniter_route(ast, file_bytes, &summary.name, controller)?;
let formals = php_formal_names(func_node, file_bytes);
let request_params = bind_php_path_params(&formals, &path);
@ -120,17 +119,21 @@ mod tests {
fn skips_when_codeigniter_not_imported() {
let src: &[u8] = b"<?php\n$routes->get('users/(:num)', 'UserController::show');\n";
let tree = parse(src);
assert!(PhpCodeIgniterAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
PhpCodeIgniterAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_callable_does_not_reference_method() {
let src: &[u8] = b"<?php\nuse CodeIgniter\\Router\\RouteCollection;\n$routes->get('users/(:num)', 'UserController::show');\nclass UserController extends BaseController {\n public function helper($x) { return $x; }\n}\n";
let tree = parse(src);
assert!(PhpCodeIgniterAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpCodeIgniterAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -12,9 +12,9 @@
//! a `class UserController { public function show($id) {…} }`
//! declaration in the same file.
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
#[cfg(test)]
use crate::dynamic::framework::HttpMethod;
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
@ -50,8 +50,7 @@ impl FrameworkAdapter for PhpLaravelAdapter {
let (func_node, class) = find_php_function(ast, file_bytes, &summary.name)?;
let controller = class.and_then(|c| php_class_name(c, file_bytes));
let (method, path) =
find_laravel_static_route(ast, file_bytes, &summary.name, controller)?;
let (method, path) = find_laravel_static_route(ast, file_bytes, &summary.name, controller)?;
let formals = php_formal_names(func_node, file_bytes);
let request_params = bind_php_path_params(&formals, &path);
@ -143,17 +142,21 @@ mod tests {
fn skips_when_laravel_not_imported() {
let src: &[u8] = b"<?php\nfunction f($x) { return $x; }\n";
let tree = parse(src);
assert!(PhpLaravelAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none());
assert!(
PhpLaravelAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_route_mapping_does_not_reference_function() {
let src: &[u8] = b"<?php\nuse Illuminate\\Support\\Facades\\Route;\nRoute::get('/users', 'UserController@show');\nfunction helper($x) { return $x; }\n";
let tree = parse(src);
assert!(PhpLaravelAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpLaravelAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -122,15 +122,16 @@ fn walk<'a>(
&& let Some(name) = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target {
let klass = if node.kind() == "method_declaration" {
here_class
} else {
None
};
*out = Some((node, klass));
return;
}
&& name == target
{
let klass = if node.kind() == "method_declaration" {
here_class
} else {
None
};
*out = Some((node, klass));
return;
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, bytes, target, here_class, out);
@ -511,10 +512,7 @@ fn laravel_callable_matches(
}
}
fn parse_array_callable<'a>(
array: Node<'a>,
bytes: &'a [u8],
) -> Option<(Option<String>, String)> {
fn parse_array_callable<'a>(array: Node<'a>, bytes: &'a [u8]) -> Option<(Option<String>, String)> {
let mut cur = array.walk();
let elements: Vec<Node<'a>> = array
.named_children(&mut cur)
@ -544,10 +542,7 @@ fn split_laravel_callable(literal: &str) -> (Option<String>, String) {
fn leaf(qualified: &str) -> &str {
let last_backslash = qualified.rsplit('\\').next().unwrap_or(qualified);
last_backslash
.rsplit("::")
.next()
.unwrap_or(last_backslash)
last_backslash.rsplit("::").next().unwrap_or(last_backslash)
}
fn verb_method(verb: &str) -> Option<HttpMethod> {
@ -711,18 +706,12 @@ mod tests {
extract_php_path_placeholders("/u/{id}/p/{slug?}"),
vec!["id", "slug"]
);
assert_eq!(
extract_php_path_placeholders("/u/{id:[0-9]+}"),
vec!["id"]
);
assert_eq!(extract_php_path_placeholders("/u/{id:[0-9]+}"), vec!["id"]);
}
#[test]
fn extracts_codeigniter_placeholders() {
assert_eq!(
extract_php_path_placeholders("users/(:num)"),
vec!["num"]
);
assert_eq!(extract_php_path_placeholders("users/(:num)"), vec!["num"]);
assert_eq!(
extract_php_path_placeholders("p/(:any)/c/(:segment)"),
vec!["any", "segment"]
@ -778,20 +767,16 @@ mod tests {
fn finds_laravel_static_route_with_string_callable() {
let src: &[u8] = b"<?php\nRoute::get('/users/{id}', 'UserController@show');\nclass UserController {\n public function show($id) { return $id; }\n}\n";
let tree = parse(src);
let hit = find_laravel_static_route(
tree.root_node(),
src,
"show",
Some("UserController"),
)
.unwrap();
let hit = find_laravel_static_route(tree.root_node(), src, "show", Some("UserController"))
.unwrap();
assert_eq!(hit.0, HttpMethod::GET);
assert_eq!(hit.1, "/users/{id}");
}
#[test]
fn finds_laravel_static_route_with_closure() {
let src: &[u8] = b"<?php\nRoute::post('/users', function ($payload) { return $payload; });\n";
let src: &[u8] =
b"<?php\nRoute::post('/users', function ($payload) { return $payload; });\n";
let tree = parse(src);
let hit = find_laravel_static_route(tree.root_node(), src, "anything", None).unwrap();
assert_eq!(hit.0, HttpMethod::POST);
@ -802,13 +787,8 @@ mod tests {
fn finds_codeigniter_member_route() {
let src: &[u8] = b"<?php\n$routes->get('users/(:num)', 'UserController::show');\n";
let tree = parse(src);
let hit = find_codeigniter_route(
tree.root_node(),
src,
"show",
Some("UserController"),
)
.unwrap();
let hit =
find_codeigniter_route(tree.root_node(), src, "show", Some("UserController")).unwrap();
assert_eq!(hit.0, HttpMethod::GET);
assert_eq!(hit.1, "users/(:num)");
}

View file

@ -165,17 +165,21 @@ mod tests {
fn skips_when_symfony_not_imported() {
let src: &[u8] = b"<?php\n#[Route('/x')]\nfunction f() { return 1; }\n";
let tree = parse(src);
assert!(PhpSymfonyAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none());
assert!(
PhpSymfonyAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_method_has_no_route_attribute() {
let src: &[u8] = b"<?php\nuse Symfony\\Component\\Routing\\Annotation\\Route;\nclass C {\n public function helper($x) { return $x; }\n}\n";
let tree = parse(src);
assert!(PhpSymfonyAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpSymfonyAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -145,9 +145,11 @@ mod tests {
let src: &[u8] = b"<?php\nuse Twig\\Environment;\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate($body);\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[0]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -158,9 +160,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -170,9 +174,11 @@ mod tests {
let src: &[u8] = b"<?php\n// Twig\\Environment is great\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate('static');\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[0]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -180,8 +186,10 @@ mod tests {
let src: &[u8] = b"<?php\nuse Twig\\Environment;\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate($body);\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -68,9 +68,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -81,8 +83,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -141,9 +141,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -155,9 +157,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -176,8 +180,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -13,7 +13,10 @@ use crate::symbol::Lang;
fn callee_is_lodash_merge(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "merge" | "mergeWith" | "defaultsDeep" | "set" | "setWith")
matches!(
last,
"merge" | "mergeWith" | "defaultsDeep" | "set" | "setWith"
)
}
/// True when `receiver` looks like a lodash module handle (`_`, `lodash`,
@ -152,9 +155,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -165,9 +170,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -193,9 +200,11 @@ mod tests {
}],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -213,9 +222,11 @@ mod tests {
}],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -233,9 +244,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -249,8 +262,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -117,9 +117,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Object.assign")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -130,24 +132,27 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_object_create_null_mitigation() {
let src: &[u8] =
b"function run(payload) { return Object.create(null); }\n";
let src: &[u8] = b"function run(payload) { return Object.create(null); }\n";
let tree = parse_js(src);
let summary = FuncSummary {
name: "run".into(),
callees: vec![crate::summary::CalleeSite::bare("Object.create")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -164,8 +169,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Object.assign")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -11,10 +11,7 @@ const ADAPTER_NAME: &str = "pubsub-python";
fn callee_is_pubsub(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"subscribe" | "pull" | "callback" | "process_message"
)
matches!(last, "subscribe" | "pull" | "callback" | "process_message")
}
fn source_imports_pubsub(file_bytes: &[u8]) -> bool {

View file

@ -91,17 +91,19 @@ fn walk_url_registrations(
{
let last = callee.rsplit_once('.').map(|(_, s)| s).unwrap_or(callee);
if matches!(last, "path" | "re_path" | "url")
&& let Some(args) = node.child_by_field_name("arguments") {
let positional = positional_args(args);
if positional.len() >= 2 {
let view_arg = positional[1];
if view_arg_references(view_arg, bytes, target, class_target)
&& let Some(template) = first_string_arg(args, bytes) {
*out = Some(template);
return;
}
&& let Some(args) = node.child_by_field_name("arguments")
{
let positional = positional_args(args);
if positional.len() >= 2 {
let view_arg = positional[1];
if view_arg_references(view_arg, bytes, target, class_target)
&& let Some(template) = first_string_arg(args, bytes)
{
*out = Some(template);
return;
}
}
}
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
@ -137,12 +139,15 @@ fn view_arg_references(
.and_then(|s| s.rfind('(').map(|i| &s[..i]))
.and_then(|s| s.strip_suffix(".as_view"))
&& let Some(ct) = class_target
&& class.rsplit_once('.').map(|(_, s)| s).unwrap_or(class) == ct
{
return true;
}
&& class.rsplit_once('.').map(|(_, s)| s).unwrap_or(class) == ct
{
return true;
}
let stripped = trimmed.trim_end_matches("()");
let last = stripped.rsplit_once('.').map(|(_, s)| s).unwrap_or(stripped);
let last = stripped
.rsplit_once('.')
.map(|(_, s)| s)
.unwrap_or(stripped);
last == target || stripped == target
}
@ -191,12 +196,8 @@ impl FrameworkAdapter for PythonDjangoAdapter {
// - urls.py registration referencing the function
// - urls.py `ClassName.as_view()` registration referencing the enclosing class
// - class-based view method name (path falls back to `/`)
let url_template = url_template_for(
ast,
file_bytes,
&summary.name,
cbv_class_name.as_deref(),
);
let url_template =
url_template_for(ast, file_bytes, &summary.name, cbv_class_name.as_deref());
let (method, path) = if let Some(m) = cbv_method {
(m, url_template.unwrap_or_else(|| "/".to_owned()))
@ -288,18 +289,23 @@ mod tests {
fn skips_when_django_not_imported() {
let src: &[u8] = b"def list_users(request):\n return None\n";
let tree = parse(src);
assert!(PythonDjangoAdapter
.detect(&summary("list_users"), tree.root_node(), src)
.is_none());
assert!(
PythonDjangoAdapter
.detect(&summary("list_users"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_plain_helper_function() {
let src: &[u8] = b"from django.http import HttpResponse\ndef helper(x):\n return HttpResponse(x)\n";
let src: &[u8] =
b"from django.http import HttpResponse\ndef helper(x):\n return HttpResponse(x)\n";
let tree = parse(src);
assert!(PythonDjangoAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PythonDjangoAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -314,8 +320,10 @@ mod tests {
// pipeline surfaces `SpecDerivationFailed`.
let src: &[u8] = b"from django.http import HttpResponse\ndef authenticated(request, perm):\n return HttpResponse(perm)\n";
let tree = parse(src);
assert!(PythonDjangoAdapter
.detect(&summary("authenticated"), tree.root_node(), src)
.is_none());
assert!(
PythonDjangoAdapter
.detect(&summary("authenticated"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -62,8 +62,14 @@ fn decorator_route_shape(decorator: Node<'_>, bytes: &[u8]) -> Option<(HttpMetho
if target.kind() != "attribute" {
return None;
}
let object = target.child_by_field_name("object")?.utf8_text(bytes).ok()?;
let attr = target.child_by_field_name("attribute")?.utf8_text(bytes).ok()?;
let object = target
.child_by_field_name("object")?
.utf8_text(bytes)
.ok()?;
let attr = target
.child_by_field_name("attribute")?
.utf8_text(bytes)
.ok()?;
if !receiver_looks_like_fastapi(object) {
return None;
}
@ -389,8 +395,10 @@ mod tests {
fn skips_when_fastapi_not_imported() {
let src: &[u8] = b"from flask import Flask\napp = Flask(__name__)\n@app.get(\"/x\")\ndef x():\n return 1\n";
let tree = parse(src);
assert!(PythonFastApiAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
PythonFastApiAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -202,10 +202,12 @@ mod tests {
.expect("binding");
let route = binding.route.unwrap();
assert_eq!(route.path, "/users/<id>");
assert!(binding
.request_params
.iter()
.any(|p| p.name == "id" && matches!(p.source, ParamSource::PathSegment(_))));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "id" && matches!(p.source, ParamSource::PathSegment(_)))
);
}
#[test]
@ -234,17 +236,22 @@ mod tests {
fn skips_when_flask_not_imported() {
let src: &[u8] = b"def add(a, b):\n return a + b\n";
let tree = parse(src);
assert!(PythonFlaskAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none());
assert!(
PythonFlaskAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_function_has_no_decorator() {
let src: &[u8] = b"from flask import Flask\napp = Flask(__name__)\ndef helper(x):\n return x\n";
let src: &[u8] =
b"from flask import Flask\napp = Flask(__name__)\ndef helper(x):\n return x\n";
let tree = parse(src);
assert!(PythonFlaskAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PythonFlaskAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -92,9 +92,7 @@ impl FrameworkAdapter for PythonJinja2Adapter {
ast: tree_sitter::Node<'_>,
file_bytes: &[u8],
) -> Option<FrameworkBinding> {
let cheap_filter = file_bytes
.windows(b"jinja2".len())
.any(|w| w == b"jinja2")
let cheap_filter = file_bytes.windows(b"jinja2".len()).any(|w| w == b"jinja2")
|| file_bytes
.windows(b"from_string".len())
.any(|w| w == b"from_string")
@ -149,9 +147,11 @@ mod tests {
b"from jinja2 import Template\ndef render(body):\n return Template(body).render()\n";
let tree = parse_python(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -161,9 +161,11 @@ mod tests {
let tree = parse_python(src);
let mut summary = summary_for("view", &["body"], &[0]);
summary.callees = vec![crate::summary::CalleeSite::bare("render_template_string")];
assert!(PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -174,9 +176,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -186,9 +190,11 @@ mod tests {
let src: &[u8] = b"\"\"\"renders via jinja2.Template\"\"\"\ndef render(body):\n return Template(\"hello\").render()\n";
let tree = parse_python(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -199,8 +205,10 @@ mod tests {
b"from jinja2 import Template\ndef render(body):\n return Template(body).render()\n";
let tree = parse_python(src);
let summary = summary_for("render", &["body"], &[]);
assert!(PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PythonJinja2Adapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -34,9 +34,7 @@ impl FrameworkAdapter for PythonPickleAdapter {
file_bytes: &[u8],
) -> Option<FrameworkBinding> {
let matches_call = super::any_callee_matches(summary, callee_is_python_deserialize);
let matches_source = file_bytes
.windows(b"pickle".len())
.any(|w| w == b"pickle")
let matches_source = file_bytes.windows(b"pickle".len()).any(|w| w == b"pickle")
|| file_bytes
.windows(b"yaml.unsafe_load".len())
.any(|w| w == b"yaml.unsafe_load")
@ -77,9 +75,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PythonPickleAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PythonPickleAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -90,8 +90,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PythonPickleAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PythonPickleAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -33,7 +33,12 @@ pub fn source_imports_flask(bytes: &[u8]) -> bool {
pub fn source_imports_fastapi(bytes: &[u8]) -> bool {
contains_any(
bytes,
&[b"from fastapi", b"import fastapi", b"FastAPI(", b"APIRouter("],
&[
b"from fastapi",
b"import fastapi",
b"FastAPI(",
b"APIRouter(",
],
)
}
@ -95,10 +100,11 @@ fn walk<'a>(node: Node<'a>, bytes: &[u8], target: &str) -> Option<(Node<'a>, Opt
&& let Some(name) = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target {
let decorated = node.parent().filter(|p| p.kind() == "decorated_definition");
return Some((node, decorated));
}
&& name == target
{
let decorated = node.parent().filter(|p| p.kind() == "decorated_definition");
return Some((node, decorated));
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
if let Some(found) = walk(child, bytes, target) {

View file

@ -8,9 +8,7 @@
//! to the handler does not matter. Methods are picked up from the
//! `methods=[...]` kwarg when present and default to `GET`.
use crate::dynamic::framework::{
FrameworkAdapter, FrameworkBinding, HttpMethod, RouteShape,
};
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, HttpMethod, RouteShape};
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
@ -50,12 +48,13 @@ fn walk_routes(node: Node<'_>, bytes: &[u8], target: &str, out: &mut Option<(Htt
let last = callee.rsplit_once('.').map(|(_, s)| s).unwrap_or(callee);
if matches!(last, "Route" | "WebSocketRoute")
&& let Some(args) = node.child_by_field_name("arguments")
&& let Some(path) = first_string_arg(args, bytes)
&& endpoint_references(args, bytes, target) {
let method = methods_kwarg(args, bytes).unwrap_or(HttpMethod::GET);
*out = Some((method, path));
return;
}
&& let Some(path) = first_string_arg(args, bytes)
&& endpoint_references(args, bytes, target)
{
let method = methods_kwarg(args, bytes).unwrap_or(HttpMethod::GET);
*out = Some((method, path));
return;
}
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
@ -76,9 +75,10 @@ fn endpoint_references(args: Node<'_>, bytes: &[u8], target: &str) -> bool {
};
if name_text == "endpoint"
&& let Some(value) = arg.child_by_field_name("value")
&& identifier_matches(value, bytes, target) {
return true;
}
&& identifier_matches(value, bytes, target)
{
return true;
}
} else {
seen_positional += 1;
// Second positional argument is the endpoint when no
@ -204,8 +204,10 @@ mod tests {
fn skips_when_starlette_not_imported() {
let src: &[u8] = b"def homepage(request):\n return None\n";
let tree = parse(src);
assert!(PythonStarletteAdapter
.detect(&summary("homepage"), tree.root_node(), src)
.is_none());
assert!(
PythonStarletteAdapter
.detect(&summary("homepage"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -111,7 +111,8 @@ mod tests {
#[test]
fn fires_on_gin_redirect() {
let src: &[u8] = b"package vuln\n\nimport (\n\t\"net/http\"\n\t\"github.com/gin-gonic/gin\"\n)\n\
let src: &[u8] =
b"package vuln\n\nimport (\n\t\"net/http\"\n\t\"github.com/gin-gonic/gin\"\n)\n\
func Run(c *gin.Context, v string) {\n\tc.Redirect(http.StatusFound, v)\n}\n";
let tree = parse_go(src);
let summary = FuncSummary {
@ -119,9 +120,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Redirect")],
..Default::default()
};
assert!(RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -132,9 +135,11 @@ mod tests {
name: "Add".into(),
..Default::default()
};
assert!(RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -153,9 +158,11 @@ mod tests {
],
..Default::default()
};
assert!(RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,8 +175,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Redirect")],
..Default::default()
};
assert!(RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -108,9 +108,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("sendRedirect")],
..Default::default()
};
assert!(RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -121,9 +123,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -144,8 +148,10 @@ mod tests {
],
..Default::default()
};
assert!(RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -112,9 +112,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("redirect")],
..Default::default()
};
assert!(RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -125,9 +127,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -143,8 +147,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("redirect")],
..Default::default()
};
assert!(RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -73,13 +73,12 @@ impl FrameworkAdapter for RedirectPhpAdapter {
return None;
}
let has_location_token = file_contains_location_header_token(file_bytes);
let matches_call = super::any_callee_matches(summary, |name| {
match callee_last_segment(name) {
let matches_call =
super::any_callee_matches(summary, |name| match callee_last_segment(name) {
"redirect" | "withRedirect" | "RedirectResponse" => true,
"header" => has_location_token,
_ => false,
}
});
});
let matches_source = source_imports_php_web(file_bytes);
if matches_call && matches_source {
Some(FrameworkBinding {
@ -109,17 +108,18 @@ mod tests {
#[test]
fn fires_on_header_location() {
let src: &[u8] =
b"<?php\nfunction run($v) { header(\"Location: \" . $v); exit; }\n";
let src: &[u8] = b"<?php\nfunction run($v) { header(\"Location: \" . $v); exit; }\n";
let tree = parse_php(src);
let summary = FuncSummary {
name: "run".into(),
callees: vec![crate::summary::CalleeSite::bare("header")],
..Default::default()
};
assert!(RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -130,9 +130,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -149,9 +151,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("header")],
..Default::default()
};
assert!(RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -171,8 +175,10 @@ mod tests {
],
..Default::default()
};
assert!(RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -114,9 +114,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("redirect")],
..Default::default()
};
assert!(RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -127,9 +129,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -148,8 +152,10 @@ mod tests {
],
..Default::default()
};
assert!(RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -18,7 +18,7 @@ const ADAPTER_NAME: &str = "redirect-ruby";
fn callee_is_redirect(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "redirect" | "redirect_to" | "redirect!" )
matches!(last, "redirect" | "redirect_to" | "redirect!")
}
fn source_imports_ruby_web(file_bytes: &[u8]) -> bool {
@ -110,9 +110,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("redirect")],
..Default::default()
};
assert!(RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -123,9 +125,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -144,8 +148,10 @@ mod tests {
],
..Default::default()
};
assert!(RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -128,9 +128,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("to")],
..Default::default()
};
assert!(RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -141,9 +143,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -166,9 +170,11 @@ mod tests {
}],
..Default::default()
};
assert!(RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -189,9 +195,11 @@ mod tests {
}],
..Default::default()
};
assert!(RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -211,8 +219,10 @@ mod tests {
],
..Default::default()
};
assert!(RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RedirectRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -26,7 +26,10 @@ fn callee_last_segment(name: &str) -> &str {
}
fn is_erb_entry(name: &str) -> bool {
matches!(callee_last_segment(name), "result" | "result_with_hash" | "new")
matches!(
callee_last_segment(name),
"result" | "result_with_hash" | "new"
)
}
fn ast_confirms_tainted_call(root: Node<'_>, bytes: &[u8], summary: &FuncSummary) -> bool {
@ -61,7 +64,10 @@ fn walk(node: Node<'_>, bytes: &[u8], summary: &FuncSummary, found: &mut bool) {
fn first_positional_arg<'a>(args: Node<'a>) -> Option<Node<'a>> {
let mut cur = args.walk();
for arg in args.named_children(&mut cur) {
if matches!(arg.kind(), "pair" | "hash_splat_argument" | "block_argument") {
if matches!(
arg.kind(),
"pair" | "hash_splat_argument" | "block_argument"
) {
continue;
}
return Some(arg);
@ -93,9 +99,7 @@ impl FrameworkAdapter for RubyErbAdapter {
|| file_bytes
.windows(b"require \"erb\"".len())
.any(|w| w == b"require \"erb\"")
|| file_bytes
.windows(b"Erubi".len())
.any(|w| w == b"Erubi");
|| file_bytes.windows(b"Erubi".len()).any(|w| w == b"Erubi");
if !cheap_filter {
return None;
}
@ -139,9 +143,11 @@ mod tests {
let src: &[u8] = b"require 'erb'\ndef render(body)\n ERB.new(body).result\nend\n";
let tree = parse_ruby(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -152,9 +158,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -163,9 +171,11 @@ mod tests {
b"# require 'erb' is mentioned\ndef render(body)\n ERB.new(\"static\").result\nend\n";
let tree = parse_ruby(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -173,8 +183,10 @@ mod tests {
let src: &[u8] = b"require 'erb'\ndef render(body)\n ERB.new(body).result\nend\n";
let tree = parse_ruby(src);
let summary = summary_for("render", &["body"], &[]);
assert!(RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RubyErbAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -172,7 +172,11 @@ mod tests {
let binding = RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.expect("binding");
let id = binding.request_params.iter().find(|p| p.name == "id").unwrap();
let id = binding
.request_params
.iter()
.find(|p| p.name == "id")
.unwrap();
assert!(matches!(id.source, ParamSource::PathSegment(_)));
}
@ -184,7 +188,11 @@ mod tests {
let binding = RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.expect("binding");
let req = binding.request_params.iter().find(|p| p.name == "req").unwrap();
let req = binding
.request_params
.iter()
.find(|p| p.name == "req")
.unwrap();
assert!(matches!(req.source, ParamSource::Implicit));
}
@ -194,9 +202,11 @@ mod tests {
b"require 'hanami/action'\nclass Plain\n def call(req)\n 'ok'\n end\nend\n";
let tree = parse(src);
// No `Hanami::Action` superclass / include — must skip.
assert!(RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.is_none());
assert!(
RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -207,8 +217,10 @@ mod tests {
// `Hanami::Action` substring, so this fixture in fact does
// trip the marker — the test exists to document that bare
// `Hanami::Action` superclass alone is sufficient.
assert!(RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.is_some());
assert!(
RubyHanamiAdapter
.detect(&summary("call"), tree.root_node(), src)
.is_some()
);
}
}

View file

@ -79,9 +79,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(RubyMarshalAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
RubyMarshalAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -92,8 +94,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(RubyMarshalAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
RubyMarshalAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -81,19 +81,31 @@ fn visit_routes<'a>(
format!("{path_prefix}/{ident}"),
format!("{ctrl_prefix}{ident}/"),
),
NestingKind::ScopePath => (format!("{path_prefix}/{ident}"), ctrl_prefix.to_owned()),
NestingKind::ScopePath => {
(format!("{path_prefix}/{ident}"), ctrl_prefix.to_owned())
}
};
recurse_into_block(node, bytes, controller, action, &path_pfx, &ctrl_pfx, out);
return;
}
if let Some(found) = try_route_mapping(node, bytes, controller, action, path_prefix, ctrl_prefix) {
if let Some(found) =
try_route_mapping(node, bytes, controller, action, path_prefix, ctrl_prefix)
{
*out = Some(found);
return;
}
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
visit_routes(child, bytes, controller, action, path_prefix, ctrl_prefix, out);
visit_routes(
child,
bytes,
controller,
action,
path_prefix,
ctrl_prefix,
out,
);
}
}
@ -153,7 +165,15 @@ fn recurse_into_block<'a>(
let mut cur = call.walk();
for child in call.named_children(&mut cur) {
if child.kind() == "do_block" || child.kind() == "block" {
visit_routes(child, bytes, controller, action, path_prefix, ctrl_prefix, out);
visit_routes(
child,
bytes,
controller,
action,
path_prefix,
ctrl_prefix,
out,
);
}
}
}
@ -208,9 +228,7 @@ fn controller_matches(routes_ctrl: &str, controller_class: &str) -> bool {
}
fn rails_controller_path(class_name: &str) -> String {
let stripped = class_name
.strip_suffix("Controller")
.unwrap_or(class_name);
let stripped = class_name.strip_suffix("Controller").unwrap_or(class_name);
// Rails routes use the singular-segment lower form joined by `/`
// for module-namespaced controllers (`Api::Users` → `api/users`).
let segments: Vec<String> = stripped
@ -356,8 +374,15 @@ mod tests {
.expect("binding");
let route = binding.route.unwrap();
assert_eq!(route.path, "/u/:id");
let id = binding.request_params.iter().find(|p| p.name == "id").unwrap();
assert!(matches!(id.source, crate::dynamic::framework::ParamSource::PathSegment(_)));
let id = binding
.request_params
.iter()
.find(|p| p.name == "id")
.unwrap();
assert!(matches!(
id.source,
crate::dynamic::framework::ParamSource::PathSegment(_)
));
}
#[test]
@ -409,9 +434,11 @@ mod tests {
fn skips_when_class_is_not_a_controller() {
let src: &[u8] = b"class Foo\n def bar\n 'ok'\n end\nend\n";
let tree = parse(src);
assert!(RubyRailsAdapter
.detect(&summary("bar"), tree.root_node(), src)
.is_none());
assert!(
RubyRailsAdapter
.detect(&summary("bar"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -419,29 +446,29 @@ mod tests {
let src: &[u8] =
b"class UsersController < ApplicationController\n def index\n 'ok'\n end\nend\n";
let tree = parse(src);
assert!(RubyRailsAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none());
assert!(
RubyRailsAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_files_without_rails_marker() {
let src: &[u8] =
b"class UsersController < Object\n def index\n 'ok'\n end\nend\n";
let src: &[u8] = b"class UsersController < Object\n def index\n 'ok'\n end\nend\n";
let tree = parse(src);
assert!(RubyRailsAdapter
.detect(&summary("index"), tree.root_node(), src)
.is_none());
assert!(
RubyRailsAdapter
.detect(&summary("index"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn rails_controller_path_drops_suffix_and_snake_cases() {
assert_eq!(rails_controller_path("UsersController"), "users");
assert_eq!(rails_controller_path("UserPostsController"), "user_posts");
assert_eq!(
rails_controller_path("Api::UsersController"),
"api/users"
);
assert_eq!(rails_controller_path("Api::UsersController"), "api/users");
assert_eq!(rails_controller_path("Foo"), "foo");
}
}

View file

@ -96,10 +96,11 @@ fn walk_class<'a>(
return;
}
if node.kind() == "class"
&& let Some(method) = find_method_in_class(node, bytes, target) {
*out = Some((node, method));
return;
}
&& let Some(method) = find_method_in_class(node, bytes, target)
{
*out = Some((node, method));
return;
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk_class(child, bytes, target, out);
@ -109,7 +110,11 @@ fn walk_class<'a>(
/// Find a `method` node named `target` directly inside a `class`
/// body. Returns `None` when the class has no body or no method of
/// that name.
pub fn find_method_in_class<'a>(class: Node<'a>, bytes: &'a [u8], target: &str) -> Option<Node<'a>> {
pub fn find_method_in_class<'a>(
class: Node<'a>,
bytes: &'a [u8],
target: &str,
) -> Option<Node<'a>> {
let body = named_child_of_kind(class, "body_statement")?;
let mut cur = body.walk();
for member in body.named_children(&mut cur) {
@ -117,9 +122,10 @@ pub fn find_method_in_class<'a>(class: Node<'a>, bytes: &'a [u8], target: &str)
continue;
}
if let Some(name) = method_identifier(member, bytes)
&& name == target {
return Some(member);
}
&& name == target
{
return Some(member);
}
}
None
}
@ -349,7 +355,10 @@ pub fn bind_path_params(formals: &[String], path: &str) -> Vec<ParamBinding> {
}
fn is_implicit_formal(name: &str) -> bool {
matches!(name, "env" | "request" | "req" | "params" | "response" | "res")
matches!(
name,
"env" | "request" | "req" | "params" | "response" | "res"
)
}
/// Read the first positional symbol argument (`:foo`) from an
@ -489,8 +498,7 @@ mod tests {
#[test]
fn class_includes_detects_hanami_v2() {
let src: &[u8] =
b"class A\n include Hanami::Action\n def call(req)\n end\nend\n";
let src: &[u8] = b"class A\n include Hanami::Action\n def call(req)\n end\nend\n";
let tree = parse(src);
let mut cur = tree.root_node().walk();
let class = tree

View file

@ -41,10 +41,11 @@ fn collect_routes(root: Node<'_>, bytes: &[u8]) -> Vec<SinatraRoute> {
fn visit(node: Node<'_>, bytes: &[u8], out: &mut Vec<SinatraRoute>) {
if node.kind() == "call"
&& let Some(route) = try_route(node, bytes) {
out.push(route);
return;
}
&& let Some(route) = try_route(node, bytes)
{
out.push(route);
return;
}
// Sinatra routes live at top level or directly under a `class App <
// Sinatra::Base` body — never inside a helper method's body. Skip
// descent through `method` / `singleton_method` so a stray `get '/x'
@ -101,9 +102,10 @@ fn block_parameter_names(block: Node<'_>, bytes: &[u8]) -> Vec<String> {
let mut bc = child.walk();
for p in child.named_children(&mut bc) {
if p.kind() == "identifier"
&& let Ok(t) = p.utf8_text(bytes) {
out.push(t.to_owned());
}
&& let Ok(t) = p.utf8_text(bytes)
{
out.push(t.to_owned());
}
}
}
out
@ -196,8 +198,7 @@ mod tests {
#[test]
fn fires_on_marker_comment() {
let src: &[u8] =
b"# nyx-shape: sinatra\nget '/run' do |payload|\n payload\nend\n";
let src: &[u8] = b"# nyx-shape: sinatra\nget '/run' do |payload|\n payload\nend\n";
let tree = parse(src);
let binding = RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
@ -207,13 +208,16 @@ mod tests {
#[test]
fn binds_path_placeholder() {
let src: &[u8] =
b"require 'sinatra'\nget '/u/:id' do |id|\n id\nend\n";
let src: &[u8] = b"require 'sinatra'\nget '/u/:id' do |id|\n id\nend\n";
let tree = parse(src);
let binding = RubySinatraAdapter
.detect(&summary("id"), tree.root_node(), src)
.expect("binding");
let id = binding.request_params.iter().find(|p| p.name == "id").unwrap();
let id = binding
.request_params
.iter()
.find(|p| p.name == "id")
.unwrap();
assert!(matches!(id.source, ParamSource::PathSegment(_)));
}
@ -223,9 +227,11 @@ mod tests {
let tree = parse(src);
// No do/end block — the Sinatra adapter must not claim a
// Rails-style `routes.draw` mapping.
assert!(RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -243,9 +249,11 @@ mod tests {
fn skips_when_sinatra_not_imported() {
let src: &[u8] = b"get '/run' do |p|\n p\nend\n";
let tree = parse(src);
assert!(RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -279,9 +287,11 @@ mod tests {
let src: &[u8] =
b"require 'sinatra'\ndef helper\n get '/run' do |payload|\n payload\n end\nend\n";
let tree = parse(src);
assert!(RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
RubySinatraAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]

View file

@ -114,18 +114,22 @@ mod tests {
fn skips_when_actix_not_imported() {
let src: &[u8] = b"#[get(\"/u\")]\nfn show() {}\n";
let tree = parse(src);
assert!(RustActixAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustActixAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_attribute_missing() {
let src: &[u8] = b"use actix_web::App;\nfn helper(x: String) {}\n";
let tree = parse(src);
assert!(RustActixAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
RustActixAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -170,8 +174,10 @@ mod tests {
async fn show() -> String { String::new() }\n\
async fn other() -> String { String::new() }\n";
let tree = parse(src);
assert!(RustActixAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustActixAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -116,17 +116,21 @@ mod tests {
fn skips_when_axum_not_imported() {
let src: &[u8] = b"fn show() {}\n";
let tree = parse(src);
assert!(RustAxumAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustAxumAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_route_does_not_reference_function() {
let src: &[u8] = b"use axum::Router;\nfn build() -> Router { Router::new().route(\"/u\", get(show)) }\nfn helper() {}\n";
let tree = parse(src);
assert!(RustAxumAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
RustAxumAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -86,7 +86,8 @@ mod tests {
#[test]
fn fires_on_get_with_angle_placeholder() {
let src: &[u8] = b"use rocket::get;\n#[get(\"/u/<id>\")]\nfn show(id: String) -> String { id }\n";
let src: &[u8] =
b"use rocket::get;\n#[get(\"/u/<id>\")]\nfn show(id: String) -> String { id }\n";
let tree = parse(src);
let binding = RustRocketAdapter
.detect(&summary("show"), tree.root_node(), src)
@ -118,8 +119,10 @@ mod tests {
fn skips_when_rocket_not_imported() {
let src: &[u8] = b"#[get(\"/u\")]\nfn show() {}\n";
let tree = parse(src);
assert!(RustRocketAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustRocketAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -83,22 +83,13 @@ fn contains_any(haystack: &[u8], needles: &[&[u8]]) -> bool {
/// Find a top-level `function_item` whose `name` field equals
/// `target`. Walks the AST recursively so functions nested inside
/// `impl` blocks are also matched.
pub fn find_rust_function<'a>(
root: Node<'a>,
bytes: &'a [u8],
target: &str,
) -> Option<Node<'a>> {
pub fn find_rust_function<'a>(root: Node<'a>, bytes: &'a [u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_rs(root, bytes, target, &mut hit);
hit
}
fn walk_rs<'a>(
node: Node<'a>,
bytes: &'a [u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_rs<'a>(node: Node<'a>, bytes: &'a [u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -143,9 +134,10 @@ fn push_pattern_name(pat: Node<'_>, bytes: &[u8], out: &mut Vec<String>) {
match pat.kind() {
"identifier" => {
if let Ok(text) = pat.utf8_text(bytes)
&& text != "_" {
out.push(text.to_owned());
}
&& text != "_"
{
out.push(text.to_owned());
}
}
"mut_pattern" | "ref_pattern" => {
let mut cur = pat.walk();
@ -310,10 +302,7 @@ pub fn rust_string_literal(node: Node<'_>, bytes: &[u8]) -> Option<String> {
}
let raw = node.utf8_text(bytes).ok()?;
let trimmed = raw.trim();
if trimmed.len() >= 2
&& trimmed.starts_with('"')
&& trimmed.ends_with('"')
{
if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
Some(trimmed[1..trimmed.len() - 1].to_owned())
} else {
None
@ -324,10 +313,7 @@ pub fn rust_string_literal(node: Node<'_>, bytes: &[u8]) -> Option<String> {
/// for a `#[get("/path")]` / `#[post(...)]` / `#[route(...)]` macro.
/// Returns `(method, path)` on first match. Used by both actix-web
/// (`#[get("/path")]`) and rocket (same syntax).
pub fn find_method_attribute<'a>(
func: Node<'a>,
bytes: &'a [u8],
) -> Option<(HttpMethod, String)> {
pub fn find_method_attribute<'a>(func: Node<'a>, bytes: &'a [u8]) -> Option<(HttpMethod, String)> {
let parent = func.parent()?;
let mut cur = parent.walk();
let children: Vec<Node<'_>> = parent.children(&mut cur).collect();
@ -359,9 +345,10 @@ pub fn find_method_attribute<'a>(
let mut cur = func.walk();
for c in func.children(&mut cur) {
if c.kind() == "attribute_item"
&& let Some(hit) = read_route_attribute(c, bytes) {
return Some(hit);
}
&& let Some(hit) = read_route_attribute(c, bytes)
{
return Some(hit);
}
}
None
}
@ -494,20 +481,14 @@ fn try_axum_route_call<'a>(
/// Parse the `get(handler)` / `axum::routing::get(handler)` wrapper
/// emitted by axum. Returns `(method, handler_node)` on success.
fn parse_axum_verb_wrapper<'a>(
node: Node<'a>,
bytes: &'a [u8],
) -> Option<(HttpMethod, Node<'a>)> {
fn parse_axum_verb_wrapper<'a>(node: Node<'a>, bytes: &'a [u8]) -> Option<(HttpMethod, Node<'a>)> {
if node.kind() != "call_expression" {
return None;
}
let func = node.child_by_field_name("function")?;
let leaf = match func.kind() {
"identifier" => func.utf8_text(bytes).ok()?,
"scoped_identifier" => func
.child_by_field_name("name")?
.utf8_text(bytes)
.ok()?,
"scoped_identifier" => func.child_by_field_name("name")?.utf8_text(bytes).ok()?,
_ => return None,
};
let method = verb_from_ident(leaf)?;
@ -613,10 +594,7 @@ fn try_actix_route_call<'a>(
/// Parse `web::get().to(handler)` / `web::post().to(handler)` /
/// `web::method(Method::PATCH).to(handler)` shapes. Returns
/// `(method, handler_node)` on the first matching `.to(...)` call.
fn parse_actix_web_verb_to<'a>(
node: Node<'a>,
bytes: &'a [u8],
) -> Option<(HttpMethod, Node<'a>)> {
fn parse_actix_web_verb_to<'a>(node: Node<'a>, bytes: &'a [u8]) -> Option<(HttpMethod, Node<'a>)> {
if node.kind() != "call_expression" {
return None;
}
@ -721,21 +699,21 @@ fn walk_warp<'a>(
while let Some(p) = parent {
if p.kind() == "call_expression"
&& let Some(func) = p.child_by_field_name("function")
&& func.kind() == "field_expression"
&& let Some(field) = func.child_by_field_name("field")
&& let Ok(field_text) = field.utf8_text(bytes)
&& matches!(field_text, "map" | "and_then" | "untuple_one")
{
let args = p.child_by_field_name("arguments");
if let Some(args) = args {
let mut cur = args.walk();
for c in args.named_children(&mut cur) {
if axum_callable_matches(c, bytes, target) {
hit_target = true;
}
&& func.kind() == "field_expression"
&& let Some(field) = func.child_by_field_name("field")
&& let Ok(field_text) = field.utf8_text(bytes)
&& matches!(field_text, "map" | "and_then" | "untuple_one")
{
let args = p.child_by_field_name("arguments");
if let Some(args) = args {
let mut cur = args.walk();
for c in args.named_children(&mut cur) {
if axum_callable_matches(c, bytes, target) {
hit_target = true;
}
}
}
}
// Detect verb-filter calls (`warp::get()`, `warp::post()`).
let mut cur = p.walk();
for child in p.children(&mut cur) {
@ -843,8 +821,7 @@ mod tests {
fn finds_axum_route_get() {
let src: &[u8] = b"use axum::Router;\nfn build() -> Router { Router::new().route(\"/u/{id}\", get(show)) }\nfn show() {}\n";
let tree = parse(src);
let (method, path) =
find_axum_route(tree.root_node(), src, "show").expect("hit");
let (method, path) = find_axum_route(tree.root_node(), src, "show").expect("hit");
assert_eq!(method, HttpMethod::GET);
assert_eq!(path, "/u/{id}");
}
@ -853,8 +830,7 @@ mod tests {
fn finds_axum_route_with_scoped_verb() {
let src: &[u8] = b"use axum::Router;\nfn build() -> Router { Router::new().route(\"/x\", axum::routing::post(save)) }\nfn save() {}\n";
let tree = parse(src);
let (method, path) =
find_axum_route(tree.root_node(), src, "save").expect("hit");
let (method, path) = find_axum_route(tree.root_node(), src, "save").expect("hit");
assert_eq!(method, HttpMethod::POST);
assert_eq!(path, "/x");
}
@ -871,8 +847,7 @@ mod tests {
#[test]
fn finds_rocket_post_attribute() {
let src: &[u8] =
b"#[post(\"/save\", data = \"<body>\")]\nfn save(body: String) {}\n";
let src: &[u8] = b"#[post(\"/save\", data = \"<body>\")]\nfn save(body: String) {}\n";
let tree = parse(src);
let func = find_rust_function(tree.root_node(), src, "save").unwrap();
let (method, path) = find_method_attribute(func, src).expect("hit");
@ -890,7 +865,11 @@ mod tests {
#[test]
fn binds_implicit_request_as_implicit() {
let formals = vec!["req".to_string(), "request".to_string(), "state".to_string()];
let formals = vec![
"req".to_string(),
"request".to_string(),
"state".to_string(),
];
let bindings = bind_rust_path_params(&formals, "/x");
for b in &bindings {
assert!(matches!(b.source, ParamSource::Implicit));
@ -908,8 +887,7 @@ mod tests {
fn finds_warp_path_macro_with_map_target() {
let src: &[u8] = b"use warp::Filter;\nfn build() { let r = warp::path!(\"users\" / u32).map(show); }\nfn show(id: u32) -> String { String::new() }\n";
let tree = parse(src);
let (_method, path) =
find_warp_route(tree.root_node(), src, "show").expect("hit");
let (_method, path) = find_warp_route(tree.root_node(), src, "show").expect("hit");
assert!(path.contains("users"));
}
@ -923,8 +901,7 @@ mod tests {
#[test]
fn warp_multi_typed_anonymous_placeholders_bind_positionally() {
let formals = vec!["user_id".to_string(), "post_slug".to_string()];
let bindings =
bind_rust_path_params(&formals, "/users/{u32}/posts/{String}");
let bindings = bind_rust_path_params(&formals, "/users/{u32}/posts/{String}");
assert!(matches!(bindings[0].source, ParamSource::PathSegment(_)));
assert!(matches!(bindings[1].source, ParamSource::PathSegment(_)));
}

View file

@ -112,17 +112,21 @@ mod tests {
fn skips_when_warp_not_imported() {
let src: &[u8] = b"fn show() {}\n";
let tree = parse(src);
assert!(RustWarpAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustWarpAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_no_path_macro() {
let src: &[u8] = b"use warp::Filter;\nfn show() {}\n";
let tree = parse(src);
assert!(RustWarpAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
RustWarpAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -139,8 +139,10 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(ScheduledCronAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
ScheduledCronAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -15,10 +15,7 @@ const ADAPTER_NAME: &str = "scheduled-sidekiq";
fn callee_is_sidekiq(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"perform_async" | "perform_in" | "perform" | "set"
)
matches!(last, "perform_async" | "perform_in" | "perform" | "set")
}
fn source_imports_sidekiq(file_bytes: &[u8]) -> bool {

View file

@ -37,7 +37,12 @@ fn source_imports_actioncable(file_bytes: &[u8]) -> bool {
fn extract_path(file_bytes: &[u8]) -> String {
let text = std::str::from_utf8(file_bytes).unwrap_or("");
for needle in ["stream_from '", "stream_from \"", "stream_for '", "stream_for \""] {
for needle in [
"stream_from '",
"stream_from \"",
"stream_for '",
"stream_for \"",
] {
if let Some(idx) = text.find(needle) {
let after = &text[idx + needle.len()..];
let close = if needle.ends_with('"') { '"' } else { '\'' };

View file

@ -27,7 +27,10 @@ const ADAPTER_NAME: &str = "xpath-java";
fn callee_is_xpath_eval(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "evaluate" | "compile" | "selectNodes" | "selectSingleNode")
matches!(
last,
"evaluate" | "compile" | "selectNodes" | "selectSingleNode"
)
}
fn source_imports_xpath(file_bytes: &[u8]) -> bool {
@ -158,9 +161,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XpathJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -176,8 +181,10 @@ mod tests {
}\n}\n";
let tree = parse_java(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -142,9 +142,11 @@ mod tests {
}\nmodule.exports = { run };\n";
let tree = parse_js(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -155,9 +157,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,8 +172,10 @@ mod tests {
}\nmodule.exports = { run };\n";
let tree = parse_js(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -143,9 +143,11 @@ mod tests {
}\n";
let tree = parse_php(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -156,9 +158,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -172,8 +176,10 @@ mod tests {
}\n";
let tree = parse_php(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -25,7 +25,10 @@ const ADAPTER_NAME: &str = "xpath-python";
fn callee_is_xpath_eval(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "xpath" | "evaluate" | "find" | "findall" | "iterfind" | "XPath")
matches!(
last,
"xpath" | "evaluate" | "find" | "findall" | "iterfind" | "XPath"
)
}
fn source_imports_lxml(file_bytes: &[u8]) -> bool {
@ -141,9 +144,11 @@ mod tests {
return tree.xpath(\"//user[@name='\" + name + \"']\")\n";
let tree = parse_python(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -154,9 +159,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,8 +175,10 @@ mod tests {
return q(tree, name=name)\n";
let tree = parse_python(src);
let summary = summary_for("run", &["name"], &[0]);
assert!(XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XpathPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -113,9 +113,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("NewDecoder")],
..Default::default()
};
assert!(XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -126,9 +128,11 @@ mod tests {
name: "Add".into(),
..Default::default()
};
assert!(XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -145,8 +149,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("NewDecoder")],
..Default::default()
};
assert!(XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -161,9 +161,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -180,9 +182,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("parse")],
..Default::default()
};
assert!(XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -200,8 +204,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("parse")],
..Default::default()
};
assert!(XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -19,7 +19,9 @@ pub struct XxePhpAdapter;
const ADAPTER_NAME: &str = "xxe-php";
fn callee_is_xml_parser(name: &str) -> bool {
let last = name.rsplit_once("::").map(|(_, s)| s)
let last = name
.rsplit_once("::")
.map(|(_, s)| s)
.or_else(|| name.rsplit_once('.').map(|(_, s)| s))
.or_else(|| name.rsplit_once("->").map(|(_, s)| s))
.unwrap_or(name);
@ -137,16 +139,19 @@ mod tests {
#[test]
fn fires_on_simplexml_load_string() {
let src: &[u8] = b"<?php\nfunction run($body) {\n return simplexml_load_string($body);\n}\n";
let src: &[u8] =
b"<?php\nfunction run($body) {\n return simplexml_load_string($body);\n}\n";
let tree = parse_php(src);
let summary = FuncSummary {
name: "run".into(),
callees: vec![crate::summary::CalleeSite::bare("simplexml_load_string")],
..Default::default()
};
assert!(XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -157,9 +162,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -173,9 +180,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("simplexml_load_string")],
..Default::default()
};
assert!(XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -188,9 +197,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("simplexml_load_string")],
..Default::default()
};
assert!(XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -206,8 +217,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("simplexml_load_string")],
..Default::default()
};
assert!(XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxePhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
}

View file

@ -23,12 +23,7 @@ fn callee_is_xml_parser(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"XMLParser"
| "parse"
| "fromstring"
| "parseString"
| "XMLPullParser"
| "iterparse"
"XMLParser" | "parse" | "fromstring" | "parseString" | "XMLPullParser" | "iterparse"
)
}
@ -126,9 +121,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("fromstring")],
..Default::default()
};
assert!(XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -139,9 +136,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -156,9 +155,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("fromstring")],
..Default::default()
};
assert!(XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -171,8 +172,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("fromstring")],
..Default::default()
};
assert!(XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxePythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -17,7 +17,9 @@ pub struct XxeRubyAdapter;
const ADAPTER_NAME: &str = "xxe-ruby";
fn callee_is_xml_parser(name: &str) -> bool {
let last = name.rsplit_once("::").map(|(_, s)| s)
let last = name
.rsplit_once("::")
.map(|(_, s)| s)
.or_else(|| name.rsplit_once('.').map(|(_, s)| s))
.unwrap_or(name);
matches!(last, "new" | "parse" | "XML" | "load")
@ -124,9 +126,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("new")],
..Default::default()
};
assert!(XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -137,9 +141,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -153,9 +159,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("new")],
..Default::default()
};
assert!(XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,9 +176,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("XML")],
..Default::default()
};
assert!(XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -183,8 +193,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("XML")],
..Default::default()
};
assert!(XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
XxeRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
}