Merge pull request #1799 from a-rookie-of-C-language/fix/mybatis-annotation-method-name

fix(mybatis): skip annotation arguments when parsing mapper method names
This commit is contained in:
arookieofc
2026-11-06 20:31:56 +08:00
committed by GitHub
3 changed files with 58 additions and 2 deletions
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
c29594e55d321ee5240f8f61223d72c9095695a5b19ff1f8a25c5a6402a4a736
5e7f7adc2c49503d2278ed2a446f18f69561ce51df264100f77f50d69804c277
+57 -1
View File
@@ -70,6 +70,51 @@ fn ident(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '$'
}
/// Index of the `(` opening a method's parameter list inside a declaration
/// fragment. `(`s that belong to annotation arguments — `@Anno(…)` — are
/// skipped so `@DataPermission(column = "id") int count()` finds the paren
/// after `count`, not the one after `DataPermission`.
fn method_paren(stmt: &str) -> Option<usize> {
let mut rest = stmt;
let mut base = 0;
while let Some(i) = rest.find('(') {
// Is the token right before this `(` an annotation name (`@Anno`, `@a.b.Anno`)?
let before = rest[..i].trim_end();
let annotation = before.rfind('@').is_some_and(|a| before[a + 1..].chars().all(|c| ident(c) || c == '.'));
if !annotation {
return Some(base + i);
}
// Skip the annotation's argument list (it may nest and hold literals).
let mut depth = 1usize;
let mut end = None;
let mut chars = rest[i + 1..].char_indices().peekable();
while let Some((j, c)) = chars.next() {
match c {
'"' | '\'' => {
for (_, d) in chars.by_ref() {
if d == c {
break;
}
}
}
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
end = Some(i + 1 + j + 1);
break;
}
}
_ => {}
}
}
let skip = end?;
base += skip;
rest = &rest[skip..];
}
None
}
/// The interface's name and methods (with their parameter names).
pub fn parse_java(text: &str) -> MapperJava {
let package =
@@ -95,7 +140,7 @@ pub fn parse_java(text: &str) -> MapperJava {
}
';' if depth == 0 => {
let stmt = &text[stmt_start..k];
if let Some(paren) = stmt.find('(') {
if let Some(paren) = method_paren(stmt) {
let before = stmt[..paren].trim_end();
let name_start = before.rfind(|c: char| !ident(c)).map_or(0, |p| p + 1);
let method = &before[name_start..];
@@ -361,6 +406,17 @@ mod tests {
assert_eq!(n.line_markers(&xml)[0].tooltip, "Go to UserMapper.findById()");
}
#[test]
fn method_annotations_do_not_swallow_the_method_name() {
// A method-level annotation with arguments: `stmt.find('(')` would land
// inside `@DataPermission(…)` and read `DataPermission` as the name.
let java = parse_java(
"package com.acme;\n\npublic interface SecuredMapper {\n @DataPermission(column = \"id\", scope = (@Scope(\"self\")))\n List<User> findAll(@Param(\"org\") String org);\n\n @org.acme.Check(value = \"(nested)\")\n int count();\n}\n",
);
let names: Vec<_> = java.methods.iter().map(|(m, p)| (m.name.as_str(), p.clone())).collect();
assert_eq!(names, [("findAll", vec!["org".to_owned()]), ("count", Vec::<String>::new())]);
}
#[test]
fn completes_ids_and_parameters() {
let _host = setup();