Minor simplification of sourcekit_lsp.py, added sleeping at startup

This commit is contained in:
MischaPanch authored and Michael Panchenko committed 2025-08-07 22:06:35 +02:00
1 parent 5236ffe2b6
commit 6d28c7d722
2 files changed
+125 -342

No files matched your search

+98 -309
View File
@@ -3,17 +3,16 @@ import os
import pathlib import pathlib
import subprocess import subprocess
import threading import threading
import time
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger 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.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
class SourceKitLSP(SolidLanguageServer): class SourceKitLSP(SolidLanguageServer):
@@ -31,53 +30,25 @@ class SourceKitLSP(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"] return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"]
@staticmethod @staticmethod
def _get_swift_version(): def _get_sourcekit_lsp_version() -> str:
"""Get the installed Swift version or None if not found.""" """Get the installed sourcekit-lsp version or raise error if sourcekit was 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: try:
result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False) result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False)
if result.returncode == 0: if result.returncode == 0:
return result.stdout.strip() return result.stdout.strip()
except FileNotFoundError: else:
return None raise Exception(f"`sourcekit-lsp -h` resulted in: {result}")
return None except Exception as e:
@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( raise RuntimeError(
"Swift is not installed. Please install Swift from https://swift.org/download/ and make sure it is added to your PATH." "Could not find sourcekit-lsp, please install it as described in https://github.com/apple/sourcekit-lsp#installation"
) "And make sure it is available on your PATH."
) from e
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__( def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
): ):
self._setup_runtime_dependency() sourcekit_version = self._get_sourcekit_lsp_version()
logger.log(f"Starting sourcekit lsp with version: {sourcekit_version}", logging.INFO)
super().__init__( super().__init__(
config, config,
@@ -96,40 +67,25 @@ class SourceKitLSP(SolidLanguageServer):
Returns the initialize params for the Swift Language Server. Returns the initialize params for the Swift Language Server.
""" """
root_uri = pathlib.Path(repository_absolute_path).as_uri() root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params = { initialize_params = {
"capabilities": { "capabilities": {
"general": { "general": {
"markdown": { "markdown": {"parser": "marked", "version": "1.1.0"},
"parser": "marked", "positionEncodings": ["utf-16"],
"version": "1.1.0" "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"},
},
"positionEncodings": [
"utf-16"
],
"regularExpressions": {
"engine": "ECMAScript",
"version": "ES2020"
},
"staleRequestSupport": { "staleRequestSupport": {
"cancel": True, "cancel": True,
"retryOnContentModified": [ "retryOnContentModified": [
"textDocument/semanticTokens/full", "textDocument/semanticTokens/full",
"textDocument/semanticTokens/range", "textDocument/semanticTokens/range",
"textDocument/semanticTokens/full/delta" "textDocument/semanticTokens/full/delta",
] ],
}
},
"notebookDocument": {
"synchronization": {
"dynamicRegistration": True,
"executionSummarySupport": True
}
},
"textDocument": {
"callHierarchy": {
"dynamicRegistration": True
}, },
},
"notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
"textDocument": {
"callHierarchy": {"dynamicRegistration": True},
"codeAction": { "codeAction": {
"codeActionLiteralSupport": { "codeActionLiteralSupport": {
"codeActionKind": { "codeActionKind": {
@@ -141,7 +97,7 @@ class SourceKitLSP(SolidLanguageServer):
"refactor.inline", "refactor.inline",
"refactor.rewrite", "refactor.rewrite",
"source", "source",
"source.organizeImports" "source.organizeImports",
] ]
} }
}, },
@@ -150,193 +106,85 @@ class SourceKitLSP(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"honorsChangeAnnotations": True, "honorsChangeAnnotations": True,
"isPreferredSupport": True, "isPreferredSupport": True,
"resolveSupport": { "resolveSupport": {"properties": ["edit"]},
"properties": [
"edit"
]
}
},
"codeLens": {
"dynamicRegistration": True
},
"colorProvider": {
"dynamicRegistration": True
}, },
"codeLens": {"dynamicRegistration": True},
"colorProvider": {"dynamicRegistration": True},
"completion": { "completion": {
"completionItem": { "completionItem": {
"commitCharactersSupport": True, "commitCharactersSupport": True,
"deprecatedSupport": True, "deprecatedSupport": True,
"documentationFormat": [ "documentationFormat": ["markdown", "plaintext"],
"markdown",
"plaintext"
],
"insertReplaceSupport": True, "insertReplaceSupport": True,
"insertTextModeSupport": { "insertTextModeSupport": {"valueSet": [1, 2]},
"valueSet": [
1,
2
]
},
"labelDetailsSupport": True, "labelDetailsSupport": True,
"preselectSupport": True, "preselectSupport": True,
"resolveSupport": { "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]},
"properties": [
"documentation",
"detail",
"additionalTextEdits"
]
},
"snippetSupport": True, "snippetSupport": True,
"tagSupport": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"completionItemKind": { "completionItemKind": {
"valueSet": [ "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]
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"
]
}, },
"completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data"]},
"contextSupport": True, "contextSupport": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"insertTextMode": 2 "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
}, },
"declaration": {"dynamicRegistration": True, "linkSupport": True},
"definition": {"dynamicRegistration": True, "linkSupport": True},
"diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False},
"documentHighlight": {"dynamicRegistration": True},
"documentLink": {"dynamicRegistration": True, "tooltipSupport": True},
"documentSymbol": { "documentSymbol": {
"dynamicRegistration": True, "dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True, "hierarchicalDocumentSymbolSupport": True,
"labelSupport": True, "labelSupport": True,
"symbolKind": { "symbolKind": {
"valueSet": [ "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]
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": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"foldingRange": { "foldingRange": {
"dynamicRegistration": True, "dynamicRegistration": True,
"foldingRange": { "foldingRange": {"collapsedText": False},
"collapsedText": False "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]},
},
"foldingRangeKind": {
"valueSet": [
"comment",
"imports",
"region"
]
},
"lineFoldingOnly": True, "lineFoldingOnly": True,
"rangeLimit": 5000 "rangeLimit": 5000,
},
"formatting": {
"dynamicRegistration": True
},
"hover": {
"contentFormat": [
"markdown",
"plaintext"
],
"dynamicRegistration": True
},
"implementation": {
"dynamicRegistration": True,
"linkSupport": True
}, },
"formatting": {"dynamicRegistration": True},
"hover": {"contentFormat": ["markdown", "plaintext"], "dynamicRegistration": True},
"implementation": {"dynamicRegistration": True, "linkSupport": True},
"inlayHint": { "inlayHint": {
"dynamicRegistration": True, "dynamicRegistration": True,
"resolveSupport": { "resolveSupport": {"properties": ["tooltip", "textEdits", "label.tooltip", "label.location", "label.command"]},
"properties": [
"tooltip",
"textEdits",
"label.tooltip",
"label.location",
"label.command"
]
}
},
"inlineValue": {
"dynamicRegistration": True
},
"linkedEditingRange": {
"dynamicRegistration": True
},
"onTypeFormatting": {
"dynamicRegistration": True
}, },
"inlineValue": {"dynamicRegistration": True},
"linkedEditingRange": {"dynamicRegistration": True},
"onTypeFormatting": {"dynamicRegistration": True},
"publishDiagnostics": { "publishDiagnostics": {
"codeDescriptionSupport": True, "codeDescriptionSupport": True,
"dataSupport": True, "dataSupport": True,
"relatedInformation": True, "relatedInformation": True,
"tagSupport": { "tagSupport": {"valueSet": [1, 2]},
"valueSet": [ "versionSupport": False,
1,
2
]
},
"versionSupport": False
},
"rangeFormatting": {
"dynamicRegistration": True,
"rangesSupport": True
},
"references": {
"dynamicRegistration": True
}, },
"rangeFormatting": {"dynamicRegistration": True, "rangesSupport": True},
"references": {"dynamicRegistration": True},
"rename": { "rename": {
"dynamicRegistration": True, "dynamicRegistration": True,
"honorsChangeAnnotations": True, "honorsChangeAnnotations": True,
"prepareSupport": True, "prepareSupport": True,
"prepareSupportDefaultBehavior": 1 "prepareSupportDefaultBehavior": 1,
},
"selectionRange": {
"dynamicRegistration": True
}, },
"selectionRange": {"dynamicRegistration": True},
"semanticTokens": { "semanticTokens": {
"augmentsSyntaxTokens": True, "augmentsSyntaxTokens": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"formats": [ "formats": ["relative"],
"relative"
],
"multilineTokenSupport": False, "multilineTokenSupport": False,
"overlappingTokenSupport": False, "overlappingTokenSupport": False,
"requests": { "requests": {"full": {"delta": True}, "range": True},
"full": {
"delta": True
},
"range": True
},
"serverCancelSupport": True, "serverCancelSupport": True,
"tokenModifiers": [ "tokenModifiers": [
"declaration", "declaration",
@@ -348,7 +196,7 @@ class SourceKitLSP(SolidLanguageServer):
"async", "async",
"modification", "modification",
"documentation", "documentation",
"defaultLibrary" "defaultLibrary",
], ],
"tokenTypes": [ "tokenTypes": [
"namespace", "namespace",
@@ -373,67 +221,35 @@ class SourceKitLSP(SolidLanguageServer):
"number", "number",
"regexp", "regexp",
"operator", "operator",
"decorator" "decorator",
] ],
}, },
"signatureHelp": { "signatureHelp": {
"contextSupport": True, "contextSupport": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"signatureInformation": { "signatureInformation": {
"activeParameterSupport": True, "activeParameterSupport": True,
"documentationFormat": [ "documentationFormat": ["markdown", "plaintext"],
"markdown", "parameterInformation": {"labelOffsetSupport": True},
"plaintext" },
],
"parameterInformation": {
"labelOffsetSupport": True
}
}
}, },
"synchronization": { "synchronization": {"didSave": True, "dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True},
"didSave": True, "typeDefinition": {"dynamicRegistration": True, "linkSupport": True},
"dynamicRegistration": True, "typeHierarchy": {"dynamicRegistration": True},
"willSave": True,
"willSaveWaitUntil": True
},
"typeDefinition": {
"dynamicRegistration": True,
"linkSupport": True
},
"typeHierarchy": {
"dynamicRegistration": True
}
}, },
"window": { "window": {
"showDocument": { "showDocument": {"support": True},
"support": True "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
}, "workDoneProgress": True,
"showMessage": {
"messageActionItem": {
"additionalPropertiesSupport": True
}
},
"workDoneProgress": True
}, },
"workspace": { "workspace": {
"applyEdit": True, "applyEdit": True,
"codeLens": { "codeLens": {"refreshSupport": True},
"refreshSupport": True
},
"configuration": True, "configuration": True,
"diagnostics": { "diagnostics": {"refreshSupport": True},
"refreshSupport": True "didChangeConfiguration": {"dynamicRegistration": True},
}, "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True},
"didChangeConfiguration": { "executeCommand": {"dynamicRegistration": True},
"dynamicRegistration": True
},
"didChangeWatchedFiles": {
"dynamicRegistration": True,
"relativePatternSupport": True
},
"executeCommand": {
"dynamicRegistration": True
},
"fileOperations": { "fileOperations": {
"didCreate": True, "didCreate": True,
"didDelete": True, "didDelete": True,
@@ -441,70 +257,38 @@ class SourceKitLSP(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"willCreate": True, "willCreate": True,
"willDelete": True, "willDelete": True,
"willRename": True "willRename": True,
},
"foldingRange": {
"refreshSupport": True
},
"inlayHint": {
"refreshSupport": True
},
"inlineValue": {
"refreshSupport": True
},
"semanticTokens": {
"refreshSupport": False
}, },
"foldingRange": {"refreshSupport": True},
"inlayHint": {"refreshSupport": True},
"inlineValue": {"refreshSupport": True},
"semanticTokens": {"refreshSupport": False},
"symbol": { "symbol": {
"dynamicRegistration": True, "dynamicRegistration": True,
"resolveSupport": { "resolveSupport": {"properties": ["location.range"]},
"properties": [
"location.range"
]
},
"symbolKind": { "symbolKind": {
"valueSet": [ "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]
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": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"workspaceEdit": { "workspaceEdit": {
"changeAnnotationSupport": { "changeAnnotationSupport": {"groupsOnLabel": True},
"groupsOnLabel": True
},
"documentChanges": True, "documentChanges": True,
"failureHandling": "textOnlyTransactional", "failureHandling": "textOnlyTransactional",
"normalizesLineEndings": True, "normalizesLineEndings": True,
"resourceOperations": [ "resourceOperations": ["create", "rename", "delete"],
"create",
"rename",
"delete"
]
}, },
"workspaceFolders": True "workspaceFolders": True,
} },
},
"clientInfo": {
"name": "Visual Studio Code",
"version": "1.102.2"
}, },
"clientInfo": {"name": "Visual Studio Code", "version": "1.102.2"},
"initializationOptions": { "initializationOptions": {
"backgroundIndexing": True, "backgroundIndexing": True,
"backgroundPreparationMode": "enabled", "backgroundPreparationMode": "enabled",
"textDocument/codeLens": { "textDocument/codeLens": {"supportedCommands": {"swift.debug": "swift.debug", "swift.run": "swift.run"}},
"supportedCommands": {
"swift.debug": "swift.debug",
"swift.run": "swift.run"
}
},
"window/didChangeActiveDocument": True, "window/didChangeActiveDocument": True,
"workspace/getReferenceDocument": True, "workspace/getReferenceDocument": True,
"workspace/peekDocuments": True "workspace/peekDocuments": True,
}, },
"locale": "en", "locale": "en",
"processId": os.getpid(), "processId": os.getpid(),
@@ -517,7 +301,7 @@ class SourceKitLSP(SolidLanguageServer):
} }
], ],
} }
return initialize_params return initialize_params
def _start_server(self): def _start_server(self):
@@ -549,13 +333,18 @@ class SourceKitLSP(SolidLanguageServer):
capabilities = init_response["capabilities"] capabilities = init_response["capabilities"]
self.logger.log(f"SourceKit LSP capabilities: {list(capabilities.keys())}", logging.INFO) self.logger.log(f"SourceKit LSP capabilities: {list(capabilities.keys())}", logging.INFO)
assert "textDocumentSync" in capabilities, "textDocumentSync capability missing" assert "textDocumentSync" in capabilities, "textDocumentSync capability missing"
assert "definitionProvider" in capabilities, "definitionProvider capability missing" assert "definitionProvider" in capabilities, "definitionProvider capability missing"
self.server.notify.initialized({}) self.server.notify.initialized({})
self.completions_available.set() self.completions_available.set()
# sourcekit-lsp server is typically ready immediately after initialization
self.server_ready.set() self.server_ready.set()
self.server_ready.wait() self.server_ready.wait()
# 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.
# Unfortunately, sourcekit doesn't send a signal when it's really ready
time.sleep(5)
+27 -33
View File
@@ -6,8 +6,6 @@ like request_references using the Swift test repository.
""" """
import os import os
import sys
import time
import platform import platform
import pytest import pytest
@@ -17,30 +15,31 @@ from serena.text_utils import LineType
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
# Skip Swift tests on Windows due to complex GitHub Actions configuration # Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows" WINDOWS_SKIP = platform.system() == "Windows"
WINDOWS_SKIP_REASON = "GitHub Actions configuration for Swift on Windows is complex, skipping for now." 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)] pytestmark = [pytest.mark.swift, pytest.mark.skipif(WINDOWS_SKIP, reason=WINDOWS_SKIP_REASON)]
class TestSwiftLanguageServerBasics: class TestSwiftLanguageServerBasics:
"""Test basic functionality of the Swift language server.""" """Test basic functionality of the Swift language server."""
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on Calculator class usage.""" """Test goto_definition on Calculator class usage."""
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
# Find the Calculator usage at line 5: let calculator = Calculator() # Find the Calculator usage at line 5: let calculator = Calculator()
# Position should be at the "Calculator()" call # Position should be at the "Calculator()" call
definitions = language_server.request_definition(file_path, 4, 23) # Position at 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 isinstance(definitions, list), "Definitions should be a list"
assert len(definitions) > 0, "Should find definition for Calculator class" assert len(definitions) > 0, "Should find definition for Calculator class"
# Verify the definition points to the Calculator class definition # Verify the definition points to the Calculator class definition
calculator_def = definitions[0] calculator_def = definitions[0]
assert calculator_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift" assert calculator_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The Calculator class is defined starting at line 16 # The Calculator class is defined starting at line 16
start_line = calculator_def.get("range", {}).get("start", {}).get("line") 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}" assert start_line == 15, f"Calculator class definition should be at line 16, got {start_line + 1}"
@@ -49,17 +48,17 @@ class TestSwiftLanguageServerBasics:
def test_goto_definition_user_struct(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_user_struct(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on User struct usage.""" """Test goto_definition on User struct usage."""
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
# Find the User usage at line 9: let user = User(name: "Alice", age: 30) # Find the User usage at line 9: let user = User(name: "Alice", age: 30)
# Position should be at the "User(...)" call # Position should be at the "User(...)" call
definitions = language_server.request_definition(file_path, 8, 18) # Position at 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 isinstance(definitions, list), "Definitions should be a list"
assert len(definitions) > 0, "Should find definition for User struct" assert len(definitions) > 0, "Should find definition for User struct"
# Verify the definition points to the User struct definition # Verify the definition points to the User struct definition
user_def = definitions[0] user_def = definitions[0]
assert user_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift" assert user_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The User struct is defined starting at line 26 # The User struct is defined starting at line 26
start_line = user_def.get("range", {}).get("start", {}).get("line") 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}" assert start_line == 25, f"User struct definition should be at line 26, got {start_line + 1}"
@@ -68,16 +67,16 @@ class TestSwiftLanguageServerBasics:
def test_goto_definition_calculator_method(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_calculator_method(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on Calculator method usage.""" """Test goto_definition on Calculator method usage."""
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
# Find the add method usage at line 6: let result = calculator.add(5, 3) # Find the add method usage at line 6: let result = calculator.add(5, 3)
# Position should be at the "add" method call # Position should be at the "add" method call
definitions = language_server.request_definition(file_path, 5, 28) # Position at 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" assert isinstance(definitions, list), "Definitions should be a list"
# Verify the definition points to the add method definition # Verify the definition points to the add method definition
add_def = definitions[0] add_def = definitions[0]
assert add_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift" assert add_def.get("uri", "").endswith("main.swift"), "Definition should be in main.swift"
# The add method is defined starting at line 17 # The add method is defined starting at line 17
start_line = add_def.get("range", {}).get("start", {}).get("line") 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}" assert start_line == 16, f"add method definition should be at line 17, got {start_line + 1}"
@@ -86,42 +85,38 @@ class TestSwiftLanguageServerBasics:
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition across files - Utils struct.""" """Test goto_definition across files - Utils struct."""
utils_file = os.path.join("src", "utils.swift") 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) # 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 # We'll test goto_definition on Utils struct itself
symbols = language_server.request_document_symbols(utils_file) symbols = language_server.request_document_symbols(utils_file)
utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None) utils_symbol = next((s for s in symbols[0] if s.get("name") == "Utils"), None)
sel_start = utils_symbol["selectionRange"]["start"] sel_start = utils_symbol["selectionRange"]["start"]
definitions = language_server.request_definition(utils_file, sel_start["line"], sel_start["character"]) definitions = language_server.request_definition(utils_file, sel_start["line"], sel_start["character"])
assert isinstance(definitions, list), "Definitions should be a list" assert isinstance(definitions, list), "Definitions should be a list"
# Should find the Utils struct definition itself # Should find the Utils struct definition itself
utils_def = definitions[0] utils_def = definitions[0]
assert utils_def.get("uri", "").endswith("utils.swift"), "Definition should be in utils.swift" assert utils_def.get("uri", "").endswith("utils.swift"), "Definition should be in utils.swift"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_request_references_calculator_class(self, language_server: SolidLanguageServer) -> None: 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.""" """Test request_references on the Calculator class."""
# Get references to the Calculator class in main.swift # Get references to the Calculator class in main.swift
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
symbols = language_server.request_document_symbols(file_path) symbols = language_server.request_document_symbols(file_path)
calculator_symbol = next((s for s in symbols[0] if s.get("name") == "Calculator"), None) calculator_symbol = next((s for s in symbols[0] if s.get("name") == "Calculator"), None)
sel_start = calculator_symbol["selectionRange"]["start"] sel_start = calculator_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list" assert isinstance(references, list), "References should be a list"
assert len(references) > 0, "Calculator class should be referenced" assert len(references) > 0, "Calculator class should be referenced"
# Validate that Calculator is referenced in the main function # Validate that Calculator is referenced in the main function
calculator_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")] 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" assert len(calculator_refs) > 0, "Calculator class should be referenced in main.swift"
# Check that one reference is at line 5 (let calculator = Calculator()) # 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] 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" assert len(line_5_refs) > 0, "Calculator should be referenced at line 5"
@@ -132,17 +127,17 @@ class TestSwiftLanguageServerBasics:
# Get references to the User struct in main.swift # Get references to the User struct in main.swift
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
symbols = language_server.request_document_symbols(file_path) symbols = language_server.request_document_symbols(file_path)
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None) user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
sel_start = user_symbol["selectionRange"]["start"] sel_start = user_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list" assert isinstance(references, list), "References should be a list"
# Validate that User is referenced in the main function # Validate that User is referenced in the main function
user_refs = [ref for ref in references if ref.get("uri", "").endswith("main.swift")] 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" assert len(user_refs) > 0, "User struct should be referenced in main.swift"
# Check that one reference is at line 9 (let user = User(...)) # 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] 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" assert len(line_9_refs) > 0, "User should be referenced at line 9"
@@ -160,17 +155,16 @@ class TestSwiftLanguageServerBasics:
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"]) references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert isinstance(references, list), "References should be a list" assert isinstance(references, list), "References should be a list"
assert len(references) > 0, "Utils struct should be referenced" assert len(references) > 0, "Utils struct should be referenced"
# Validate that Utils is referenced in main.swift # Validate that Utils is referenced in main.swift
utils_refs = [ref for ref in references if ref.get("uri", "").endswith("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" assert len(utils_refs) > 0, "Utils struct should be referenced in main.swift"
# Check that one reference is at line 12 (Utils.calculateArea call) # 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] 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" assert len(line_12_refs) > 0, "Utils should be referenced at line 12"
class TestSwiftProjectBasics: class TestSwiftProjectBasics:
@pytest.mark.parametrize("project", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("project", [Language.SWIFT], indirect=True)
def test_retrieve_content_around_line(self, project: Project) -> None: def test_retrieve_content_around_line(self, project: Project) -> None:
@@ -187,7 +181,7 @@ class TestSwiftProjectBasics:
break break
except: except:
continue continue
assert calculator_line is not None, "Calculator class not found" assert calculator_line is not None, "Calculator class not found"
line_calc = project.retrieve_content_around_line(file_path, calculator_line) line_calc = project.retrieve_content_around_line(file_path, calculator_line)
assert len(line_calc.lines) == 1 assert len(line_calc.lines) == 1
@@ -209,7 +203,7 @@ class TestSwiftProjectBasics:
user_matches = [m for m in matches if "User" in str(m)] user_matches = [m for m in matches if "User" in str(m)]
assert len(user_matches) > 0, "Should find User struct" assert len(user_matches) > 0, "Should find User struct"
# Scenario 4: Search for class definitions # Scenario 4: Search for class definitions
class_pattern = r"class\s+\w+" class_pattern = r"class\s+\w+"
matches = project.search_source_files_for_pattern(class_pattern) matches = project.search_source_files_for_pattern(class_pattern)
assert len(matches) > 0, "Should find class definitions" assert len(matches) > 0, "Should find class definitions"
@@ -225,4 +219,4 @@ class TestSwiftProjectBasics:
assert len(matches) > 0, "Should find enum definitions" assert len(matches) > 0, "Should find enum definitions"
# Should find Status enum # Should find Status enum
status_matches = [m for m in matches if "Status" in str(m)] status_matches = [m for m in matches if "Status" in str(m)]
assert len(status_matches) > 0, "Should find Status enum" assert len(status_matches) > 0, "Should find Status enum"