diff --git a/CHANGELOG.md b/CHANGELOG.md index ffcc011..f26149b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Updated publisher name from `` 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 diff --git a/README.md b/README.md index 1d8b857..3bbe47c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bundled/tool/lsp_server.py b/bundled/tool/lsp_server.py index 183cfa7..9ca564a 100644 --- a/bundled/tool/lsp_server.py +++ b/bundled/tool/lsp_server.py @@ -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: diff --git a/src/test/python_tests/test_lsp_server_helpers.py b/src/test/python_tests/test_lsp_server_helpers.py index 4414ce6..da3454e 100644 --- a/src/test/python_tests/test_lsp_server_helpers.py +++ b/src/test/python_tests/test_lsp_server_helpers.py @@ -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(): @@ -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():