Add swift lsp support

This commit is contained in:
Yangze Luo committed 2025-08-03 13:26:07 +08:00
1 parent 2ef00a7e0d
commit 2087bd3d1b
11 files changed
+933 -4

No files matched your search

+31 -3
View File
@@ -27,7 +27,7 @@ jobs:
python-version: "${{ matrix.python-version }}"
- uses: actions/setup-go@v5
with:
go-version: '>=1.17.0'
go-version: ">=1.17.0"
# Add Go bin directory to PATH for this workflow
# GITHUB_PATH is a special file that GitHub Actions uses to modify PATH
# Writing to this file adds the directory to the PATH for subsequent steps
@@ -47,8 +47,8 @@ jobs:
if: runner.os != 'Windows'
uses: erlef/setup-beam@v1
with:
elixir-version: '1.18.4'
otp-version: '26.1'
elixir-version: "1.18.4"
otp-version: "26.1"
- name: Prepare java
uses: actions/setup-java@v3
@@ -64,6 +64,34 @@ jobs:
with:
terraform_version: "1.5.0"
terraform_wrapper: false
- name: Install Swift with swiftly (macOS)
if: runner.os == 'macOS'
run: |
echo "=== Installing swiftly on macOS ==="
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
~/.swiftly/bin/swiftly init --quiet-shell-followup && \
. "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" && \
hash -r
swiftly install --use 6.1.2
swiftly use 6.1.2
echo "~/.swiftly/bin" >> $GITHUB_PATH
echo "Swiftly installed successfully"
- name: Install Swift with swiftly (Ubuntu)
if: runner.os == 'Linux'
run: |
echo "=== Installing swiftly on Ubuntu ==="
curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \
tar zxf swiftly-$(uname -m).tar.gz && \
./swiftly init --quiet-shell-followup && \
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \
hash -r
swiftly install --use 6.1.2
swiftly use 6.1.2
sudo apt-get -y install libcurl4-openssl-dev
echo "=== Adding Swift toolchain to PATH ==="
echo "$HOME/.local/share/swiftly/bin" >> $GITHUB_PATH
echo "Swiftly installed successfully!"
- name: Install uv
shell: bash
run: curl -LsSf https://astral.sh/uv/install.sh | sh
+4
View File
@@ -227,3 +227,7 @@ tmp/
/test/resources/repos/elixir/test_repo/deps
# Exception: Don't ignore Elixir test repository lib directory (contains source code)
!/test/resources/repos/elixir/test_repo/lib
# Swift
/test/resources/repos/swift/test_repo/.build
/test/resources/repos/swift/test_repo/.swiftpm
+1
View File
@@ -76,6 +76,7 @@ With Serena, we provide
* Elixir (Requires NextLS and Elixir install; **Windows not supported** - Next LS does not provide Windows binaries)
* Clojure
* C/C++ (You may experience issues with finding references, we are working on it)
* Swift (requires Swift and sourcekit-lsp to be installed)
* indirect support (may require some code changes/manual installation) for:
* Ruby (untested)
* Kotlin (untested)
+2 -1
View File
@@ -45,7 +45,7 @@ explicit = true
[project.scripts]
serena = "serena.cli:top_level"
serena-mcp-server = "serena.cli:start_mcp_server"
index-project = "serena.cli:index_project" # deprecated
index-project = "serena.cli:index_project" # deprecated
[project.license]
text = "MIT"
@@ -259,6 +259,7 @@ markers = [
"csharp: language server running for C#",
"elixir: language server running for Elixir",
"terraform: language server running for Terraform",
"swift: language server running for Swift",
"snapshot: snapshot tests for symbolic editing operations",
]
@@ -0,0 +1,561 @@
import logging
import os
import pathlib
import subprocess
import threading
from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler import lsp_types
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
class SourceKitLSP(SolidLanguageServer):
"""
Provides Swift specific instantiation of the LanguageServer class using sourcekit-lsp.
"""
@override
def is_ignored_dirname(self, dirname: str) -> bool:
# For Swift projects, we should ignore:
# - .build: Swift Package Manager build artifacts
# - .swiftpm: Swift Package Manager metadata
# - node_modules: if the project has JavaScript components
# - dist/build: common output directories
return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"]
@staticmethod
def _get_swift_version():
"""Get the installed Swift version or None if not found."""
try:
result = subprocess.run(["swift", "--version"], capture_output=True, text=True, check=False)
if result.returncode == 0:
return result.stdout.strip()
except FileNotFoundError:
return None
return None
@staticmethod
def _get_sourcekit_lsp_version():
"""Get the installed sourcekit-lsp version or None if not found."""
try:
result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False)
if result.returncode == 0:
return result.stdout.strip()
except FileNotFoundError:
return None
return None
@staticmethod
def _setup_runtime_dependency():
"""
Check if required Swift runtime dependencies are available.
Raises RuntimeError with helpful message if dependencies are missing.
"""
swift_version = SourceKitLSP._get_swift_version()
if not swift_version:
raise RuntimeError(
"Swift is not installed. Please install Swift from https://swift.org/download/ and make sure it is added to your PATH."
)
sourcekit_lsp_version = SourceKitLSP._get_sourcekit_lsp_version()
if not sourcekit_lsp_version:
raise RuntimeError(
"Found a Swift version but sourcekit-lsp is not installed.\n"
"Please install sourcekit-lsp as described in https://github.com/apple/sourcekit-lsp#installation\n\n"
"After installation, make sure it is added to your PATH."
)
return True
def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
):
self._setup_runtime_dependency()
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path),
"swift",
solidlsp_settings,
)
self.server_ready = threading.Event()
self.request_id = 0
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
Returns the initialize params for the Swift Language Server.
"""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params = {
"capabilities": {
"general": {
"markdown": {
"parser": "marked",
"version": "1.1.0"
},
"positionEncodings": [
"utf-16"
],
"regularExpressions": {
"engine": "ECMAScript",
"version": "ES2020"
},
"staleRequestSupport": {
"cancel": True,
"retryOnContentModified": [
"textDocument/semanticTokens/full",
"textDocument/semanticTokens/range",
"textDocument/semanticTokens/full/delta"
]
}
},
"notebookDocument": {
"synchronization": {
"dynamicRegistration": True,
"executionSummarySupport": True
}
},
"textDocument": {
"callHierarchy": {
"dynamicRegistration": True
},
"codeAction": {
"codeActionLiteralSupport": {
"codeActionKind": {
"valueSet": [
"",
"quickfix",
"refactor",
"refactor.extract",
"refactor.inline",
"refactor.rewrite",
"source",
"source.organizeImports"
]
}
},
"dataSupport": True,
"disabledSupport": True,
"dynamicRegistration": True,
"honorsChangeAnnotations": True,
"isPreferredSupport": True,
"resolveSupport": {
"properties": [
"edit"
]
}
},
"codeLens": {
"dynamicRegistration": True
},
"colorProvider": {
"dynamicRegistration": True
},
"completion": {
"completionItem": {
"commitCharactersSupport": True,
"deprecatedSupport": True,
"documentationFormat": [
"markdown",
"plaintext"
],
"insertReplaceSupport": True,
"insertTextModeSupport": {
"valueSet": [
1,
2
]
},
"labelDetailsSupport": True,
"preselectSupport": True,
"resolveSupport": {
"properties": [
"documentation",
"detail",
"additionalTextEdits"
]
},
"snippetSupport": True,
"tagSupport": {
"valueSet": [
1
]
}
},
"completionItemKind": {
"valueSet": [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
]
},
"completionList": {
"itemDefaults": [
"commitCharacters",
"editRange",
"insertTextFormat",
"insertTextMode",
"data"
]
},
"contextSupport": True,
"dynamicRegistration": True,
"insertTextMode": 2
},
"declaration": {
"dynamicRegistration": True,
"linkSupport": True
},
"definition": {
"dynamicRegistration": True,
"linkSupport": True
},
"diagnostic": {
"dynamicRegistration": True,
"relatedDocumentSupport": False
},
"documentHighlight": {
"dynamicRegistration": True
},
"documentLink": {
"dynamicRegistration": True,
"tooltipSupport": True
},
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
"labelSupport": True,
"symbolKind": {
"valueSet": [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
]
},
"tagSupport": {
"valueSet": [
1
]
}
},
"foldingRange": {
"dynamicRegistration": True,
"foldingRange": {
"collapsedText": False
},
"foldingRangeKind": {
"valueSet": [
"comment",
"imports",
"region"
]
},
"lineFoldingOnly": True,
"rangeLimit": 5000
},
"formatting": {
"dynamicRegistration": True
},
"hover": {
"contentFormat": [
"markdown",
"plaintext"
],
"dynamicRegistration": True
},
"implementation": {
"dynamicRegistration": True,
"linkSupport": True
},
"inlayHint": {
"dynamicRegistration": True,
"resolveSupport": {
"properties": [
"tooltip",
"textEdits",
"label.tooltip",
"label.location",
"label.command"
]
}
},
"inlineValue": {
"dynamicRegistration": True
},
"linkedEditingRange": {
"dynamicRegistration": True
},
"onTypeFormatting": {
"dynamicRegistration": True
},
"publishDiagnostics": {
"codeDescriptionSupport": True,
"dataSupport": True,
"relatedInformation": True,
"tagSupport": {
"valueSet": [
1,
2
]
},
"versionSupport": False
},
"rangeFormatting": {
"dynamicRegistration": True,
"rangesSupport": True
},
"references": {
"dynamicRegistration": True
},
"rename": {
"dynamicRegistration": True,
"honorsChangeAnnotations": True,
"prepareSupport": True,
"prepareSupportDefaultBehavior": 1
},
"selectionRange": {
"dynamicRegistration": True
},
"semanticTokens": {
"augmentsSyntaxTokens": True,
"dynamicRegistration": True,
"formats": [
"relative"
],
"multilineTokenSupport": False,
"overlappingTokenSupport": False,
"requests": {
"full": {
"delta": True
},
"range": True
},
"serverCancelSupport": True,
"tokenModifiers": [
"declaration",
"definition",
"readonly",
"static",
"deprecated",
"abstract",
"async",
"modification",
"documentation",
"defaultLibrary"
],
"tokenTypes": [
"namespace",
"type",
"class",
"enum",
"interface",
"struct",
"typeParameter",
"parameter",
"variable",
"property",
"enumMember",
"event",
"function",
"method",
"macro",
"keyword",
"modifier",
"comment",
"string",
"number",
"regexp",
"operator",
"decorator"
]
},
"signatureHelp": {
"contextSupport": True,
"dynamicRegistration": True,
"signatureInformation": {
"activeParameterSupport": True,
"documentationFormat": [
"markdown",
"plaintext"
],
"parameterInformation": {
"labelOffsetSupport": True
}
}
},
"synchronization": {
"didSave": True,
"dynamicRegistration": True,
"willSave": True,
"willSaveWaitUntil": True
},
"typeDefinition": {
"dynamicRegistration": True,
"linkSupport": True
},
"typeHierarchy": {
"dynamicRegistration": True
}
},
"window": {
"showDocument": {
"support": True
},
"showMessage": {
"messageActionItem": {
"additionalPropertiesSupport": True
}
},
"workDoneProgress": True
},
"workspace": {
"applyEdit": True,
"codeLens": {
"refreshSupport": True
},
"configuration": True,
"diagnostics": {
"refreshSupport": True
},
"didChangeConfiguration": {
"dynamicRegistration": True
},
"didChangeWatchedFiles": {
"dynamicRegistration": True,
"relativePatternSupport": True
},
"executeCommand": {
"dynamicRegistration": True
},
"fileOperations": {
"didCreate": True,
"didDelete": True,
"didRename": True,
"dynamicRegistration": True,
"willCreate": True,
"willDelete": True,
"willRename": True
},
"foldingRange": {
"refreshSupport": True
},
"inlayHint": {
"refreshSupport": True
},
"inlineValue": {
"refreshSupport": True
},
"semanticTokens": {
"refreshSupport": False
},
"symbol": {
"dynamicRegistration": True,
"resolveSupport": {
"properties": [
"location.range"
]
},
"symbolKind": {
"valueSet": [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
]
},
"tagSupport": {
"valueSet": [
1
]
}
},
"workspaceEdit": {
"changeAnnotationSupport": {
"groupsOnLabel": True
},
"documentChanges": True,
"failureHandling": "textOnlyTransactional",
"normalizesLineEndings": True,
"resourceOperations": [
"create",
"rename",
"delete"
]
},
"workspaceFolders": True
}
},
"clientInfo": {
"name": "Visual Studio Code",
"version": "1.102.2"
},
"initializationOptions": {
"backgroundIndexing": True,
"backgroundPreparationMode": "enabled",
"textDocument/codeLens": {
"supportedCommands": {
"swift.debug": "swift.debug",
"swift.run": "swift.run"
}
},
"window/didChangeActiveDocument": True,
"workspace/getReferenceDocument": True,
"workspace/peekDocuments": True
},
"locale": "en",
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"workspaceFolders": [
{
"uri": root_uri,
"name": os.path.basename(repository_absolute_path),
}
],
}
return initialize_params
def _start_server(self):
"""Start sourcekit-lsp server process"""
def register_capability_handler(_params):
return
def window_log_message(msg):
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
def do_nothing(_params):
return
self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.logger.log("Starting sourcekit-lsp server process", logging.INFO)
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
self.logger.log(
"Sending initialize request from LSP client to LSP server and awaiting response",
logging.INFO,
)
init_response = self.server.send.initialize(initialize_params)
capabilities = init_response["capabilities"]
self.logger.log(f"SourceKit LSP capabilities: {list(capabilities.keys())}", logging.INFO)
assert "textDocumentSync" in capabilities, "textDocumentSync capability missing"
assert "definitionProvider" in capabilities, "definitionProvider capability missing"
self.server.notify.initialized({})
self.completions_available.set()
# sourcekit-lsp server is typically ready immediately after initialization
self.server_ready.set()
self.server_ready.wait()
+5
View File
@@ -226,6 +226,11 @@ class SolidLanguageServer(ABC):
ls = TerraformLS(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
elif config.code_language == Language.SWIFT:
from solidlsp.language_servers.sourcekit_lsp import SourceKitLSP
ls = SourceKitLSP(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
else:
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
raise SolidLSPException(f"Language {config.code_language} is not supported")
+3
View File
@@ -42,6 +42,7 @@ class Language(str, Enum):
CLOJURE = "clojure"
ELIXIR = "elixir"
TERRAFORM = "terraform"
SWIFT = "swift"
# Experimental or deprecated Language Servers
TYPESCRIPT_VTS = "typescript_vts"
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
@@ -103,6 +104,8 @@ class Language(str, Enum):
return FilenameMatcher("*.ex", "*.exs")
case self.TERRAFORM:
return FilenameMatcher("*.tf", "*.tfvars", "*.tfstate")
case self.SWIFT:
return FilenameMatcher("*.swift")
case _:
raise ValueError(f"Unhandled language: {self}")
@@ -0,0 +1,16 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "test_repo",
products: [
.library(
name: "test_repo",
targets: ["test_repo"]),
],
targets: [
.target(
name: "test_repo",
dependencies: []),
]
)
@@ -0,0 +1,62 @@
import Foundation
// Main entry point
func main() {
let calculator = Calculator()
let result = calculator.add(5, 3)
print("Result: \(result)")
let user = User(name: "Alice", age: 30)
user.greet()
let area = Utils.calculateArea(radius: 5.0)
print("Circle area: \(area)")
}
class Calculator {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
}
struct User {
let name: String
let age: Int
func greet() {
print("Hello, my name is \(name) and I am \(age) years old.")
}
func isAdult() -> Bool {
return age >= 18
}
}
enum Status {
case active
case inactive
case pending
}
protocol Drawable {
func draw()
}
class Circle: Drawable {
let radius: Double
init(radius: Double) {
self.radius = radius
}
func draw() {
print("Drawing a circle with radius \(radius)")
}
}
// Call main
main()
@@ -0,0 +1,20 @@
import Foundation
public struct Utils {
public static func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter.string(from: date)
}
public static func calculateArea(radius: Double) -> Double {
return Double.pi * radius * radius
}
}
public extension String {
func isValidEmail() -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: self)
}
}
+228
View File
@@ -0,0 +1,228 @@
"""
Basic integration tests for the Swift language server functionality.
These tests validate the functionality of the language server APIs
like request_references using the Swift test repository.
"""
import os
import sys
import time
import platform
import pytest
from serena.project import Project
from serena.text_utils import LineType
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
# Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows"
WINDOWS_SKIP_REASON = "GitHub Actions configuration for Swift on Windows is complex, skipping for now."
pytestmark = [pytest.mark.swift, pytest.mark.skipif(WINDOWS_SKIP, reason=WINDOWS_SKIP_REASON)]
class TestSwiftLanguageServerBasics:
"""Test basic functionality of the Swift language server."""
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on Calculator class usage."""
file_path = os.path.join("src", "main.swift")
# Find the Calculator usage at line 5: let calculator = Calculator()
# Position should be at the "Calculator()" call
definitions = language_server.request_definition(file_path, 4, 23) # Position at Calculator() call
assert isinstance(definitions, list), "Definitions should be a list"
assert len(definitions) > 0, "Should find definition for Calculator class"
# Verify the definition points to the Calculator class definition
calculator_def = definitions[0]
assert calculator_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The Calculator class is defined starting at line 16
start_line = calculator_def.get("range", {}).get("start", {}).get("line")
assert start_line == 15, f"Calculator class definition should be at line 16, got {start_line + 1}"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_user_struct(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on User struct usage."""
file_path = os.path.join("src", "main.swift")
# Find the User usage at line 9: let user = User(name: "Alice", age: 30)
# Position should be at the "User(...)" call
definitions = language_server.request_definition(file_path, 8, 18) # Position at User(...) call
assert isinstance(definitions, list), "Definitions should be a list"
assert len(definitions) > 0, "Should find definition for User struct"
# Verify the definition points to the User struct definition
user_def = definitions[0]
assert user_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The User struct is defined starting at line 26
start_line = user_def.get("range", {}).get("start", {}).get("line")
assert start_line == 25, f"User struct definition should be at line 26, got {start_line + 1}"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_calculator_method(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on Calculator method usage."""
file_path = os.path.join("src", "main.swift")
# Find the add method usage at line 6: let result = calculator.add(5, 3)
# Position should be at the "add" method call
definitions = language_server.request_definition(file_path, 5, 28) # Position at add method call
assert isinstance(definitions, list), "Definitions should be a list"
# Verify the definition points to the add method definition
add_def = definitions[0]
assert add_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The add method is defined starting at line 17
start_line = add_def.get("range", {}).get("start", {}).get("line")
assert start_line == 16, f"add method definition should be at line 17, got {start_line + 1}"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition across files - Utils struct."""
utils_file = os.path.join("src", "utils.swift")
# First, let's check if Utils is used anywhere (it might not be in this simple test)
# We'll test goto_definition on Utils struct itself
symbols = language_server.request_document_symbols(utils_file)
utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
sel_start = utils_symbol["selectionRange"]["start"]
definitions = language_server.request_definition(utils_file, sel_start["line"], sel_start["character"])
assert isinstance(definitions, list), "Definitions should be a list"
# Should find the Utils struct definition itself
utils_def = definitions[0]
assert utils_def.get("uri", "").endswith("utils.swift"), "Definition should be in utils.swift"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_request_references_calculator_class(self, language_server: SolidLanguageServer) -> None:
# SourceKit LSP needs a short initialization period after startup
# before it can provide accurate reference information. This sleep
# prevents race conditions where references might not be available yet.
time.sleep(5)
"""Test request_references on the Calculator class."""
# Get references to the Calculator class in main.swift
file_path = os.path.join("src", "main.swift")
symbols = language_server.request_document_symbols(file_path)
calculator_symbol = next((s for s in symbols[0] if s.get("name") == "Calculator"), None)
sel_start = calculator_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list"
assert len(references) > 0, "Calculator class should be referenced"
# Validate that Calculator is referenced in the main function
calculator_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
assert len(calculator_refs) > 0, "Calculator class should be referenced in main.swift"
# Check that one reference is at line 5 (let calculator = Calculator())
line_5_refs = [ref for ref in calculator_refs if ref.get("range", {}).get("start", {}).get("line") == 4]
assert len(line_5_refs) > 0, "Calculator should be referenced at line 5"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_request_references_user_struct(self, language_server: SolidLanguageServer) -> None:
"""Test request_references on the User struct."""
# Get references to the User struct in main.swift
file_path = os.path.join("src", "main.swift")
symbols = language_server.request_document_symbols(file_path)
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
sel_start = user_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list"
# Validate that User is referenced in the main function
user_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
assert len(user_refs) > 0, "User struct should be referenced in main.swift"
# Check that one reference is at line 9 (let user = User(...))
line_9_refs = [ref for ref in user_refs if ref.get("range", {}).get("start", {}).get("line") == 8]
assert len(line_9_refs) > 0, "User should be referenced at line 9"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_request_references_utils_struct(self, language_server: SolidLanguageServer) -> None:
"""Test request_references on the Utils struct."""
# Get references to the Utils struct in utils.swift
file_path = os.path.join("src", "utils.swift")
symbols = language_server.request_document_symbols(file_path)
utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
if not utils_symbol or "selectionRange" not in utils_symbol:
raise AssertionError("Utils symbol or its selectionRange not found")
sel_start = utils_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list"
assert len(references) > 0, "Utils struct should be referenced"
# Validate that Utils is referenced in main.swift
utils_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")]
assert len(utils_refs) > 0, "Utils struct should be referenced in main.swift"
# Check that one reference is at line 12 (Utils.calculateArea call)
line_12_refs = [ref for ref in utils_refs if ref.get("range", {}).get("start", {}).get("line") == 11]
assert len(line_12_refs) > 0, "Utils should be referenced at line 12"
class TestSwiftProjectBasics:
@pytest.mark.parametrize("project", [Language.SWIFT], indirect=True)
def test_retrieve_content_around_line(self, project: Project) -> None:
"""Test retrieve_content_around_line functionality with various scenarios."""
file_path = os.path.join("src", "main.swift")
# Scenario 1: Find Calculator class definition
calculator_line = None
for line_num in range(1, 50): # Search first 50 lines
try:
line_content = project.retrieve_content_around_line(file_path, line_num)
if line_content.lines and "class Calculator" in line_content.lines[0].line_content:
calculator_line = line_num
break
except:
continue
assert calculator_line is not None, "Calculator class not found"
line_calc = project.retrieve_content_around_line(file_path, calculator_line)
assert len(line_calc.lines) == 1
assert "class Calculator" in line_calc.lines[0].line_content
assert line_calc.lines[0].line_number == calculator_line
assert line_calc.lines[0].match_type == LineType.MATCH
# Scenario 2: Context above and below Calculator class
with_context_around_calculator = project.retrieve_content_around_line(file_path, calculator_line, 2, 2)
assert len(with_context_around_calculator.lines) == 5
assert "class Calculator" in with_context_around_calculator.matched_lines[0].line_content
assert with_context_around_calculator.num_matched_lines == 1
# Scenario 3: Search for struct definitions
struct_pattern = r"struct\s+\w+"
matches = project.search_source_files_for_pattern(struct_pattern)
assert len(matches) > 0, "Should find struct definitions"
# Should find User struct
user_matches = [m for m in matches if "User" in str(m)]
assert len(user_matches) > 0, "Should find User struct"
# Scenario 4: Search for class definitions
class_pattern = r"class\s+\w+"
matches = project.search_source_files_for_pattern(class_pattern)
assert len(matches) > 0, "Should find class definitions"
# Should find Calculator and Circle classes
calculator_matches = [m for m in matches if "Calculator" in str(m)]
circle_matches = [m for m in matches if "Circle" in str(m)]
assert len(calculator_matches) > 0, "Should find Calculator class"
assert len(circle_matches) > 0, "Should find Circle class"
# Scenario 5: Search for enum definitions
enum_pattern = r"enum\s+\w+"
matches = project.search_source_files_for_pattern(enum_pattern)
assert len(matches) > 0, "Should find enum definitions"
# Should find Status enum
status_matches = [m for m in matches if "Status" in str(m)]
assert len(status_matches) > 0, "Should find Status enum"