Skip to content

Commit 802f26b

Browse files
committed
Fix @method parsing
1 parent 6fd0775 commit 802f26b

3 files changed

Lines changed: 84 additions & 1 deletion

File tree

src/definition/resolve.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,15 @@ impl Backend {
11331133
// The method name is followed by `(` in a @method tag.
11341134
let method_pattern = member_name;
11351135
for (line_idx, line) in content.lines().enumerate() {
1136-
if let Some(col) = line.find(method_pattern) {
1136+
// Search for ALL occurrences of the pattern within the line,
1137+
// not just the first one. This is important when the method
1138+
// name collides with a type keyword (e.g. `string`) that also
1139+
// appears as the return type on the same line.
1140+
let mut search_start = 0;
1141+
while let Some(offset) = line[search_start..].find(method_pattern) {
1142+
let col = search_start + offset;
1143+
search_start = col + method_pattern.len();
1144+
11371145
// Verify the character after the name is `(` (method call syntax).
11381146
let after_pos = col + method_pattern.len();
11391147
if after_pos >= line.len() {

src/docblock.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,22 @@ mod tests {
10431043
assert!(!methods[0].parameters[0].is_required);
10441044
}
10451045

1046+
#[test]
1047+
fn method_tag_name_matches_type_keyword() {
1048+
let doc = "/** @method static string string(string $key, \\Closure|string|null $default = null) */";
1049+
let methods = extract_method_tags(doc);
1050+
assert_eq!(methods.len(), 1);
1051+
assert_eq!(methods[0].name, "string");
1052+
assert_eq!(methods[0].return_type.as_deref(), Some("string"));
1053+
assert!(methods[0].is_static);
1054+
assert_eq!(methods[0].parameters.len(), 2);
1055+
assert_eq!(methods[0].parameters[0].name, "$key");
1056+
assert_eq!(
1057+
methods[0].parameters[0].type_hint.as_deref(),
1058+
Some("string")
1059+
);
1060+
}
1061+
10461062
// ─── @property tag extraction ───────────────────────────────────────
10471063

10481064
#[test]

tests/definition_members.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,3 +1304,62 @@ async fn test_goto_definition_this_property_vs_method_disambiguation() {
13041304
other => panic!("Expected Scalar location, got: {:?}", other),
13051305
}
13061306
}
1307+
1308+
// ── @method tag: method name matches a type keyword ─────────────────────────
1309+
1310+
#[tokio::test]
1311+
async fn test_goto_definition_method_tag_name_matches_type_keyword() {
1312+
let backend = create_test_backend();
1313+
1314+
let uri = Url::parse("file:///method_string.php").unwrap();
1315+
let text = concat!(
1316+
"<?php\n", // 0
1317+
"/**\n", // 1
1318+
" * @method static string string(string $key, \\Closure|string|null $default = null)\n", // 2
1319+
" */\n", // 3
1320+
"class Config {\n", // 4
1321+
"}\n", // 5
1322+
"\n", // 6
1323+
"Config::string('hello');\n", // 7
1324+
);
1325+
1326+
let open_params = DidOpenTextDocumentParams {
1327+
text_document: TextDocumentItem {
1328+
uri: uri.clone(),
1329+
language_id: "php".to_string(),
1330+
version: 1,
1331+
text: text.to_string(),
1332+
},
1333+
};
1334+
backend.did_open(open_params).await;
1335+
1336+
// Click on "string" in `Config::string('hello')` on line 7, character 8
1337+
let params = GotoDefinitionParams {
1338+
text_document_position_params: TextDocumentPositionParams {
1339+
text_document: TextDocumentIdentifier { uri: uri.clone() },
1340+
position: Position {
1341+
line: 7,
1342+
character: 10,
1343+
},
1344+
},
1345+
work_done_progress_params: WorkDoneProgressParams::default(),
1346+
partial_result_params: PartialResultParams::default(),
1347+
};
1348+
1349+
let result = backend.goto_definition(params).await.unwrap();
1350+
assert!(
1351+
result.is_some(),
1352+
"Should resolve Config::string() to the @method tag declaration"
1353+
);
1354+
1355+
match result.unwrap() {
1356+
GotoDefinitionResponse::Scalar(location) => {
1357+
assert_eq!(location.uri, uri);
1358+
assert_eq!(
1359+
location.range.start.line, 2,
1360+
"@method string string(...) is declared on line 2"
1361+
);
1362+
}
1363+
other => panic!("Expected Scalar location, got: {:?}", other),
1364+
}
1365+
}

0 commit comments

Comments
 (0)