diff --git a/lsproxy/Cargo.toml b/lsproxy/Cargo.toml index 633c0e8e..3fe0d2c0 100644 --- a/lsproxy/Cargo.toml +++ b/lsproxy/Cargo.toml @@ -23,8 +23,8 @@ serde_json = "1.0" strum = "0.25" strum_macros = "0.25" tokio = { version = "1.0", features = ["full", "process"] } -utoipa = { version = "5.0", features = ["actix_extras"] } -utoipa-swagger-ui = { version = "9.0.1", features = ["actix-web"] } +utoipa = { git = "https://github.com/juhaku/utoipa.git", rev = "cecda0531bf7d90800af66b186055932ee730526", features = ["actix_extras"] } +utoipa-swagger-ui = { git = "https://github.com/juhaku/utoipa.git", rev = "cecda0531bf7d90800af66b186055932ee730526", features = ["actix-web"] } rand = "0.8" async-trait = "0.1" futures = "0.3" diff --git a/lsproxy/src/ast_grep/symbol/rules/javascript/function.yml b/lsproxy/src/ast_grep/symbol/rules/javascript/function.yml index 4b784b52..079f0b59 100644 --- a/lsproxy/src/ast_grep/symbol/rules/javascript/function.yml +++ b/lsproxy/src/ast_grep/symbol/rules/javascript/function.yml @@ -24,5 +24,7 @@ rule: inside: kind: pair has: - kind: arrow_function + any: + - kind: function_expression + - kind: arrow_function pattern: $CONTEXT diff --git a/lsproxy/src/ast_grep/symbol/rules/javascript/method.yml b/lsproxy/src/ast_grep/symbol/rules/javascript/method.yml index 4b4be26b..a795908b 100644 --- a/lsproxy/src/ast_grep/symbol/rules/javascript/method.yml +++ b/lsproxy/src/ast_grep/symbol/rules/javascript/method.yml @@ -1,8 +1,13 @@ id: method language: javascript rule: - kind: identifier - pattern: $NAME - inside: - kind: method_definition - pattern: $CONTEXT + pattern: $NAME + any: + - kind: identifier + inside: + kind: method_definition + pattern: $CONTEXT + - kind: property_identifier + inside: + kind: method_definition + pattern: $CONTEXT diff --git a/lsproxy/src/lsp/manager/language_tests/js_tests.rs b/lsproxy/src/lsp/manager/language_tests/js_tests.rs index 39273649..3e8ee301 100644 --- a/lsproxy/src/lsp/manager/language_tests/js_tests.rs +++ b/lsproxy/src/lsp/manager/language_tests/js_tests.rs @@ -17,7 +17,7 @@ async fn test_workspace_files() -> Result<(), Box> { .ok_or("Manager is not initialized")?; let files = manager.list_files().await?; - assert_eq!(files, vec!["astar_search.js"]); + assert_eq!(files, vec!["astar_search.js", "functions.js", "methods.js"]); Ok(()) } @@ -246,3 +246,548 @@ async fn test_file_symbols() -> Result<(), Box> { assert_eq!(symbol_response, expected); Ok(()) } + +#[tokio::test] +async fn test_file_symbols_functions_js() -> Result<(), Box> { + let context = TestContext::setup(&js_sample_path(), true).await?; + let manager = context + .manager + .as_ref() + .ok_or("Manager is not initialized")?; + + let file_path = "functions.js"; + let file_symbols = manager.definitions_in_file_ast_grep(file_path).await?; + let mut symbol_response: SymbolResponse = file_symbols.into_iter().map(Symbol::from).collect(); + + let mut expected = vec![ + Symbol { + name: "objWithFuncExpr".to_string(), + kind: "variable".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 1, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 1, + character: 0, + }, + end: Position { + line: 5, + character: 1, + }, + }, + }, + }, + Symbol { + name: "propFuncExpr".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 2, + character: 2, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 2, + character: 0, + }, + end: Position { + line: 4, + character: 3, + }, + }, + }, + }, + Symbol { + name: "objWithArrowFunc".to_string(), + kind: "variable".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 8, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 8, + character: 0, + }, + end: Position { + line: 10, + character: 1, + }, + }, + }, + }, + Symbol { + name: "propArrowFunc".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 9, + character: 2, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 9, + character: 0, + }, + end: Position { + line: 9, + character: 25, + }, + }, + }, + }, + Symbol { + name: "topLevelStandardFunction".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 13, + character: 9, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 13, + character: 0, + }, + end: Position { + line: 13, + character: 38, + }, + }, + }, + }, + Symbol { + name: "topLevelArrowConst".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 16, + character: 6, + }, + }, + file_range: FileRange { + // Assuming range covers the 'const ...;' declaration + path: file_path.to_string(), + range: Range { + start: Position { + line: 16, + character: 0, + }, + end: Position { + line: 16, + character: 35, + }, + }, + }, + }, + Symbol { + name: "namedInnerFuncExpr".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 19, + character: 39, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 19, + character: 0, + }, + end: Position { + line: 19, + character: 62, + }, + }, + }, + }, + Symbol { + name: "topLevelFuncExprConst".to_string(), + kind: "variable".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 19, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 19, + character: 0, + }, + end: Position { + line: 19, + character: 62, + }, + }, + }, + }, + Symbol { + name: "assignedArrowLet".to_string(), + kind: "variable".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 22, + character: 4, + }, + }, + file_range: FileRange { + // Range of the assignment expression + path: file_path.to_string(), + range: Range { + start: Position { + line: 22, + character: 0, + }, + end: Position { + line: 22, + character: 20, + }, + }, + }, + }, + Symbol { + name: "assignedArrowLet".to_string(), + kind: "function".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 23, + character: 0, + }, + }, + file_range: FileRange { + // Range of the assignment expression + path: file_path.to_string(), + range: Range { + start: Position { + line: 23, + character: 0, + }, + end: Position { + line: 23, + character: 27, + }, + }, + }, + }, + ]; + + symbol_response.sort_by_key(|s| s.name.clone()); + expected.sort_by_key(|s| s.name.clone()); + + if symbol_response != expected { + eprintln!("Actual symbols count: {}", symbol_response.len()); + eprintln!("Expected symbols count: {}", expected.len()); + for i in 0..std::cmp::max(symbol_response.len(), expected.len()) { + eprintln!("--- Symbol {} ---", i); + if i < symbol_response.len() { + eprintln!("Actual: {:?}", symbol_response[i]); + } else { + eprintln!("Actual: None"); + } + if i < expected.len() { + eprintln!("Expected: {:?}", expected[i]); + } else { + eprintln!("Expected: None"); + } + } + } + + assert_eq!( + symbol_response, expected, + "Symbols from functions.js do not match expected symbols." + ); + Ok(()) +} + +#[tokio::test] +async fn test_file_symbols_methods_js() -> Result<(), Box> { + let context = TestContext::setup(&js_sample_path(), true).await?; + let manager = context + .manager + .as_ref() + .ok_or("Manager is not initialized")?; + + let file_path = "methods.js"; + let file_symbols = manager.definitions_in_file_ast_grep(file_path).await?; + let mut symbol_response: SymbolResponse = file_symbols.into_iter().map(Symbol::from).collect(); + + let mut expected = vec![ + Symbol { + name: "MyClassExample".to_string(), + kind: "class".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 1, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 1, + character: 0, + }, + end: Position { + line: 13, + character: 1, + }, + }, + }, + }, + Symbol { + name: "classMethodRegular".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 2, + character: 2, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 2, + character: 0, + }, + end: Position { + line: 2, + character: 25, + }, + }, + }, + }, + Symbol { + name: "staticClassMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 4, + character: 9, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 4, + character: 0, + }, + end: Position { + line: 4, + character: 31, + }, + }, + }, + }, + Symbol { + name: "getterMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 6, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 6, + character: 0, + }, + end: Position { + line: 8, + character: 3, + }, + }, + }, + }, + Symbol { + name: "setterMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 10, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 10, + character: 0, + }, + end: Position { + line: 12, + character: 3, + }, + }, + }, + }, + Symbol { + name: "objWithShorthand".to_string(), + kind: "variable".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 16, + character: 6, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 16, + character: 0, + }, + end: Position { + line: 22, + character: 1, + }, + }, + }, + }, + Symbol { + name: "shorthandObjMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 17, + character: 2, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 17, + character: 0, + }, + end: Position { + line: 17, + character: 25, + }, + }, + }, + }, + Symbol { + name: "generatorShorthandMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 19, + character: 3, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 19, + character: 0, + }, + end: Position { + line: 19, + character: 32, + }, + }, + }, + }, + Symbol { + name: "asyncShorthandMethod".to_string(), + kind: "method".to_string(), + identifier_position: FilePosition { + path: file_path.to_string(), + position: Position { + line: 21, + character: 8, + }, + }, + file_range: FileRange { + path: file_path.to_string(), + range: Range { + start: Position { + line: 21, + character: 0, + }, + end: Position { + line: 21, + character: 33, + }, + }, + }, + }, + ]; + + symbol_response.sort_by_key(|s| s.name.clone()); + expected.sort_by_key(|s| s.name.clone()); + + if symbol_response != expected { + eprintln!("Actual symbols count: {}", symbol_response.len()); + eprintln!("Expected symbols count: {}", expected.len()); + for i in 0..std::cmp::max(symbol_response.len(), expected.len()) { + eprintln!("--- Symbol {} ---", i); + if i < symbol_response.len() { + eprintln!("Actual: {:?}", symbol_response[i]); + } else { + eprintln!("Actual: None"); + } + if i < expected.len() { + eprintln!("Expected: {:?}", expected[i]); + } else { + eprintln!("Expected: None"); + } + } + } + + assert_eq!( + symbol_response, expected, + "Symbols from methods.js do not match expected symbols." + ); + Ok(()) +} diff --git a/sample_project/js/functions.js b/sample_project/js/functions.js new file mode 100644 index 00000000..c3493f4a --- /dev/null +++ b/sample_project/js/functions.js @@ -0,0 +1,24 @@ +// Property identifier in a 'pair' with a 'function_expression' +const objWithFuncExpr = { + propFuncExpr: function () { + console.log("Hello, world!"); + }, +}; + +// Property identifier in a 'pair' with an 'arrow_function' +const objWithArrowFunc = { + propArrowFunc: () => {}, +}; + +// Top-level function declaration +function topLevelStandardFunction() {} + +// Variable declarator with an arrow function +const topLevelArrowConst = () => {}; + +// Variable declarator with a function expression +const topLevelFuncExprConst = function namedInnerFuncExpr() {}; + +// Assignment expression with an arrow function +let assignedArrowLet; +assignedArrowLet = () => {}; diff --git a/sample_project/js/methods.js b/sample_project/js/methods.js new file mode 100644 index 00000000..9a91bb11 --- /dev/null +++ b/sample_project/js/methods.js @@ -0,0 +1,23 @@ +// Property identifier in a 'method_definition' (class method) +class MyClassExample { + classMethodRegular() {} + + static staticClassMethod() {} + + get getterMethod() { + return this._x; + } + + set setterMethod(value) { + this._x = value; + } +} + +// Property identifier in a 'method_definition' (object shorthand method) +const objWithShorthand = { + shorthandObjMethod() {}, + + *generatorShorthandMethod() {}, + + async asyncShorthandMethod() {}, +};