Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

- Updated publisher name from `<david-yz-liu>` to `david-yz-liu`
- Fixed `substitute_attr` in the bundled language server not restoring the original value when the wrapped code raised an exception
- Parse PythonTA's complete standard output as JSON and log an error when the output is malformed.

### 🔧 Internal changes

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The extension checks for `.pylintrc`, `pylintrc`, and `pyproject.toml`, in that
When project-root configuration discovery is enabled, `python-ta.configPath` is ignored.
When discovery is disabled, `python-ta.configPath` specifies the configuration file instead.

The extension runs PythonTA with its LSP reporter and expects standard output to contain a complete
JSON document. PythonTA error output and JSON parsing errors are available in the
**PythonTA VS Code Extension** output channel.

## Developers

### Requirements
Expand Down
8 changes: 4 additions & 4 deletions bundled/tool/lsp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,12 @@ def _linting_helper(document: workspace.Document) -> list[lsp.Diagnostic]:

def _parse_json_output(content: str, doc_uri: str, use_stdin: bool = False) -> list[lsp.Diagnostic]:
"""Parses PythonTA's JSON output and maps it to LSP Diagnostics."""
json_start = content.find("[")
if json_start == -1:
try:
raw_results = json.loads(content)
except json.JSONDecodeError as error:
log_error(f"Failed to parse PythonTA output as JSON: {error}")
return []
content = content[json_start:]

raw_results = json.loads(content)
diagnostics_data = []

if use_stdin:
Expand Down
15 changes: 12 additions & 3 deletions src/test/python_tests/test_lsp_server_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ def test_parse_json_output_returns_stdin_diagnostics_despite_temporary_uri():
assert result[0].code == "E0001"


def test_parse_json_output_ignores_leading_log_lines():
def test_parse_json_output_rejects_leading_log_lines(monkeypatch):
errors = []
monkeypatch.setattr(lsp_server, "log_error", errors.append)
content = "some log preamble\nmore logs\n" + json.dumps(_make_payload())
result = lsp_server._parse_json_output(content, "file:///home/user/project/foo.py")
assert len(result) == 1

assert result == []
assert len(errors) == 1
assert "Failed to parse PythonTA output as JSON" in errors[0]


def test_parse_json_output_returns_empty_list_for_unmatched_uri():
Expand All @@ -133,8 +138,12 @@ def test_parse_json_output_returns_empty_list_for_unmatched_uri():
assert result == []


def test_parse_json_output_returns_empty_list_when_no_json_array_present():
def test_parse_json_output_reports_invalid_json(monkeypatch):
errors = []
monkeypatch.setattr(lsp_server, "log_error", errors.append)

assert lsp_server._parse_json_output("no json here", "file:///x.py") == []
assert len(errors) == 1


def test_parse_json_output_returns_empty_list_for_file_with_no_diagnostics():
Expand Down
Loading