mirror of
https://github.com/tiennm99/serena.git
synced 2026-08-02 02:21:42 +00:00
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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user