Skip to content

Commit

Permalink
Merge pull request #717 from alcarney/fix-validation
Browse files Browse the repository at this point in the history
lsp: Clamp `Position` values to the valid range
  • Loading branch information
alcarney authored Jan 11, 2024
2 parents 3018110 + d1787f5 commit 3e7a536
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 1 deletion.
2 changes: 2 additions & 0 deletions lib/esbonio/changes/714.fix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``ValueError`` thrown when the server is given invalid ``Position`` data.
Invalid values will be clamped to the range permitted by the language server protocol (``[0, 2147483647]``)
13 changes: 13 additions & 0 deletions lib/esbonio/esbonio/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import warnings
from typing import Union

from lsprotocol import types
from pygls.protocol import default_converter

try:
Expand All @@ -16,6 +17,18 @@ def esbonio_converter():
converter = default_converter()
converter.register_structure_hook(Union[Literal["auto"], int], lambda obj, _: obj)

def position_hook(obj, type_):
"""Parse a position, gracefully handling invalid data"""
l: int = obj.get("line", None) or 0
c: int = obj.get("character", None) or 0

# Clamp positions to valid range.
return types.Position(
line=min(max(l, 0), 2147483647),
character=min(max(c, 0), 2147483647),
)

converter.register_structure_hook(types.Position, position_hook)
return converter


Expand Down
2 changes: 1 addition & 1 deletion lib/esbonio/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ deps =
extras = test
commands =
python ../../scripts/check-sphinx-version.py
pytest {posargs}
pytest {posargs:tests}
[testenv:pkg]
deps =
Expand Down
1 change: 1 addition & 0 deletions lib/esbonio/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ test =
mock; python_version<"3.8"
pytest
pytest-lsp>=0.3.1
pytest-asyncio<0.23
pytest-cov
pytest-timeout
typecheck =
Expand Down
34 changes: 34 additions & 0 deletions lib/esbonio/tests/sphinx-default/test_sd_code_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import attrs
import pytest
from lsprotocol import types
from pytest_lsp import LanguageClient


@pytest.mark.asyncio
async def test_code_actions_invalid_params(client: LanguageClient):
"""Ensure that the server can handle invalid code actions data."""

with attrs.validators.disabled():
params = types.CodeActionParams(
text_document=types.TextDocumentIdentifier(
uri=client.root_uri + "/test.rst"
),
range=types.Range(
start=types.Position(line=1, character=0),
end=types.Position(line=1, character=5),
),
context=types.CodeActionContext(
diagnostics=[
types.Diagnostic(
message="I am an invalid diagnostic",
range=types.Range(
start=types.Position(line=1, character=0),
end=types.Position(line=1, character=int(1e100)),
),
)
]
),
)

results = await client.text_document_code_action_async(params)
assert results == []
51 changes: 51 additions & 0 deletions lib/esbonio/tests/unit_tests/test_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest
from lsprotocol import types

from esbonio.cli import esbonio_converter


@pytest.mark.parametrize(
"data, expected",
[
(dict(line=None, character=0), types.Position(line=0, character=0)),
(dict(line=-1, character=0), types.Position(line=0, character=0)),
(
dict(line=int(1e100), character=0),
types.Position(line=2147483647, character=0),
),
(dict(line=1, character=-2), types.Position(line=1, character=0)),
(dict(line=1, character=None), types.Position(line=1, character=0)),
(
dict(line=1, character=int(1e100)),
types.Position(line=1, character=2147483647),
),
(
dict(
diagnostics=[
dict(
message="Example message",
range=dict(
start=dict(line=1, character=0),
end=dict(line=1, character=int(1e100)),
),
)
]
),
types.CodeActionContext(
diagnostics=[
types.Diagnostic(
message="Example message",
range=types.Range(
start=types.Position(line=1, character=0),
end=types.Position(line=1, character=2147483647),
),
),
],
),
),
],
)
def test_parse_invalid_data(data, expected):
"""Ensure that we can handle invalid data as gracefully as possible."""
converter = esbonio_converter()
assert converter.structure(data, type(expected)) == expected

0 comments on commit 3e7a536

Please sign in to comment.