LS: possibility to include file symbols in find_referencing_symbols

Some minor fixes and deduplication. Adjusted tests to only use relative paths.
This commit is contained in:
Michael Panchenko
2025-04-01 17:08:22 +02:00
parent fac2822de0
commit be29beaf38
3 changed files with 145 additions and 67 deletions
+82 -22
View File
@@ -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()
+6 -5
View File
@@ -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)
+57 -40
View File
@@ -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"]