fix(bicep): stop corrupting hover/definition/references/symbols/formatting/codeAction with completion injections
_ls_to_client called _inject_completions() on EVERY response with an id+result,
not just textDocument/completion responses. pop_context() defaulted to
{'type': 'unknown'} for any untracked id, which _inject_completions treats as
'inject module-name completions' — so list-shaped LS responses for
definition/references/documentSymbol/formatting/codeAction were silently
replaced or prefixed with the Bicep module catalog list instead of returning
real Bicep LangServer results.
Found via a full LSP-capability audit against production: definition,
references, documentSymbol, formatting and codeAction all incorrectly
returned module-completion items instead of real results.
Fix: pop_context() now returns None when the response id wasn't recorded as
a pending completion request, and _ls_to_client only calls
_inject_completions() when a real completion context was found — every other
response now passes through untouched.
This commit is contained in:
@@ -187,8 +187,12 @@ class _ProxySession:
|
|||||||
|
|
||||||
return {"type": "unknown"}
|
return {"type": "unknown"}
|
||||||
|
|
||||||
def pop_context(self, msg_id) -> dict:
|
def pop_context(self, msg_id) -> dict | None:
|
||||||
return self.pending.pop(msg_id, {"type": "unknown"})
|
"""Return the tracked completion context for msg_id, or None if this
|
||||||
|
response id does not correspond to a textDocument/completion request
|
||||||
|
we recorded (e.g. hover/definition/references/... responses must never
|
||||||
|
be treated as completion responses)."""
|
||||||
|
return self.pending.pop(msg_id, None)
|
||||||
|
|
||||||
|
|
||||||
# ── Completion injection ───────────────────────────────────────────────────────
|
# ── Completion injection ───────────────────────────────────────────────────────
|
||||||
@@ -316,10 +320,14 @@ def _ls_to_client(
|
|||||||
logger.debug("LS→Client: %d bytes", len(body))
|
logger.debug("LS→Client: %d bytes", len(body))
|
||||||
try:
|
try:
|
||||||
msg = json.loads(body)
|
msg = json.loads(body)
|
||||||
context: dict = {}
|
context = None
|
||||||
if "id" in msg and "result" in msg:
|
if "id" in msg and "result" in msg:
|
||||||
context = session.pop_context(msg["id"])
|
context = session.pop_context(msg["id"])
|
||||||
out = _inject_completions(msg, context)
|
# Only rewrite responses that actually correspond to a
|
||||||
|
# textDocument/completion request we tracked — any other
|
||||||
|
# response (hover, definition, references, documentSymbol,
|
||||||
|
# formatting, codeAction, ...) must pass through untouched.
|
||||||
|
out = _inject_completions(msg, context) if context is not None else body
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
out = body
|
out = body
|
||||||
conn.sendall(_frame(out))
|
conn.sendall(_frame(out))
|
||||||
|
|||||||
@@ -502,6 +502,7 @@ def test_session_records_and_pops_context():
|
|||||||
assert ctx["type"] == "param"
|
assert ctx["type"] == "param"
|
||||||
assert ctx["module"] == "appservice"
|
assert ctx["module"] == "appservice"
|
||||||
|
|
||||||
# Second pop returns unknown
|
# Second pop (or any untracked id) returns None — the response must pass
|
||||||
assert session.pop_context(42)["type"] == "unknown"
|
# through untouched rather than being mistaken for a completion response.
|
||||||
|
assert session.pop_context(42) is None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user