diff --git a/src/solidlsp/language_servers/sourcekit_lsp.py b/src/solidlsp/language_servers/sourcekit_lsp.py index dac6cfe4..f566b693 100644 --- a/src/solidlsp/language_servers/sourcekit_lsp.py +++ b/src/solidlsp/language_servers/sourcekit_lsp.py @@ -3,17 +3,16 @@ import os import pathlib import subprocess import threading +import time 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): @@ -31,53 +30,25 @@ class SourceKitLSP(SolidLanguageServer): 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.""" + def _get_sourcekit_lsp_version() -> str: + """Get the installed sourcekit-lsp version or raise error if sourcekit was 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: + else: + raise Exception(f"`sourcekit-lsp -h` resulted in: {result}") + except Exception as e: 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 + "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 def __init__( 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__( config, @@ -96,40 +67,25 @@ class SourceKitLSP(SolidLanguageServer): 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" - }, + "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 + "textDocument/semanticTokens/full/delta", + ], }, + }, + "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}}, + "textDocument": { + "callHierarchy": {"dynamicRegistration": True}, "codeAction": { "codeActionLiteralSupport": { "codeActionKind": { @@ -141,7 +97,7 @@ class SourceKitLSP(SolidLanguageServer): "refactor.inline", "refactor.rewrite", "source", - "source.organizeImports" + "source.organizeImports", ] } }, @@ -150,193 +106,85 @@ class SourceKitLSP(SolidLanguageServer): "dynamicRegistration": True, "honorsChangeAnnotations": True, "isPreferredSupport": True, - "resolveSupport": { - "properties": [ - "edit" - ] - } - }, - "codeLens": { - "dynamicRegistration": True - }, - "colorProvider": { - "dynamicRegistration": True + "resolveSupport": {"properties": ["edit"]}, }, + "codeLens": {"dynamicRegistration": True}, + "colorProvider": {"dynamicRegistration": True}, "completion": { "completionItem": { "commitCharactersSupport": True, "deprecatedSupport": True, - "documentationFormat": [ - "markdown", - "plaintext" - ], + "documentationFormat": ["markdown", "plaintext"], "insertReplaceSupport": True, - "insertTextModeSupport": { - "valueSet": [ - 1, - 2 - ] - }, + "insertTextModeSupport": {"valueSet": [1, 2]}, "labelDetailsSupport": True, "preselectSupport": True, - "resolveSupport": { - "properties": [ - "documentation", - "detail", - "additionalTextEdits" - ] - }, + "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]}, "snippetSupport": True, - "tagSupport": { - "valueSet": [ - 1 - ] - } + "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" - ] + "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 + "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 - ] + "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 - ] - } + "tagSupport": {"valueSet": [1]}, }, "foldingRange": { "dynamicRegistration": True, - "foldingRange": { - "collapsedText": False - }, - "foldingRangeKind": { - "valueSet": [ - "comment", - "imports", - "region" - ] - }, + "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 + "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 + "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 + "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 + "prepareSupportDefaultBehavior": 1, }, + "selectionRange": {"dynamicRegistration": True}, "semanticTokens": { "augmentsSyntaxTokens": True, "dynamicRegistration": True, - "formats": [ - "relative" - ], + "formats": ["relative"], "multilineTokenSupport": False, "overlappingTokenSupport": False, - "requests": { - "full": { - "delta": True - }, - "range": True - }, + "requests": {"full": {"delta": True}, "range": True}, "serverCancelSupport": True, "tokenModifiers": [ "declaration", @@ -348,7 +196,7 @@ class SourceKitLSP(SolidLanguageServer): "async", "modification", "documentation", - "defaultLibrary" + "defaultLibrary", ], "tokenTypes": [ "namespace", @@ -373,67 +221,35 @@ class SourceKitLSP(SolidLanguageServer): "number", "regexp", "operator", - "decorator" - ] + "decorator", + ], }, "signatureHelp": { "contextSupport": True, "dynamicRegistration": True, "signatureInformation": { "activeParameterSupport": True, - "documentationFormat": [ - "markdown", - "plaintext" - ], - "parameterInformation": { - "labelOffsetSupport": True - } - } + "documentationFormat": ["markdown", "plaintext"], + "parameterInformation": {"labelOffsetSupport": True}, + }, }, - "synchronization": { - "didSave": True, - "dynamicRegistration": True, - "willSave": True, - "willSaveWaitUntil": True - }, - "typeDefinition": { - "dynamicRegistration": True, - "linkSupport": True - }, - "typeHierarchy": { - "dynamicRegistration": 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 + "showDocument": {"support": True}, + "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}}, + "workDoneProgress": True, }, "workspace": { "applyEdit": True, - "codeLens": { - "refreshSupport": True - }, + "codeLens": {"refreshSupport": True}, "configuration": True, - "diagnostics": { - "refreshSupport": True - }, - "didChangeConfiguration": { - "dynamicRegistration": True - }, - "didChangeWatchedFiles": { - "dynamicRegistration": True, - "relativePatternSupport": True - }, - "executeCommand": { - "dynamicRegistration": True - }, + "diagnostics": {"refreshSupport": True}, + "didChangeConfiguration": {"dynamicRegistration": True}, + "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True}, + "executeCommand": {"dynamicRegistration": True}, "fileOperations": { "didCreate": True, "didDelete": True, @@ -441,70 +257,38 @@ class SourceKitLSP(SolidLanguageServer): "dynamicRegistration": True, "willCreate": True, "willDelete": True, - "willRename": True - }, - "foldingRange": { - "refreshSupport": True - }, - "inlayHint": { - "refreshSupport": True - }, - "inlineValue": { - "refreshSupport": True - }, - "semanticTokens": { - "refreshSupport": False + "willRename": True, }, + "foldingRange": {"refreshSupport": True}, + "inlayHint": {"refreshSupport": True}, + "inlineValue": {"refreshSupport": True}, + "semanticTokens": {"refreshSupport": False}, "symbol": { "dynamicRegistration": True, - "resolveSupport": { - "properties": [ - "location.range" - ] - }, + "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 - ] + "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 - ] - } + "tagSupport": {"valueSet": [1]}, }, "workspaceEdit": { - "changeAnnotationSupport": { - "groupsOnLabel": True - }, + "changeAnnotationSupport": {"groupsOnLabel": True}, "documentChanges": True, "failureHandling": "textOnlyTransactional", "normalizesLineEndings": True, - "resourceOperations": [ - "create", - "rename", - "delete" - ] + "resourceOperations": ["create", "rename", "delete"], }, - "workspaceFolders": True - } - }, - "clientInfo": { - "name": "Visual Studio Code", - "version": "1.102.2" + "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" - } - }, + "textDocument/codeLens": {"supportedCommands": {"swift.debug": "swift.debug", "swift.run": "swift.run"}}, "window/didChangeActiveDocument": True, "workspace/getReferenceDocument": True, - "workspace/peekDocuments": True + "workspace/peekDocuments": True, }, "locale": "en", "processId": os.getpid(), @@ -517,7 +301,7 @@ class SourceKitLSP(SolidLanguageServer): } ], } - + return initialize_params def _start_server(self): @@ -549,13 +333,18 @@ class SourceKitLSP(SolidLanguageServer): 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() \ No newline at end of file + 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) diff --git a/test/solidlsp/swift/test_swift_basic.py b/test/solidlsp/swift/test_swift_basic.py index 464553e9..67dfe686 100644 --- a/test/solidlsp/swift/test_swift_basic.py +++ b/test/solidlsp/swift/test_swift_basic.py @@ -6,8 +6,6 @@ like request_references using the Swift test repository. """ import os -import sys -import time import platform import pytest @@ -17,30 +15,31 @@ 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}" @@ -49,17 +48,17 @@ class TestSwiftLanguageServerBasics: 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}" @@ -68,16 +67,16 @@ class TestSwiftLanguageServerBasics: 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}" @@ -86,42 +85,38 @@ class TestSwiftLanguageServerBasics: 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" @@ -132,17 +127,17 @@ class TestSwiftLanguageServerBasics: # 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" @@ -160,17 +155,16 @@ class TestSwiftLanguageServerBasics: 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: @@ -187,7 +181,7 @@ class TestSwiftProjectBasics: 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 @@ -209,7 +203,7 @@ class TestSwiftProjectBasics: 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 + # 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" @@ -225,4 +219,4 @@ class TestSwiftProjectBasics: 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" \ No newline at end of file + assert len(status_matches) > 0, "Should find Status enum"