diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index 3a7b3ae..e320d7f 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -6,38 +6,37 @@ The details of Language Specific configuration are not exposed to the user. """ import asyncio -from collections import defaultdict -from copy import copy import dataclasses -from fnmatch import fnmatch import hashlib import json -import pickle import logging import os import pathlib +import pickle import re import threading +from collections import defaultdict from contextlib import asynccontextmanager, contextmanager +from copy import copy +from fnmatch import fnmatch +from pathlib import Path, PurePath +from typing import AsyncIterator, Dict, Iterator, List, Optional, Tuple, Union, cast -from serena.text_utils import MatchedConsecutiveLines, LineType, TextLine, search_text -from .lsp_protocol_handler.lsp_constants import LSPConstants -from .lsp_protocol_handler import lsp_types as LSPTypes - +from serena.text_utils import LineType, MatchedConsecutiveLines, TextLine, search_text from . import multilspy_types -from .multilspy_logger import MultilspyLogger +from .lsp_protocol_handler import lsp_types as LSPTypes +from .lsp_protocol_handler.lsp_constants import LSPConstants +from .lsp_protocol_handler.lsp_types import SymbolKind from .lsp_protocol_handler.server import ( LanguageServerHandler, ProcessLaunchInfo, ) -from .multilspy_config import MultilspyConfig, Language +from .multilspy_config import Language, MultilspyConfig from .multilspy_exceptions import MultilspyException -from .multilspy_utils import PathUtils, FileUtils, TextUtils -from pathlib import Path, PurePath -from typing import AsyncIterator, Iterator, List, Dict, Optional, TypedDict, Union, Tuple, cast +from .multilspy_logger import MultilspyLogger +from .multilspy_utils import FileUtils, PathUtils, TextUtils from .type_helpers import ensure_all_methods_implemented - # Serena dependencies # We will need to watch out for circular imports, but it's probably better to not # move all generic util code from serena into multilspy. @@ -664,7 +663,7 @@ class LanguageServer: :param relative_file_path: The relative path of the file that has the symbols :param include_body: whether to include the body of the symbols in the result. - :return: A list of symbols in the file, and a list of root symbols that represent the tree structure of the symbols. + :return: A list of symbols in the file, and a list of root symbols that represent the tree structure of the symbols. Each symbol in hierarchy starting from the roots has a children attribute. All symbols will have a location and a children attribute. """ @@ -712,6 +711,10 @@ class LanguageServer: assert isinstance(response, list) root_nodes: List[multilspy_types.UnifiedSymbolInformation] = [] for item in response: + if "range" not in item and "location" not in item: + if item["kind"] in [SymbolKind.File, SymbolKind.Module]: + ... + turn_item_into_symbol_with_children(item) item = cast(multilspy_types.UnifiedSymbolInformation, item) root_nodes.append(item) @@ -753,7 +756,7 @@ class LanguageServer: Will ignore all directories that start with a dot (.) and __pycache__ directories. Args: - start_package_relative_path: if passed, only the symbols within this directory will be considered. + start_dir_relative_path: if passed, only the symbols within this directory will be considered. include_body: whether to include the body of the symbols in the result. Returns: @@ -829,12 +832,17 @@ class LanguageServer: fix_relative_path(root_nodes) # Create file symbol + file_rel_path = str(Path(abs_item_path).resolve().relative_to(self.repository_root_path)) + with self.open_file(file_rel_path) as file_data: + fileRange = self._get_range_from_file_content(file_data.contents) file_symbol = multilspy_types.UnifiedSymbolInformation( # type: ignore name=os.path.splitext(item)[0], kind=multilspy_types.SymbolKind.File, + range=fileRange, + selectionRange=fileRange, location=multilspy_types.Location( uri=str(pathlib.Path(abs_item_path).as_uri()), - range={"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, + range=fileRange, absolutePath=str(abs_item_path), relativePath=str(Path(abs_item_path).resolve().relative_to(self.repository_root_path)), ), @@ -848,7 +856,20 @@ class LanguageServer: # Start from the root or the specified directory start_path = start_dir_relative_path or "." return await process_directory(start_path) - + + @staticmethod + def _get_range_from_file_content(file_content: str) -> multilspy_types.Range: + """ + Get the range for the given file. + """ + lines = file_content.split("\n") + end_line = len(lines) + end_column = len(lines[-1]) + return multilspy_types.Range( + start=multilspy_types.Position(line=0, character=0), + end=multilspy_types.Position(line=end_line, character=end_column) + ) + async def request_dir_overview(self, relative_dir_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: """ An overview of the given directory. @@ -1049,6 +1070,7 @@ class LanguageServer: include_imports: bool = True, include_self: bool = False, include_body: bool = False, + include_file_symbols: bool = False, ) -> List[multilspy_types.UnifiedSymbolInformation]: """ Finds all symbols that reference the symbol at the given location. @@ -1064,6 +1086,8 @@ class LanguageServer: :param include_self: whether to include the references that is the "input symbol" itself. Only has an effect if the relative_file_path, line and column point to a symbol, for example a definition. :param include_body: whether to include the body of the symbols in the result. + :param include_file_symbols: whether to include references that are file symbols. This + is often a fallback mechanism for when the reference cannot be resolved to a symbol. :return: List of symbols that reference the target symbol. """ if not self.server_started: @@ -1115,9 +1139,41 @@ class LanguageServer: containing_symbol["location"] = ref containing_symbol["range"] = ref["range"] break - if containing_symbol is None: - self.logger.log(f"Could not find containing symbol for {ref_path}:{ref_line}:{ref_col}", logging.WARNING) + + # We failed retrieving the symbol, falling back to creating a file symbol + if containing_symbol is None and include_file_symbols: + self.logger.log( + f"Could not find containing symbol for {ref_path}:{ref_line}:{ref_col}. Returning file symbol instead", + logging.WARNING + ) + fileRange = self._get_range_from_file_content(file_data.contents) + location = multilspy_types.Location( + uri=str(pathlib.Path(os.path.join(self.repository_root_path, ref_path)).as_uri()), + range=fileRange, + absolutePath=str(os.path.join(self.repository_root_path, ref_path)), + relativePath=ref_path, + ) + name = os.path.splitext(os.path.basename(ref_path))[0] + + if include_body: + body = self.retrieve_full_file_content(ref_path) + else: + body = "" + + containing_symbol = multilspy_types.UnifiedSymbolInformation( + kind=multilspy_types.SymbolKind.File, + range=fileRange, + selectionRange=fileRange, + location=location, + name=name, + children=[], + body=body, + ) + if containing_symbol is None or not include_file_symbols and containing_symbol["kind"] == multilspy_types.SymbolKind.File: continue + + assert "location" in containing_symbol + assert "selectionRange" in containing_symbol # Checking for self-reference if ( @@ -1198,7 +1254,7 @@ class LanguageServer: f"Passing empty lines to request_container_symbol is currently not supported, {relative_file_path=}, {line=}", logging.ERROR, ) - return + return None symbols, _ = await self.request_document_symbols(relative_file_path) @@ -1220,7 +1276,7 @@ class LanguageServer: assert "range" in location location["absolutePath"] = absolute_file_path location["relativePath"] = relative_file_path - location["uri"] = f"file:/{absolute_file_path}" + location["uri"] = Path(absolute_file_path).as_uri() # Allowed container kinds, currently only for Python container_symbol_kinds = { @@ -1610,6 +1666,7 @@ class SyncLanguageServer: self, relative_file_path: str, line: int, column: int, include_imports: bool = True, include_self: bool = False, include_body: bool = False, + include_file_symbols: bool = False, ) -> List[multilspy_types.UnifiedSymbolInformation]: """ Finds all symbols that reference the symbol at the given location. @@ -1625,6 +1682,8 @@ class SyncLanguageServer: :param include_self: whether to include the references that is the "input symbol" itself. Only has an effect if the relative_file_path, line and column point to a symbol, for example a definition. :param include_body: whether to include the body of the symbols in the result. + :param include_file_symbols: whether to include references that are file symbols. This + is often a fallback mechanism for when the reference cannot be resolved to a symbol. :return: List of symbols that reference the target symbol. """ assert self.loop @@ -1636,6 +1695,7 @@ class SyncLanguageServer: include_imports=include_imports, include_self=include_self, include_body=include_body, + include_file_symbols=include_file_symbols, ), self.loop ).result() diff --git a/test/multilspy/test_basic.py b/test/multilspy/test_basic.py index 78df934..8b74bce 100644 --- a/test/multilspy/test_basic.py +++ b/test/multilspy/test_basic.py @@ -5,6 +5,7 @@ These tests validate the functionality of the language server APIs like request_references using the test repository. """ +import os from pathlib import Path from multilspy.language_server import SyncLanguageServer @@ -17,7 +18,7 @@ class TestLanguageServerBasics: def test_request_references_user_class(self, language_server: SyncLanguageServer, repo_path: Path): """Test request_references on the User class.""" # Get references to the User class in models.py - file_path = str(repo_path / "test_repo" / "models.py") + file_path = os.path.join("test_repo", "models.py") # Line 31 contains the User class definition references = language_server.request_references(file_path, 31, 6) @@ -30,7 +31,7 @@ class TestLanguageServerBasics: def test_request_references_item_class(self, language_server: SyncLanguageServer, repo_path: Path): """Test request_references on the Item class.""" # Get references to the Item class in models.py - file_path = str(repo_path / "test_repo" / "models.py") + file_path = os.path.join("test_repo", "models.py") # Line 56 contains the Item class definition references = language_server.request_references(file_path, 56, 6) @@ -44,7 +45,7 @@ class TestLanguageServerBasics: def test_request_references_function_parameter(self, language_server: SyncLanguageServer, repo_path: Path): """Test request_references on a function parameter.""" # Get references to the id parameter in get_user method - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 24 contains the get_user method with id parameter references = language_server.request_references(file_path, 24, 16) @@ -53,7 +54,7 @@ class TestLanguageServerBasics: def test_request_references_create_user_method(self, language_server: SyncLanguageServer, repo_path: Path): # Get references to the create_user method in UserService - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 15 contains the create_user method definition references = language_server.request_references(file_path, 15, 9) @@ -62,7 +63,7 @@ class TestLanguageServerBasics: def test_retrieve_content_around_line(self, language_server: SyncLanguageServer, repo_path: Path): """Test retrieve_content_around_line functionality with various scenarios.""" - file_path = str(repo_path / "test_repo" / "models.py") + file_path = os.path.join("test_repo", "models.py") # Scenario 1: Just a single line (User class definition) line_31 = language_server.retrieve_content_around_line(file_path, 31) diff --git a/test/multilspy/test_symbol_retrieval.py b/test/multilspy/test_symbol_retrieval.py index afeb728..dd630b5 100644 --- a/test/multilspy/test_symbol_retrieval.py +++ b/test/multilspy/test_symbol_retrieval.py @@ -7,7 +7,6 @@ These tests focus on the following methods: """ import os -from pathlib import Path from multilspy.language_server import SyncLanguageServer from multilspy.multilspy_types import SymbolKind @@ -16,10 +15,10 @@ from multilspy.multilspy_types import SymbolKind class TestLanguageServerSymbols: """Test the language server's symbol-related functionality.""" - def test_request_containing_symbol_function(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_containing_symbol_function(self, language_server: SyncLanguageServer): """Test request_containing_symbol for a function.""" # Test for a position inside the create_user method - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 17 is inside the create_user method body containing_symbol = language_server.request_containing_symbol(file_path, 17, 20, include_body=True) @@ -30,9 +29,9 @@ class TestLanguageServerSymbols: if "body" in containing_symbol: assert containing_symbol["body"].strip().startswith("def create_user(self") - def test_references_to_variables(self, language_server: SyncLanguageServer, repo_path: Path): + def test_references_to_variables(self, language_server: SyncLanguageServer): """Test request_referencing_symbols for a variable.""" - file_path = str(repo_path / "test_repo" / "variables.py") + file_path = os.path.join("test_repo", "variables.py") # Line 75 contains the field status that is later modified ref_symbols = language_server.request_referencing_symbols(file_path, 74, 4) @@ -44,10 +43,10 @@ class TestLanguageServerSymbols: assert "dataclass_instance" in ref_names assert "second_dataclass" in ref_names - def test_request_containing_symbol_class(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_containing_symbol_class(self, language_server: SyncLanguageServer): """Test request_containing_symbol for a class.""" # Test for a position inside the UserService class but outside any method - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 9 is the class definition line for UserService containing_symbol = language_server.request_containing_symbol(file_path, 9, 7) @@ -56,10 +55,10 @@ class TestLanguageServerSymbols: assert containing_symbol["name"] == "UserService" assert containing_symbol["kind"] == SymbolKind.Class - def test_request_containing_symbol_nested(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_containing_symbol_nested(self, language_server: SyncLanguageServer): """Test request_containing_symbol with nested scopes.""" # Test for a position inside a method which is inside a class - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 18 is inside the create_user method inside UserService class containing_symbol = language_server.request_containing_symbol(file_path, 18, 25) @@ -81,20 +80,20 @@ class TestLanguageServerSymbols: assert parent_symbol["name"] == "UserService" assert parent_symbol["kind"] == SymbolKind.Class - def test_request_containing_symbol_none(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_containing_symbol_none(self, language_server: SyncLanguageServer): """Test request_containing_symbol for a position with no containing symbol.""" # Test for a position outside any function/class (e.g., in imports) - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 1 is in imports, not inside any function or class containing_symbol = language_server.request_containing_symbol(file_path, 1, 10) # Should return None or an empty dictionary assert containing_symbol is None or containing_symbol == {} - def test_request_referencing_symbols_function(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_referencing_symbols_function(self, language_server: SyncLanguageServer): """Test request_referencing_symbols for a function.""" # Test referencing symbols for create_user function - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 15 contains the create_user function definition ref_symbols = language_server.request_referencing_symbols(file_path, 15, 9) @@ -109,10 +108,10 @@ class TestLanguageServerSymbols: assert "start" in symbol["location"]["range"] assert "end" in symbol["location"]["range"] - def test_request_referencing_symbols_class(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_referencing_symbols_class(self, language_server: SyncLanguageServer): """Test request_referencing_symbols for a class.""" # Test referencing symbols for User class - file_path = str(repo_path / "test_repo" / "models.py") + file_path = os.path.join("test_repo", "models.py") # Line 31 contains the User class definition ref_symbols = language_server.request_referencing_symbols(file_path, 31, 6) @@ -128,10 +127,10 @@ class TestLanguageServerSymbols: assert len(services_references) > 0 - def test_request_referencing_symbols_parameter(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_referencing_symbols_parameter(self, language_server: SyncLanguageServer): """Test request_referencing_symbols for a function parameter.""" # Test referencing symbols for id parameter in get_user - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 24 contains the get_user method with id parameter ref_symbols = language_server.request_referencing_symbols(file_path, 24, 16) @@ -147,12 +146,12 @@ class TestLanguageServerSymbols: assert len(method_refs) > 0 - def test_request_referencing_symbols_none(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_referencing_symbols_none(self, language_server: SyncLanguageServer): """Test request_referencing_symbols for a position with no symbol.""" # For positions with no symbol, the method might throw an error or return None/empty list # We'll modify our test to handle this by using a try-except block - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 3 is a blank line or comment try: ref_symbols = language_server.request_referencing_symbols(file_path, 3, 0) @@ -164,10 +163,10 @@ class TestLanguageServerSymbols: pass # Tests for request_defining_symbol - def test_request_defining_symbol_variable(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_defining_symbol_variable(self, language_server: SyncLanguageServer): """Test request_defining_symbol for a variable usage.""" # Test finding the definition of a symbol in the create_user method - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 21 contains self.users[id] = user defining_symbol = language_server.request_defining_symbol(file_path, 21, 10) @@ -182,10 +181,10 @@ class TestLanguageServerSymbols: if "location" in defining_symbol and "uri" in defining_symbol["location"]: assert "services.py" in defining_symbol["location"]["uri"] - def test_request_defining_symbol_imported_class(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_defining_symbol_imported_class(self, language_server: SyncLanguageServer): """Test request_defining_symbol for an imported class.""" # Test finding the definition of the 'User' class used in the UserService.create_user method - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 20 references 'User' which was imported from models defining_symbol = language_server.request_defining_symbol(file_path, 20, 15) @@ -193,10 +192,10 @@ class TestLanguageServerSymbols: assert defining_symbol is not None assert defining_symbol.get("name") == "User" - def test_request_defining_symbol_method_call(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_defining_symbol_method_call(self, language_server: SyncLanguageServer): """Test request_defining_symbol for a method call.""" # Create an example file path for a file that calls UserService.create_user - examples_file_path = str(repo_path / "examples" / "user_management.py") + examples_file_path = os.path.join("examples", "user_management.py") # Find the line number where create_user is called # This could vary, so we'll use a relative position that makes sense @@ -217,20 +216,20 @@ class TestLanguageServerSymbols: warnings.warn("Could not verify method call definition - file structure may differ from expected") - def test_request_defining_symbol_none(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_defining_symbol_none(self, language_server: SyncLanguageServer): """Test request_defining_symbol for a position with no symbol.""" # Test for a position with no symbol (e.g., whitespace or comment) - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 3 is a blank line defining_symbol = language_server.request_defining_symbol(file_path, 3, 0) # Should return None for positions with no symbol assert defining_symbol is None - def test_request_containing_symbol_variable(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_containing_symbol_variable(self, language_server: SyncLanguageServer): """Test request_containing_symbol where the symbol is a variable.""" # Test for a position inside a variable definition - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # Line 74 defines the 'user' variable containing_symbol = language_server.request_containing_symbol(file_path, 73, 1) @@ -239,10 +238,10 @@ class TestLanguageServerSymbols: assert containing_symbol["name"] == "user_var_str" assert containing_symbol["kind"] == SymbolKind.Variable - def test_request_defining_symbol_nested_function(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_defining_symbol_nested_function(self, language_server: SyncLanguageServer): """Test request_defining_symbol for a nested function or closure.""" # Use the existing nested.py file which contains nested classes and methods - file_path = str(repo_path / "test_repo" / "nested.py") + file_path = os.path.join("test_repo", "nested.py") # Test 1: Find definition of nested method - line with 'b = OuterClass().NestedClass().find_me()' defining_symbol = language_server.request_defining_symbol(file_path, 15, 35) # Position of find_me() call @@ -289,12 +288,12 @@ class TestLanguageServerSymbols: assert defining_symbol.get("name") == "func_within_func" assert defining_symbol.get("kind") == SymbolKind.Function.value - def test_symbol_methods_integration(self, language_server: SyncLanguageServer, repo_path: Path): + def test_symbol_methods_integration(self, language_server: SyncLanguageServer): """Test the integration between different symbol-related methods.""" # This test demonstrates using the various symbol methods together # by finding a symbol and then checking its definition - file_path = str(repo_path / "test_repo" / "services.py") + file_path = os.path.join("test_repo", "services.py") # First approach: Use a method from the UserService class # Step 1: Find a method we know exists @@ -337,7 +336,7 @@ class TestLanguageServerSymbols: warnings.warn("Could not verify container hierarchy - implementation detail") - def test_symbol_tree_structure(self, language_server: SyncLanguageServer, repo_path: Path): + def test_symbol_tree_structure(self, language_server: SyncLanguageServer): """Test that the symbol tree structure is correctly built.""" # Get all symbols in the test file repo_structure = language_server.request_full_symbol_tree() @@ -361,13 +360,13 @@ class TestLanguageServerSymbols: if "location" in user_management_node and "relativePath" in user_management_node["location"]: user_management_rel_path = user_management_node["location"]["relativePath"] assert user_management_rel_path == os.path.join("examples", "user_management.py") - _, user_management_roots = language_server.request_document_symbols(str(repo_path / "examples" / "user_management.py")) + _, user_management_roots = language_server.request_document_symbols(os.path.join("examples", "user_management.py")) assert user_management_roots == user_management_node["children"] - def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer, repo_path: Path): + def test_symbol_tree_structure_subdir(self, language_server: SyncLanguageServer): """Test that the symbol tree structure is correctly built.""" # Get all symbols in the test file - examples_package_roots = language_server.request_full_symbol_tree(start_package_relative_path=str(repo_path / "examples")) + examples_package_roots = language_server.request_full_symbol_tree(start_package_relative_path="examples") assert len(examples_package_roots) == 1 examples_package = examples_package_roots[0] assert examples_package["name"] == "examples" @@ -381,10 +380,10 @@ class TestLanguageServerSymbols: if "location" in user_management_node and "relativePath" in user_management_node["location"]: user_management_rel_path = user_management_node["location"]["relativePath"] assert user_management_rel_path == os.path.join("examples", "user_management.py") - _, user_management_roots = language_server.request_document_symbols(str(repo_path / "examples" / "user_management.py")) + _, user_management_roots = language_server.request_document_symbols(os.path.join("examples", "user_management.py")) assert user_management_roots == user_management_node["children"] - def test_request_dir_overview(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_dir_overview(self, language_server: SyncLanguageServer): """Test that request_dir_overview returns correct symbol information for files in a directory.""" # Get overview of the examples directory overview = language_server.request_dir_overview("test_repo") @@ -408,7 +407,7 @@ class TestLanguageServerSymbols: for symbol in expected_symbols: assert symbol in services_symbols - def test_request_document_overview(self, language_server: SyncLanguageServer, repo_path: Path): + def test_request_document_overview(self, language_server: SyncLanguageServer): """Test that request_document_overview returns correct symbol information for a file.""" # Get overview of the user_management.py file overview = language_server.request_document_overview(os.path.join("examples", "user_management.py")) @@ -416,3 +415,21 @@ class TestLanguageServerSymbols: # Verify that we have entries for both files symbol_names = {s_info[0] for s_info in overview} assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names) + + def test_containing_symbol_of_var_is_file(self, language_server: SyncLanguageServer): + """Test that the containing symbol of a variable is the file itself.""" + # Get the containing symbol of a variable in a file + file_path = os.path.join("test_repo", "services.py") + # import of typing + references_to_typing = language_server.request_referencing_symbols( + file_path, 4, 6, include_imports=False, include_file_symbols=True + ) + assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File} + assert {ref["body"] for ref in references_to_typing} == {""} + + # now include bodies + references_to_typing = language_server.request_referencing_symbols( + file_path, 4, 6, include_imports=False, include_file_symbols=True, include_body=True + ) + assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File} + assert references_to_typing[0]["body"]