mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
cargo fmt
This commit is contained in:
parent
bec7bbf96c
commit
3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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('"') {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(_)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 { '\'' };
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue