From 74f100e2c5e9c9e7dbe5962882c762bd4313e84f Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Sat, 14 Jan 2023 13:59:22 +0000 Subject: [PATCH] lsp: Typing fixes --- .../directives/directive-registry.rst | 10 +++++----- lib/esbonio/esbonio/lsp/__init__.py | 4 ++-- lib/esbonio/esbonio/lsp/directives.py | 19 ++++++++++--------- lib/esbonio/esbonio/lsp/rst/directives.py | 11 +++++++---- lib/esbonio/esbonio/lsp/spelling.py | 16 ++++++++++------ lib/esbonio/esbonio/lsp/sphinx/cli.py | 7 +++++-- lib/esbonio/esbonio/lsp/sphinx/domains.py | 14 ++++++++------ 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/docs/lsp/extending/directives/directive-registry.rst b/docs/lsp/extending/directives/directive-registry.rst index 7f7f84fa6..d74a490db 100644 --- a/docs/lsp/extending/directives/directive-registry.rst +++ b/docs/lsp/extending/directives/directive-registry.rst @@ -44,7 +44,7 @@ This method should return a dictionary where the keys are the canonical name of def __init__(self, app: Sphinx): self.app = app # Sphinx application instance. - def index_directives(self) -> Dict[str, Directive]: + def index_directives(self) -> Dict[str, Type[Directive]]: directives = {} for prefix, domain in self.app.domains.items(): for name, directive in domain.directives.items(): @@ -74,12 +74,12 @@ The :meth:`~DirectiveLanguageFeature.suggest_directives` method is called each t It can be used to tailor the list of directives that are offered to the user, depending on the current context. Each ``DirectiveLanguageFeature`` has a default implementation, which may be sufficient depending on your use case:: - def suggest_directives(self, context: CompletionContext) -> Iterable[Tuple[str, Directive]]: + def suggest_directives(self, context: CompletionContext) -> Iterable[Tuple[str, Type[Directive]]]: return self.index_directives().items() However, in the case of Sphinx domains, we need to modify this to also include the short form of the directives in the standard and primary domains:: - def suggest_directives(self, context: CompletionContext) -> Iterable[Tuple[str, Directive]]: + def suggest_directives(self, context: CompletionContext) -> Iterable[Tuple[str, Type[Directive]]]: directives = self.index_directives() primary_domain = self.app.config.primary_domain @@ -103,12 +103,12 @@ The :meth:`~DirectiveLanguageFeature.get_implementation` method is used by the l This powers features such as documentation hovers and goto implementation. As with ``suggest_directives``, each ``DirectiveLanguageFeature`` has a default implementation which may be sufficient for your use case:: - def get_implementation(self, directive: str, domain: Optional[str]) -> Optional[Directive]: + def get_implementation(self, directive: str, domain: Optional[str]) -> Optional[Type[Directive]]: return self.index_directives().get(directive, None) In the case of Sphinx domains, if we see a directive without a domain prefix we need to see if it belongs to the standard or primary domains:: - def get_implementation(self, directive: str, domain: Optional[str]) -> Optional[Directive]: + def get_implementation(self, directive: str, domain: Optional[str]) -> Optional[Type[Directive]]: directives = self.index_directives() if domain is not None: diff --git a/lib/esbonio/esbonio/lsp/__init__.py b/lib/esbonio/esbonio/lsp/__init__.py index 85bdfaec2..53269d9d3 100644 --- a/lib/esbonio/esbonio/lsp/__init__.py +++ b/lib/esbonio/esbonio/lsp/__init__.py @@ -253,7 +253,7 @@ def on_completion(ls: RstLanguageServer, params: CompletionParams): ls.logger.debug("Completion context: %s", context) for item in feature.complete(context): - item.data = {"source_feature": name, **(item.data or {})} + item.data = {"source_feature": name, **(item.data or {})} # type: ignore items.append(item) return CompletionList(is_incomplete=False, items=items) @@ -264,7 +264,7 @@ def on_completion(ls: RstLanguageServer, params: CompletionParams): def on_completion_resolve( ls: RstLanguageServer, item: CompletionItem ) -> CompletionItem: - source = (item.data or {}).get("source_feature", "") + source = (item.data or {}).get("source_feature", "") # type: ignore feature = ls.get_feature(source) if not feature: diff --git a/lib/esbonio/esbonio/lsp/directives.py b/lib/esbonio/esbonio/lsp/directives.py index 152db20ea..df4b878a0 100644 --- a/lib/esbonio/esbonio/lsp/directives.py +++ b/lib/esbonio/esbonio/lsp/directives.py @@ -8,6 +8,7 @@ from typing import List from typing import Optional from typing import Tuple +from typing import Type from docutils.parsers.rst import Directive from lsprotocol.types import CompletionItem @@ -57,17 +58,17 @@ def complete_arguments( def get_implementation( self, directive: str, domain: Optional[str] - ) -> Optional[Directive]: + ) -> Optional[Type[Directive]]: """Return the implementation for the given directive name.""" return self.index_directives().get(directive, None) - def index_directives(self) -> Dict[str, Directive]: + def index_directives(self) -> Dict[str, Type[Directive]]: """Return all known directives.""" return dict() def suggest_directives( self, context: CompletionContext - ) -> Iterable[Tuple[str, Directive]]: + ) -> Iterable[Tuple[str, Type[Directive]]]: """Suggest directives that may be used, given a completion context.""" return self.index_directives().items() @@ -80,7 +81,7 @@ def suggest_options( if impl is None: return [] - option_spec = impl.option_spec or {} + option_spec = getattr(impl, "option_spec", {}) return option_spec.keys() def resolve_argument_link( @@ -427,7 +428,7 @@ class name of the directive's implementation. doc["description"] = "\n".join(description) self._documentation[key] = doc - def get_directives(self) -> Dict[str, Directive]: + def get_directives(self) -> Dict[str, Type[Directive]]: """Return a dictionary of all known directives.""" directives = {} @@ -446,7 +447,7 @@ def get_directives(self) -> Dict[str, Directive]: def get_implementation( self, directive: str, domain: Optional[str] - ) -> Optional[Directive]: + ) -> Optional[Type[Directive]]: """Return the implementation of a directive given its name Parameters @@ -483,7 +484,7 @@ def get_implementation( def suggest_directives( self, context: CompletionContext - ) -> Iterable[Tuple[str, Directive]]: + ) -> Iterable[Tuple[str, Type[Directive]]]: """Suggest directives that may be used, given a completion context. Parameters @@ -604,9 +605,9 @@ def complete_directives(self, context: CompletionContext) -> List[CompletionItem # TODO: Give better names to arguments based on what they represent. if include_argument: insert_format = InsertTextFormat.Snippet + nargs = getattr(directive, "required_arguments", 0) args = " " + " ".join( - "${{{0}:arg{0}}}".format(i) - for i in range(1, directive.required_arguments + 1) + "${{{0}:arg{0}}}".format(i) for i in range(1, nargs + 1) ) else: args = "" diff --git a/lib/esbonio/esbonio/lsp/rst/directives.py b/lib/esbonio/esbonio/lsp/rst/directives.py index 0dafc139f..5e030a4ae 100644 --- a/lib/esbonio/esbonio/lsp/rst/directives.py +++ b/lib/esbonio/esbonio/lsp/rst/directives.py @@ -3,6 +3,7 @@ from typing import Dict from typing import Optional from typing import Tuple +from typing import Type from typing import Union import pkg_resources @@ -18,11 +19,11 @@ class Docutils(DirectiveLanguageFeature): def __init__(self) -> None: - self._directives: Optional[Dict[str, Directive]] = None + self._directives: Optional[Dict[str, Type[Directive]]] = None """Cache for known directives.""" @property - def directives(self) -> Dict[str, Directive]: + def directives(self) -> Dict[str, Type[Directive]]: if self._directives is not None: return self._directives @@ -46,11 +47,13 @@ def get_implementation(self, directive: str, domain: Optional[str]): return self.directives.get(directive, None) - def index_directives(self) -> Dict[str, Directive]: + def index_directives(self) -> Dict[str, Type[Directive]]: return self.directives -def resolve_directive(directive: Union[Directive, Tuple[str, str]]) -> Directive: +def resolve_directive( + directive: Union[Type[Directive], Tuple[str, str]] +) -> Type[Directive]: """Return the directive based on the given reference. 'Core' docutils directives are returned as tuples ``(modulename, ClassName)`` diff --git a/lib/esbonio/esbonio/lsp/spelling.py b/lib/esbonio/esbonio/lsp/spelling.py index 1a723166c..a0e554661 100644 --- a/lib/esbonio/esbonio/lsp/spelling.py +++ b/lib/esbonio/esbonio/lsp/spelling.py @@ -9,13 +9,13 @@ from lsprotocol.types import CodeAction from lsprotocol.types import CodeActionKind from lsprotocol.types import CodeActionParams +from lsprotocol.types import Diagnostic +from lsprotocol.types import DiagnosticSeverity +from lsprotocol.types import DidSaveTextDocumentParams +from lsprotocol.types import Position +from lsprotocol.types import Range from lsprotocol.types import TextEdit from lsprotocol.types import WorkspaceEdit -from lsprotocol.types.basic_structures import Diagnostic -from lsprotocol.types.basic_structures import DiagnosticSeverity -from lsprotocol.types.basic_structures import Position -from lsprotocol.types.basic_structures import Range -from lsprotocol.types.workspace import DidSaveTextDocumentParams from spellchecker import SpellChecker # type: ignore from esbonio.lsp.rst import LanguageFeature @@ -48,7 +48,11 @@ def code_action(self, params: CodeActionParams) -> List[CodeAction]: if len(ranges) > 0 and diagnostic.range not in ranges: continue - for fix in self.lang.candidates(error.text): + fixes = self.lang.candidates(error.text) + if fixes is None: + continue + + for fix in fixes: actions.append( CodeAction( title=f"Correct '{error.text}' -> '{fix}'", diff --git a/lib/esbonio/esbonio/lsp/sphinx/cli.py b/lib/esbonio/esbonio/lsp/sphinx/cli.py index 11870b377..78e04479b 100644 --- a/lib/esbonio/esbonio/lsp/sphinx/cli.py +++ b/lib/esbonio/esbonio/lsp/sphinx/cli.py @@ -4,6 +4,7 @@ import sys from typing import List +from esbonio.cli import esbonio_converter from esbonio.lsp.sphinx import SphinxConfig @@ -17,7 +18,8 @@ def config_cmd(args, extra): def config_to_cli(config: str): - conf = SphinxConfig(**json.loads(config)) + converter = esbonio_converter() + conf = converter.structure(json.loads(config), SphinxConfig) print(" ".join(conf.to_cli_args())) return 0 @@ -27,7 +29,8 @@ def cli_to_config(cli_args: List[str]): if conf is None: return 1 - print(json.dumps(conf.dict(by_alias=True), indent=2)) + converter = esbonio_converter() + print(json.dumps(converter.unstructure(conf), indent=2)) return 0 diff --git a/lib/esbonio/esbonio/lsp/sphinx/domains.py b/lib/esbonio/esbonio/lsp/sphinx/domains.py index e7d7ecc37..d85c663a2 100644 --- a/lib/esbonio/esbonio/lsp/sphinx/domains.py +++ b/lib/esbonio/esbonio/lsp/sphinx/domains.py @@ -7,6 +7,7 @@ from typing import Optional from typing import Set from typing import Tuple +from typing import Type import pygls.uris as Uri from docutils import nodes @@ -70,11 +71,11 @@ class DomainDirectives(DirectiveLanguageFeature, DomainHelpers): def __init__(self, rst: SphinxLanguageServer): self.rst = rst - self._directives: Optional[Dict[str, Directive]] = None + self._directives: Optional[Dict[str, Type[Directive]]] = None """Cache for known directives.""" @property - def directives(self) -> Dict[str, Directive]: + def directives(self) -> Dict[str, Type[Directive]]: if self._directives is not None: return self._directives @@ -89,7 +90,7 @@ def directives(self) -> Dict[str, Directive]: def get_implementation( self, directive: str, domain: Optional[str] - ) -> Optional[Directive]: + ) -> Optional[Type[Directive]]: if domain is not None: return self.directives.get(f"{domain}:{directive}", None) @@ -106,12 +107,12 @@ def get_implementation( # Try the std domain return self.directives.get(f"std:{directive}", None) - def index_directives(self) -> Dict[str, Directive]: + def index_directives(self) -> Dict[str, Type[Directive]]: return self.directives def suggest_directives( self, context: CompletionContext - ) -> Iterable[Tuple[str, Directive]]: + ) -> Iterable[Tuple[str, Type[Directive]]]: # In addition to providing each directive fully qualified, we should provide a # suggestion for directives in the std and primary domains without the prefix. @@ -137,7 +138,8 @@ def suggest_options( if impl is None: return [] - return impl.option_spec.keys() + option_spec = getattr(impl, "option_spec", {}) + return option_spec.keys() class DomainRoles(RoleLanguageFeature, DomainHelpers):