chore(lsprotocol): migrate to 2025.0.0 and cleanup artifacts
The changes align the project with the 2025.0.0 lsprotocol release, removing the old backport and updating type hints in the protocol hooks to use Sequence where appropriate. The dist-info and packaging metadata for older lsprotocol versions are replaced with the new 2025.0.0 artifacts. - Remove exceptiongroup backport used on Python <3.11 - Use Sequence instead of List in LS protocol hooks - Replace old dist-info with 2025.0.0 metadata
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Iterable
|
||||
from difflib import get_close_matches
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from tclint.syntax_tree import ArgExpansion, BareWord, BracedWord, Node, QuotedWord
|
||||
@@ -18,6 +19,33 @@ class CommandArgError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_suggestion(value: str | None, candidates: Iterable[str]) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
unique_candidates = sorted({candidate for candidate in candidates if candidate})
|
||||
if value in unique_candidates:
|
||||
unique_candidates.remove(value)
|
||||
|
||||
if not unique_candidates:
|
||||
return None
|
||||
|
||||
cutoff = 0.85 if len(value) <= 3 else 0.75
|
||||
matches = get_close_matches(value, unique_candidates, n=1, cutoff=cutoff)
|
||||
if not matches:
|
||||
return None
|
||||
|
||||
return matches[0]
|
||||
|
||||
|
||||
def _did_you_mean_suffix(value: str | None, candidates: Iterable[str]) -> str:
|
||||
suggestion = get_suggestion(value, candidates)
|
||||
if suggestion is None:
|
||||
return ""
|
||||
|
||||
return f"; did you mean {suggestion}?"
|
||||
|
||||
|
||||
def arg_count(args: list[Node], parser: Parser) -> tuple[int, bool]:
|
||||
"""Returns the number of arguments in args, taking {*} into account.
|
||||
|
||||
@@ -83,6 +111,9 @@ def check_count(command, min=None, max=None):
|
||||
|
||||
|
||||
def eval(args: list[Node], parser: Parser, command: str) -> list[Node]:
|
||||
if len(args) == 1:
|
||||
return [parser.parse_script(args[0])]
|
||||
|
||||
if len(args) > 1 and any(isinstance(arg, (QuotedWord, BracedWord)) for arg in args):
|
||||
# Slightly odd restriction, but our syntax tree doesn't have a great way
|
||||
# to handle this case. We require each command argument to correspond to
|
||||
@@ -174,10 +205,19 @@ def check_arg_spec(
|
||||
mapping = map_positionals(positionals, arg_spec["positionals"], command)
|
||||
args = list(args)
|
||||
for arg_i, map_to_spec in zip(positional_args, mapping):
|
||||
arg = args[arg_i]
|
||||
|
||||
if _positional_has_type("script", arg_spec, map_to_spec):
|
||||
args[arg_i] = parser.parse_script(args[arg_i])
|
||||
args[arg_i] = parser.parse_script(arg)
|
||||
elif _positional_has_type("expression", arg_spec, map_to_spec):
|
||||
args[arg_i] = parser.parse_expression(args[arg_i])
|
||||
args[arg_i] = parser.parse_expression(arg)
|
||||
elif len(map_to_spec) == 1:
|
||||
positional_spec = arg_spec["positionals"][map_to_spec[0]]
|
||||
_validate_value(
|
||||
arg,
|
||||
positional_spec["value"],
|
||||
f"{command} {positional_spec['name']}",
|
||||
)
|
||||
|
||||
return args
|
||||
|
||||
@@ -201,12 +241,18 @@ def dispatch_subcommands(
|
||||
if "" in spec:
|
||||
return check_command(command, args, parser, spec[""])
|
||||
|
||||
valid_subcommands = [name for name in spec.keys() if name != ""]
|
||||
|
||||
if subcommand is not None:
|
||||
msg = f"invalid subcommand for {command}: got {subcommand}"
|
||||
suggestion = _did_you_mean_suffix(subcommand, valid_subcommands)
|
||||
else:
|
||||
msg = f"no subcommand provided for {command}"
|
||||
suggestion = ""
|
||||
|
||||
raise CommandArgError(f"{msg}, expected one of {', '.join(spec.keys())}")
|
||||
raise CommandArgError(
|
||||
f"{msg}, expected one of {', '.join(valid_subcommands)}{suggestion}"
|
||||
)
|
||||
|
||||
|
||||
def map_switches(
|
||||
@@ -250,17 +296,23 @@ def map_switches(
|
||||
continue
|
||||
|
||||
if contents in switches:
|
||||
if contents in mapped and not switches[contents]["repeated"]:
|
||||
switch_spec = switches[contents]
|
||||
|
||||
if contents in mapped and not switch_spec["repeated"]:
|
||||
raise CommandArgError(
|
||||
f"duplicate argument for {command_name}: {contents}"
|
||||
)
|
||||
if switches[contents]["value"]:
|
||||
if switch_spec["value"]:
|
||||
arg_i += 1
|
||||
if arg_i > len(args):
|
||||
expected = _switch_value_description(switch_spec)
|
||||
raise CommandArgError(
|
||||
f"invalid arguments for {command_name}: expected value after"
|
||||
f" {contents}"
|
||||
f"invalid arguments for {command_name}: expected"
|
||||
f" {expected} after {contents}"
|
||||
)
|
||||
_validate_value(
|
||||
args[arg_i - 1], switch_spec["value"], f"{command_name} {contents}"
|
||||
)
|
||||
mapped.add(contents)
|
||||
continue
|
||||
|
||||
@@ -281,11 +333,52 @@ def map_switches(
|
||||
f" {', '.join(prefix_matches)}"
|
||||
)
|
||||
|
||||
raise CommandArgError(f"unrecognized argument for {command_name}: {contents}")
|
||||
raise CommandArgError(
|
||||
f"unrecognized argument for {command_name}: {contents}"
|
||||
f"{_did_you_mean_suffix(contents, switches.keys())}"
|
||||
)
|
||||
|
||||
return mapped, positional_args
|
||||
|
||||
|
||||
def _switch_value_description(switch_spec: dict) -> str:
|
||||
metavar = switch_spec.get("metavar")
|
||||
if metavar is not None:
|
||||
return metavar
|
||||
|
||||
value_spec = switch_spec.get("value")
|
||||
if value_spec is None:
|
||||
return "value"
|
||||
|
||||
value_type = value_spec.get("type")
|
||||
if value_type == "int":
|
||||
return "int value"
|
||||
|
||||
return "value"
|
||||
|
||||
|
||||
def _validate_value(arg: Node, value_spec: dict | None, context: str) -> None:
|
||||
if value_spec is None:
|
||||
return
|
||||
|
||||
contents = arg.contents
|
||||
if contents is None:
|
||||
return
|
||||
|
||||
value_type = value_spec.get("type")
|
||||
if value_type in {"any", "variadic", "script", "expression"}:
|
||||
return
|
||||
|
||||
if value_type == "int":
|
||||
try:
|
||||
int(contents, 0)
|
||||
return
|
||||
except ValueError as error:
|
||||
raise CommandArgError(
|
||||
f"invalid value for {context}: got {contents}, expected {value_type}"
|
||||
) from error
|
||||
|
||||
|
||||
def map_positionals(
|
||||
args: list[Node], spec: list[dict], command_name: str
|
||||
) -> list[list[int]]:
|
||||
|
||||
Reference in New Issue
Block a user