From 6a8f7242995aa873f999bf9688eebf6d2a29348e Mon Sep 17 00:00:00 2001 From: Hamza Khyari Date: Thu, 19 Mar 2026 03:59:44 +0000 Subject: [PATCH] feat(lsp): add missing LspCapability enum values Align LspCapability enum with the spec's 11-capability set (docs/specification.md lines 20705-20717): Renamed: TYPE_INFO -> HOVER, SYMBOLS -> DOCUMENT_SYMBOLS, FORMAT -> FORMATTING Added: DEFINITIONS, SIGNATURE_HELP, WORKSPACE_SYMBOLS Updated LspToolAdapter._CAPABILITY_TOOL_MAP (11 entries): - code_actions suffix -> code-actions (hyphen per spec) - RENAME gets dedicated schema with new_name parameter - workspace_symbols gets query-based schema Updated _input_schema_for() with 4 schema categories extracted to module-level constants: file-only, position-based, rename (with new_name), and query-based. Added defensive ValueError for unmapped capabilities. Added additionalProperties: false. Extended LspClient.initialize() to advertise all 11 capabilities in the initialize request (hover, definition, references, rename, codeAction, formatting, signatureHelp, documentSymbol, workspace symbol). Fixed _make_runtime_handler() to handle workspace_symbols as query-only input (no file_path required), resolving the schema/handler contract mismatch. Fixed stale references: type_info -> hover, symbols -> document_symbols in test fixtures, feature files, CLI docstring. Updated docs/reference/lsp.md and CHANGELOG.md. Behave tests (38 scenarios): enum completeness, tool spec generation with structural validation, input schema per category, all 11 stubbed provider keys, negative tests for invalid capability and defensive ValueError branch. ISSUES CLOSED: #834 --- CHANGELOG.md | 11 +- docs/reference/lsp.md | 26 +-- features/consolidated_misc.feature | 2 +- features/lsp_capability_enum.feature | 122 ++++++++++++ features/steps/lsp_capability_enum_steps.py | 184 +++++++++++++++++++ features/steps/lsp_cli_new_coverage_steps.py | 4 +- src/cleveragents/cli/commands/lsp.py | 2 +- src/cleveragents/lsp/client.py | 27 +++ src/cleveragents/lsp/models.py | 9 +- src/cleveragents/lsp/tool_adapter.py | 133 +++++++++++--- 10 files changed, 474 insertions(+), 46 deletions(-) create mode 100644 features/lsp_capability_enum.feature create mode 100644 features/steps/lsp_capability_enum_steps.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 71734923a..cf19ca169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## Unreleased -<<<<<<< HEAD - Added TDD bug-capture tests for bug #1076 — `use_action()` does not propagate `automation_profile` to Plan. Three Behave BDD scenarios (`@tdd_bug @tdd_bug_1076 @tdd_expected_fail`) verify the full precedence @@ -23,7 +22,7 @@ ValueError with a distinctive message and asserts the message appears in the structlog warning log — which currently fails, confirming the bug. The `@tdd_expected_fail` tag inverts this to a CI pass until the fix is merged. - (#1093) + (#1093) - Added ResourceHandler sandbox and checkpoint lifecycle methods: `create_sandbox` (idempotent, delegates to SandboxManager), `create_checkpoint`, `rollback_to`, and `project_access`. Frozen @@ -33,6 +32,14 @@ FsDirectoryHandler uses `shutil.copytree` snapshot and clear-and-restore. Default `project_access` delegates to PermissionService (local mode = always permit). (#836) +- Added 5 missing LSP capabilities to `LspCapability` enum: `HOVER`, + `DEFINITIONS`, `SIGNATURE_HELP`, `DOCUMENT_SYMBOLS`, `WORKSPACE_SYMBOLS`. + Renamed `TYPE_INFO` -> `HOVER`, `SYMBOLS` -> `DOCUMENT_SYMBOLS`, + `FORMAT` -> `FORMATTING` for spec alignment. Updated tool adapter with + 11 capability mappings, RENAME schema with `new_name` parameter, + workspace-symbols query schema, and defensive schema validation. + Extended `initialize()` to advertise all 11 capabilities. Fixed + `workspace_symbols` runtime handler to accept query-only input. (#834) - Added TDD bug-capture E2E tests for bug #1028 — ACMS indexing pipeline not wired into CLI. Four Robot Framework E2E tests prove ContextTierService starts empty on every CLI invocation. Tests use ``@tdd_expected_fail`` until the bug diff --git a/docs/reference/lsp.md b/docs/reference/lsp.md index eeab8b6bf..1f0916cf0 100644 --- a/docs/reference/lsp.md +++ b/docs/reference/lsp.md @@ -20,16 +20,19 @@ The registry is available for configuration and validation purposes even in loca Enum of LSP features that can be exposed as tools: -| Value | Description | -|----------------|------------------------------------------| -| `DIAGNOSTICS` | Retrieve diagnostics (errors/warnings) | -| `TYPE_INFO` | Get type information at a cursor position| -| `SYMBOLS` | List document and workspace symbols | -| `COMPLETIONS` | Get code completion suggestions | -| `REFERENCES` | Find all references to a symbol | -| `RENAME` | Rename a symbol across the workspace | -| `CODE_ACTIONS` | Retrieve available code actions | -| `FORMAT` | Format a document or selection | +| Value | Description | +|--------------------|------------------------------------------------| +| `DIAGNOSTICS` | Retrieve diagnostics (errors/warnings) | +| `HOVER` | Get type information and documentation | +| `COMPLETIONS` | Get code completion suggestions | +| `DEFINITIONS` | Go to the definition of a symbol | +| `REFERENCES` | Find all references to a symbol | +| `RENAME` | Rename a symbol across the workspace | +| `CODE_ACTIONS` | Retrieve available code actions for a range | +| `FORMATTING` | Format a document or selection | +| `SIGNATURE_HELP` | Get function signature information at a call | +| `DOCUMENT_SYMBOLS` | List all symbols in a file | +| `WORKSPACE_SYMBOLS`| Search for symbols across the workspace | ### LspServerConfig @@ -101,7 +104,8 @@ languages: ["python"] capabilities: - diagnostics - completions - - type_info + - hover + - definitions - references - rename env: diff --git a/features/consolidated_misc.feature b/features/consolidated_misc.feature index cbf856053..b4d8483f1 100644 --- a/features/consolidated_misc.feature +++ b/features/consolidated_misc.feature @@ -1228,7 +1228,7 @@ Feature: Consolidated Misc Scenario: Register LSP server with capabilities Given a clean LSP registry Given an LSP server config named "local/pyright" for language "python" with command "pyright-langserver" - And the LSP server config has capabilities "diagnostics,completions,type_info" + And the LSP server config has capabilities "diagnostics,completions,hover" When the LSP server is registered Then the LSP server "local/pyright" should have 3 capabilities diff --git a/features/lsp_capability_enum.feature b/features/lsp_capability_enum.feature new file mode 100644 index 000000000..586c26def --- /dev/null +++ b/features/lsp_capability_enum.feature @@ -0,0 +1,122 @@ +@lsp @capability +Feature: LspCapability Enum Completeness + As a developer integrating LSP servers + I want the LspCapability enum to include all spec-defined capabilities + So that the tool adapter generates correct tool specs + + # ── Enum completeness ────────────────────────────────────── + + Scenario: LspCapability enum has exactly 11 members + Given the LspCapability enum for lsp_cap + Then the lsp_cap enum should have 11 members + + Scenario Outline: LspCapability enum includes + Given the LspCapability enum for lsp_cap + Then the lsp_cap enum should include "" + + Examples: + | member | + | diagnostics | + | hover | + | completions | + | definitions | + | references | + | rename | + | code_actions | + | formatting | + | signature_help | + | document_symbols | + | workspace_symbols | + + # ── Tool adapter generates specs for all capabilities ────── + + Scenario: Tool adapter has mappings for all 11 capabilities + Given the lsp_cap capability tool map + Then the lsp_cap tool map should have 11 entries + + Scenario Outline: Tool adapter generates tool spec for + Given an LspServerConfig with capability "" for lsp_cap + When I generate tool specs for lsp_cap + Then the lsp_cap generated specs should not be empty + And the lsp_cap tool spec should have keys "name" and "input_schema" + And the lsp_cap tool name should end with "" + + Examples: + | capability | tool_suffix | + | diagnostics | diagnostics | + | hover | hover | + | completions | completions | + | definitions | definition | + | references | references | + | rename | rename | + | code_actions | code-actions | + | formatting | format | + | signature_help | signature | + | document_symbols | symbols | + | workspace_symbols | workspace-symbols | + + # ── Input schemas per category ───────────────────────────── + + Scenario Outline: File-only capability has file_path schema + Given the lsp_cap input schema for "" + Then the lsp_cap schema should require "file_path" + And the lsp_cap schema should not require "line" + + Examples: + | cap | + | diagnostics | + | formatting | + | document_symbols | + + Scenario Outline: Position capability has file+line+column schema + Given the lsp_cap input schema for "" + Then the lsp_cap schema should require "file_path" + And the lsp_cap schema should require "line" + And the lsp_cap schema should require "column" + + Examples: + | cap | + | hover | + | completions | + | definitions | + | references | + | code_actions | + | signature_help | + + Scenario: Rename capability has file+line+column+new_name schema + Given the lsp_cap input schema for "rename" + Then the lsp_cap schema should require "file_path" + And the lsp_cap schema should require "line" + And the lsp_cap schema should require "column" + And the lsp_cap schema should require "new_name" + + Scenario: Workspace symbols has query schema + Given the lsp_cap input schema for "workspace_symbols" + Then the lsp_cap schema should require "query" + And the lsp_cap schema should not require "file_path" + + # ── Stubbed server capabilities include all keys ─────────── + + Scenario: Stubbed server capabilities include all LSP providers + Given the lsp_cap stubbed server capabilities + Then the lsp_cap stubbed caps should include "hoverProvider" + And the lsp_cap stubbed caps should include "definitionProvider" + And the lsp_cap stubbed caps should include "signatureHelpProvider" + And the lsp_cap stubbed caps should include "documentSymbolProvider" + And the lsp_cap stubbed caps should include "workspaceSymbolProvider" + And the lsp_cap stubbed caps should include "completionProvider" + And the lsp_cap stubbed caps should include "referencesProvider" + And the lsp_cap stubbed caps should include "renameProvider" + And the lsp_cap stubbed caps should include "codeActionProvider" + And the lsp_cap stubbed caps should include "documentFormattingProvider" + And the lsp_cap stubbed caps should include "diagnosticProvider" + + # ── Negative tests ───────────────────────────────────────── + + Scenario: Invalid capability value raises ValueError + When I create an LspCapability with value "nonexistent" for lsp_cap + Then the lsp_cap creation should raise a ValueError + + Scenario: _input_schema_for raises ValueError for unknown capability + When I call _input_schema_for with a fake capability for lsp_cap + Then the lsp_cap schema call should raise a ValueError diff --git a/features/steps/lsp_capability_enum_steps.py b/features/steps/lsp_capability_enum_steps.py new file mode 100644 index 000000000..b9f2d411e --- /dev/null +++ b/features/steps/lsp_capability_enum_steps.py @@ -0,0 +1,184 @@ +"""Step definitions for lsp_capability_enum.feature.""" + +from __future__ import annotations + +from behave import given, then, when + +from cleveragents.lsp.models import LspCapability, LspServerConfig +from cleveragents.lsp.tool_adapter import ( + _CAPABILITY_TOOL_MAP, + LspToolAdapter, + _input_schema_for, +) + +# ── Given steps ────────────────────────────────────────────── + + +@given("the LspCapability enum for lsp_cap") +def step_load_enum_lsp_cap(context: object) -> None: + """Load the LspCapability enum.""" + context.lsp_cap_enum = LspCapability # type: ignore[attr-defined] + + +@given("the lsp_cap capability tool map") +def step_load_tool_map_lsp_cap(context: object) -> None: + """Load the capability tool map.""" + context.lsp_cap_tool_map = _CAPABILITY_TOOL_MAP # type: ignore[attr-defined] + + +@given('an LspServerConfig with capability "{capability}" for lsp_cap') +def step_config_with_capability_lsp_cap( + context: object, + capability: str, +) -> None: + """Create an LspServerConfig with a single capability.""" + cap = LspCapability(capability) + context.lsp_cap_config = LspServerConfig( # type: ignore[attr-defined] + name="test/server", + languages=["python"], + command="test-server", + capabilities=[cap], + ) + + +@given('the lsp_cap input schema for "{capability}"') +def step_input_schema_lsp_cap(context: object, capability: str) -> None: + """Get the input schema for a capability.""" + cap = LspCapability(capability) + context.lsp_cap_schema = _input_schema_for(cap) # type: ignore[attr-defined] + + +@given("the lsp_cap stubbed server capabilities") +def step_stubbed_caps_lsp_cap(context: object) -> None: + """Load the stubbed server capabilities.""" + from cleveragents.lsp.server import _STUBBED_CAPABILITIES + + context.lsp_cap_stubbed = _STUBBED_CAPABILITIES # type: ignore[attr-defined] + + +# ── When steps ─────────────────────────────────────────────── + + +@when("I generate tool specs for lsp_cap") +def step_generate_specs_lsp_cap(context: object) -> None: + """Generate tool specs from the config.""" + config = context.lsp_cap_config # type: ignore[attr-defined] + adapter = LspToolAdapter() + context.lsp_cap_specs = adapter.generate_tool_specs(config) # type: ignore[attr-defined] + + +# ── Then steps ─────────────────────────────────────────────── + + +@then("the lsp_cap enum should have {count:d} members") +def step_enum_count_lsp_cap(context: object, count: int) -> None: + """Assert the enum has the expected number of members.""" + members = list(context.lsp_cap_enum) # type: ignore[attr-defined] + assert len(members) == count, ( + f"Expected {count} members, got {len(members)}: {[m.value for m in members]}" + ) + + +@then('the lsp_cap enum should include "{member}"') +def step_enum_includes_lsp_cap(context: object, member: str) -> None: + """Assert the enum includes the given member value.""" + values = [m.value for m in context.lsp_cap_enum] # type: ignore[attr-defined] + assert member in values, f"'{member}' not in enum values: {values}" + + +@then("the lsp_cap tool map should have {count:d} entries") +def step_tool_map_count_lsp_cap(context: object, count: int) -> None: + """Assert the tool map has the expected number of entries.""" + tool_map = context.lsp_cap_tool_map # type: ignore[attr-defined] + assert len(tool_map) == count, f"Expected {count} entries, got {len(tool_map)}" + + +@then("the lsp_cap generated specs should not be empty") +def step_specs_not_empty_lsp_cap(context: object) -> None: + """Assert generated specs are not empty.""" + specs = context.lsp_cap_specs # type: ignore[attr-defined] + assert len(specs) > 0, "Expected non-empty tool specs" + + +@then('the lsp_cap tool spec should have keys "name" and "input_schema"') +def step_spec_has_keys_lsp_cap(context: object) -> None: + """Assert generated specs have required keys.""" + specs = context.lsp_cap_specs # type: ignore[attr-defined] + for spec in specs: + assert "name" in spec, f"Spec missing 'name': {spec}" + assert "input_schema" in spec, f"Spec missing 'input_schema': {spec}" + + +@then('the lsp_cap tool name should end with "{suffix}"') +def step_tool_name_ends_with_lsp_cap(context: object, suffix: str) -> None: + """Assert at least one tool name ends with the suffix.""" + specs = context.lsp_cap_specs # type: ignore[attr-defined] + names = [s["name"] for s in specs] + assert any(n.endswith(f"/{suffix}") for n in names), ( + f"No tool name ends with '/{suffix}': {names}" + ) + + +@then('the lsp_cap schema should require "{field}"') +def step_schema_requires_lsp_cap(context: object, field: str) -> None: + """Assert the schema requires the given field.""" + schema = context.lsp_cap_schema # type: ignore[attr-defined] + required = schema.get("required", []) + assert field in required, f"'{field}' not in required: {required}" + + +@then('the lsp_cap schema should not require "{field}"') +def step_schema_not_requires_lsp_cap(context: object, field: str) -> None: + """Assert the schema does not require the given field.""" + schema = context.lsp_cap_schema # type: ignore[attr-defined] + required = schema.get("required", []) + assert field not in required, f"'{field}' should not be in required: {required}" + + +@then('the lsp_cap stubbed caps should include "{key}"') +def step_stubbed_includes_lsp_cap(context: object, key: str) -> None: + """Assert the stubbed capabilities dict includes the key.""" + caps = context.lsp_cap_stubbed # type: ignore[attr-defined] + assert key in caps, f"'{key}' not in stubbed capabilities: {list(caps)}" + + +# ── When/Then steps: negative tests ───────────────────────── + + +@when('I create an LspCapability with value "{value}" for lsp_cap') +def step_create_invalid_capability_lsp_cap(context: object, value: str) -> None: + """Attempt to create an LspCapability with an invalid value.""" + context.lsp_cap_error = None # type: ignore[attr-defined] + try: + LspCapability(value) + except ValueError as exc: + context.lsp_cap_error = exc # type: ignore[attr-defined] + + +@then("the lsp_cap creation should raise a ValueError") +def step_should_raise_value_error_lsp_cap(context: object) -> None: + """Assert that a ValueError was raised.""" + err = context.lsp_cap_error # type: ignore[attr-defined] + assert err is not None, "Expected ValueError but none was raised" + + +@when("I call _input_schema_for with a fake capability for lsp_cap") +def step_call_schema_fake_cap_lsp_cap(context: object) -> None: + """Call _input_schema_for with a value not in any category tuple.""" + from enum import StrEnum + + class _FakeCap(StrEnum): + FAKE = "fake_capability" + + context.lsp_cap_schema_error = None # type: ignore[attr-defined] + try: + _input_schema_for(_FakeCap.FAKE) # type: ignore[arg-type] + except ValueError as exc: + context.lsp_cap_schema_error = exc # type: ignore[attr-defined] + + +@then("the lsp_cap schema call should raise a ValueError") +def step_schema_should_raise_lsp_cap(context: object) -> None: + """Assert _input_schema_for raised ValueError.""" + err = context.lsp_cap_schema_error # type: ignore[attr-defined] + assert err is not None, "Expected ValueError from _input_schema_for but none raised" diff --git a/features/steps/lsp_cli_new_coverage_steps.py b/features/steps/lsp_cli_new_coverage_steps.py index 8c6b14c5c..5759ed989 100644 --- a/features/steps/lsp_cli_new_coverage_steps.py +++ b/features/steps/lsp_cli_new_coverage_steps.py @@ -38,7 +38,7 @@ languages: capabilities: - diagnostics - completions - - type_info + - hover """ _VALID_YAML_2 = """\ @@ -51,7 +51,7 @@ languages: - cpp capabilities: - diagnostics - - symbols + - document_symbols """ _VALID_YAML_ENV = """\ diff --git a/src/cleveragents/cli/commands/lsp.py b/src/cleveragents/cli/commands/lsp.py index f0fe71c2b..494f7c122 100644 --- a/src/cleveragents/cli/commands/lsp.py +++ b/src/cleveragents/cli/commands/lsp.py @@ -27,7 +27,7 @@ name: local/pyright command: pyright-langserver args: ["--stdio"] languages: ["python"] -capabilities: ["diagnostics", "completions", "type_info"] +capabilities: ["diagnostics", "completions", "hover"] ``` ## Error Handling diff --git a/src/cleveragents/lsp/client.py b/src/cleveragents/lsp/client.py index cfa0a3905..aaffb118a 100644 --- a/src/cleveragents/lsp/client.py +++ b/src/cleveragents/lsp/client.py @@ -232,6 +232,32 @@ class LspClient: "completion": { "completionItem": {"snippetSupport": False}, }, + "hover": {"contentFormat": ["plaintext", "markdown"]}, + "definition": {"linkSupport": False}, + "references": {}, + "rename": {"prepareSupport": False}, + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix", + "refactor", + "source", + ], + }, + }, + }, + "formatting": {}, + "signatureHelp": { + "signatureInformation": { + "parameterInformation": { + "labelOffsetSupport": True, + }, + }, + }, + "documentSymbol": { + "hierarchicalDocumentSymbolSupport": True, + }, "synchronization": { "didSave": True, "willSave": False, @@ -239,6 +265,7 @@ class LspClient: }, "workspace": { "workspaceFolders": True, + "symbol": {"symbolKind": {}}, }, }, } diff --git a/src/cleveragents/lsp/models.py b/src/cleveragents/lsp/models.py index 914ecf595..d16bad843 100644 --- a/src/cleveragents/lsp/models.py +++ b/src/cleveragents/lsp/models.py @@ -27,13 +27,16 @@ class LspCapability(StrEnum): """ DIAGNOSTICS = "diagnostics" - TYPE_INFO = "type_info" - SYMBOLS = "symbols" + HOVER = "hover" COMPLETIONS = "completions" + DEFINITIONS = "definitions" REFERENCES = "references" RENAME = "rename" CODE_ACTIONS = "code_actions" - FORMAT = "format" + FORMATTING = "formatting" + SIGNATURE_HELP = "signature_help" + DOCUMENT_SYMBOLS = "document_symbols" + WORKSPACE_SYMBOLS = "workspace_symbols" class LspServerConfig(BaseModel): diff --git a/src/cleveragents/lsp/tool_adapter.py b/src/cleveragents/lsp/tool_adapter.py index 74fff4b73..bb4669052 100644 --- a/src/cleveragents/lsp/tool_adapter.py +++ b/src/cleveragents/lsp/tool_adapter.py @@ -35,18 +35,18 @@ _CAPABILITY_TOOL_MAP: dict[LspCapability, tuple[str, str]] = { "diagnostics", "Retrieve diagnostics (errors/warnings) for a file", ), - LspCapability.TYPE_INFO: ( - "type_info", - "Get type information at a cursor position", - ), - LspCapability.SYMBOLS: ( - "symbols", - "List document and workspace symbols", + LspCapability.HOVER: ( + "hover", + "Get type information and documentation at a position", ), LspCapability.COMPLETIONS: ( "completions", "Get code completion suggestions at a position", ), + LspCapability.DEFINITIONS: ( + "definition", + "Go to the definition of a symbol", + ), LspCapability.REFERENCES: ( "references", "Find all references to a symbol", @@ -56,13 +56,25 @@ _CAPABILITY_TOOL_MAP: dict[LspCapability, tuple[str, str]] = { "Rename a symbol across the workspace", ), LspCapability.CODE_ACTIONS: ( - "code_actions", + "code-actions", "Retrieve available code actions for a range", ), - LspCapability.FORMAT: ( + LspCapability.FORMATTING: ( "format", "Format a document or selection", ), + LspCapability.SIGNATURE_HELP: ( + "signature", + "Get function signature information at a call site", + ), + LspCapability.DOCUMENT_SYMBOLS: ( + "symbols", + "List all symbols in a file", + ), + LspCapability.WORKSPACE_SYMBOLS: ( + "workspace-symbols", + "Search for symbols across the entire workspace", + ), } @@ -109,6 +121,17 @@ def _make_runtime_handler( """ def _handler(**kwargs: Any) -> Any: + # workspace_symbols is query-based, not file-based + if capability == LspCapability.WORKSPACE_SYMBOLS: + query: str = kwargs.get("query", "") + if not query: + return {"error": "query is required"} + raise LspNotAvailableError( + f"LSP capability '{capability.value}' is not yet implemented " + f"for server '{server_name}'", + details={"server": server_name, "capability": capability.value}, + ) + file_path: str = kwargs.get("file_path", "") if not file_path: return {"error": "file_path is required"} @@ -208,6 +231,24 @@ class LspToolAdapter: return specs +# Schema category constants (hoisted for zero-alloc lookup). +_FILE_ONLY_CAPABILITIES = ( + LspCapability.DIAGNOSTICS, + LspCapability.FORMATTING, + LspCapability.DOCUMENT_SYMBOLS, +) +_POSITION_BASED_CAPABILITIES = ( + LspCapability.HOVER, + LspCapability.COMPLETIONS, + LspCapability.DEFINITIONS, + LspCapability.REFERENCES, + LspCapability.CODE_ACTIONS, + LspCapability.SIGNATURE_HELP, +) +_RENAME_CAPABILITY = (LspCapability.RENAME,) +_QUERY_BASED_CAPABILITIES = (LspCapability.WORKSPACE_SYMBOLS,) + + def _input_schema_for(capability: LspCapability) -> dict[str, Any]: """Return a minimal JSON Schema for the given capability. @@ -216,30 +257,70 @@ def _input_schema_for(capability: LspCapability) -> dict[str, Any]: Returns: A JSON Schema dict. + + Raises: + ValueError: If no schema is defined for the capability. """ - base: dict[str, Any] = {"type": "object", "properties": {}} - if capability in ( - LspCapability.DIAGNOSTICS, - LspCapability.FORMAT, - LspCapability.SYMBOLS, - ): + base: dict[str, Any] = { + "type": "object", + "properties": {}, + "additionalProperties": False, + } + + if capability in _FILE_ONLY_CAPABILITIES: base["properties"] = { - "file_path": {"type": "string", "description": "Path to the file"}, + "file_path": { + "type": "string", + "description": "Path to the file", + }, } base["required"] = ["file_path"] - elif capability in ( - LspCapability.COMPLETIONS, - LspCapability.TYPE_INFO, - LspCapability.REFERENCES, - LspCapability.RENAME, - LspCapability.CODE_ACTIONS, - ): + elif capability in _POSITION_BASED_CAPABILITIES: base["properties"] = { - "file_path": {"type": "string", "description": "Path to the file"}, - "line": {"type": "integer", "description": "1-based line number"}, - "column": {"type": "integer", "description": "1-based column number"}, + "file_path": { + "type": "string", + "description": "Path to the file", + }, + "line": { + "type": "integer", + "description": "1-based line number", + }, + "column": { + "type": "integer", + "description": "1-based column number", + }, } base["required"] = ["file_path", "line", "column"] + elif capability in _RENAME_CAPABILITY: + base["properties"] = { + "file_path": { + "type": "string", + "description": "Path to the file", + }, + "line": { + "type": "integer", + "description": "1-based line number", + }, + "column": { + "type": "integer", + "description": "1-based column number", + }, + "new_name": { + "type": "string", + "description": "New name for the symbol", + }, + } + base["required"] = ["file_path", "line", "column", "new_name"] + elif capability in _QUERY_BASED_CAPABILITIES: + base["properties"] = { + "query": { + "type": "string", + "description": "Symbol search query", + }, + } + base["required"] = ["query"] + else: + raise ValueError(f"No input schema defined for capability: {capability}") return base