Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pylsp-rope for openSUSE:Factory checked in at 2021-10-12 21:51:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pylsp-rope (Old) and /work/SRC/openSUSE:Factory/.python-pylsp-rope.new.2443 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pylsp-rope" Tue Oct 12 21:51:34 2021 rev:3 rq:924932 version:0.1.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pylsp-rope/python-pylsp-rope.changes 2021-10-05 22:34:30.358937560 +0200 +++ /work/SRC/openSUSE:Factory/.python-pylsp-rope.new.2443/python-pylsp-rope.changes 2021-10-12 21:51:34.940063177 +0200 @@ -1,0 +2,10 @@ +Tue Oct 12 05:16:48 UTC 2021 - Matej Cepl <mc...@suse.com> + +- Update to 0.1.6: + - Fixed issue with missing typing-extensions dependency + - Add use functions refactoring + - Internal rewrites + - Add type annotation for many things + - Improve error handling during executeCommand() + +------------------------------------------------------------------- Old: ---- pylsp-rope-0.1.4.tar.gz New: ---- pylsp-rope-0.1.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pylsp-rope.spec ++++++ --- /var/tmp/diff_new_pack.wrUixP/_old 2021-10-12 21:51:35.424063870 +0200 +++ /var/tmp/diff_new_pack.wrUixP/_new 2021-10-12 21:51:35.432063882 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-pylsp-rope -Version: 0.1.4 +Version: 0.1.6 Release: 0 Summary: Extended refactoring capabilities for Python LSP Server using Rope License: MIT @@ -30,6 +30,7 @@ BuildRequires: %{python_module setuptools} BuildRequires: %{python_module wheel} BuildRequires: python-rpm-macros +BuildRequires: (python3-typing_extensions if python3-base <= 3.6) # SECTION test requirements BuildRequires: %{python_module python-lsp-server} BuildRequires: %{python_module pytest} ++++++ pylsp-rope-0.1.4.tar.gz -> pylsp-rope-0.1.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/PKG-INFO new/pylsp-rope-0.1.6/PKG-INFO --- old/pylsp-rope-0.1.4/PKG-INFO 2021-10-05 05:14:48.619781300 +0200 +++ new/pylsp-rope-0.1.6/PKG-INFO 2021-10-12 04:16:42.299155200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pylsp-rope -Version: 0.1.4 +Version: 0.1.6 Summary: Extended refactoring capabilities for Python LSP Server using Rope. Home-page: https://github.com/python-rope/pylsp-rope Author: Lie Ryan @@ -17,11 +17,14 @@ Requires-Python: >=3.6 Description-Content-Type: text/markdown Provides-Extra: dev +Provides-Extra: test License-File: LICENSE License-File: AUTHORS.txt # pylsp-rope +[](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml) + Extended refactoring capabilities for Python LSP Server using [Rope](https://github.com/python-rope/rope). @@ -58,6 +61,7 @@ - extract method (codeAction) - extract variable (codeAction) - inline method/variable/parameter (codeAction) +- use function (codeAction) - method to method object (codeAction) - more to come... @@ -68,19 +72,28 @@ ### Extract method -This refactoring works by triggering a CodeAction when selecting a block of code. +This refactoring works by triggering a CodeAction when selecting a block of +code. ### Extract variable -This refactoring works by triggering a CodeAction when selecting a Python expression. +This refactoring works by triggering a CodeAction when selecting a Python +expression. ### Inline -This refactoring works by triggering a CodeAction when the cursor is on a resolvable Python identifier. +This refactoring works by triggering a CodeAction when the cursor is on a +resolvable Python identifier. + +### Use function + +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ### Method to method object -This refactoring works when the cursor is on a function definition. +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/README.md new/pylsp-rope-0.1.6/README.md --- old/pylsp-rope-0.1.4/README.md 2021-10-05 03:45:00.000000000 +0200 +++ new/pylsp-rope-0.1.6/README.md 2021-10-12 04:04:46.000000000 +0200 @@ -1,5 +1,7 @@ # pylsp-rope +[](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml) + Extended refactoring capabilities for Python LSP Server using [Rope](https://github.com/python-rope/rope). @@ -36,6 +38,7 @@ - extract method (codeAction) - extract variable (codeAction) - inline method/variable/parameter (codeAction) +- use function (codeAction) - method to method object (codeAction) - more to come... @@ -46,19 +49,28 @@ ### Extract method -This refactoring works by triggering a CodeAction when selecting a block of code. +This refactoring works by triggering a CodeAction when selecting a block of +code. ### Extract variable -This refactoring works by triggering a CodeAction when selecting a Python expression. +This refactoring works by triggering a CodeAction when selecting a Python +expression. ### Inline -This refactoring works by triggering a CodeAction when the cursor is on a resolvable Python identifier. +This refactoring works by triggering a CodeAction when the cursor is on a +resolvable Python identifier. + +### Use function + +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ### Method to method object -This refactoring works when the cursor is on a function definition. +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/commands.py new/pylsp-rope-0.1.6/pylsp_rope/commands.py --- old/pylsp-rope-0.1.4/pylsp_rope/commands.py 2021-10-05 03:38:45.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope/commands.py 2021-10-07 19:25:19.000000000 +0200 @@ -1,4 +1,5 @@ COMMAND_REFACTOR_EXTRACT_METHOD = "pylsp_rope.refactor.extract.method" COMMAND_REFACTOR_EXTRACT_VARIABLE = "pylsp_rope.refactor.extract.variable" COMMAND_REFACTOR_INLINE = "pylsp_rope.refactor.inline" +COMMAND_REFACTOR_USE_FUNCTION = "pylsp_rope.refactor.use_function" COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT = "pylsp_rope.refactor.method_to_method_object" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/lsp_diff.py new/pylsp-rope-0.1.6/pylsp_rope/lsp_diff.py --- old/pylsp-rope-0.1.4/pylsp_rope/lsp_diff.py 2021-10-03 16:07:31.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope/lsp_diff.py 2021-10-10 12:33:41.000000000 +0200 @@ -1,16 +1,24 @@ import difflib +from typing import Iterator, List, Tuple, cast + from pylsp_rope.text import Position +from pylsp_rope.typing import TextEdit, Line, LineNumber + + +_DifflibOpcode = Tuple[str, LineNumber, LineNumber, LineNumber, LineNumber] -def _difflib_ops_to_text_edit_ops(ops, lines): - op, start_old, end_old, start_new, end_new = ops +def _difflib_ops_to_text_edit_ops( + opcode: _DifflibOpcode, lines: List[Line] +) -> TextEdit: + op, start_old, end_old, start_new, end_new = opcode if op == "replace" or op == "insert": new_text = "".join(lines[start_new:end_new]) elif op == "delete": new_text = "" else: - assert False, ops + assert False, opcode return { "range": {"start": Position(start_old), "end": Position(end_old)}, @@ -18,16 +26,18 @@ } -def lsp_diff(lines_old, lines_new): +def lsp_diff(lines_old: List[Line], lines_new: List[Line]) -> Iterator[TextEdit]: """ Given two sequences of lines, produce a [TextEdit][1] changeset. [1]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textEdit """ matcher = difflib.SequenceMatcher(a=lines_old, b=lines_new) - for ops in matcher.get_opcodes(): - if ops[0] == "equal": + for opcode in matcher.get_opcodes(): + if opcode[0] == "equal": continue - text_edit_ops = _difflib_ops_to_text_edit_ops(ops, lines_new) + text_edit_ops = _difflib_ops_to_text_edit_ops( + cast(_DifflibOpcode, opcode), lines_new + ) yield text_edit_ops diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/plugin.py new/pylsp-rope-0.1.6/pylsp_rope/plugin.py --- old/pylsp-rope-0.1.4/pylsp_rope/plugin.py 2021-10-05 05:02:12.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope/plugin.py 2021-10-12 02:06:26.000000000 +0200 @@ -1,16 +1,19 @@ import ast import logging +from typing import List -import rope.base.exceptions from pylsp import hookimpl -from rope.refactor import extract, inline, method_object +from pylsp.lsp import MessageType +from rope.refactor import extract, inline, method_object, usefunction -from pylsp_rope import commands +from pylsp_rope import typing, commands from pylsp_rope.project import ( get_project, get_resource, + get_resources, apply_rope_changeset, ) +from pylsp_rope.typing import DocumentUri, CodeActionKind logger = logging.getLogger(__name__) @@ -48,12 +51,14 @@ @hookimpl -def pylsp_commands(config, workspace): +def pylsp_commands(config, workspace) -> List[str]: return [getattr(commands, cmd) for cmd in dir(commands) if not cmd.startswith("_")] @hookimpl -def pylsp_code_actions(config, workspace, document, range, context): +def pylsp_code_actions( + config, workspace, document, range, context +) -> List[typing.CodeAction]: logger.info("textDocument/codeAction: %s %s %s", document, range, context) class info: @@ -74,11 +79,22 @@ document_uri=document.uri, range=range, ), - "Inline method/variable": CommandRefactorInline( + "Inline method/variable/parameter": CommandRefactorInline( workspace, document_uri=document.uri, position=info.position, ), + "Use function": CommandRefactorUseFunction( + workspace, + document_uri=document.uri, + position=info.position, + ), + "Use function for current file only": CommandRefactorUseFunction( + workspace, + document_uri=document.uri, + position=info.position, + documents=[document.uri], + ), "To method object": CommandRefactorMethodToMethodObject( workspace, document_uri=document.uri, @@ -97,50 +113,71 @@ def pylsp_execute_command(config, workspace, command, arguments): logger.info("workspace/executeCommand: %s %s", command, arguments) - commands = [ - CommandRefactorExtractMethod, - CommandRefactorExtractVariable, - CommandRefactorInline, - CommandRefactorMethodToMethodObject, - ] + commands = {cmd.name: cmd for cmd in Command.__subclasses__()} - for cmd in commands: - if command == cmd.name: - cmd(workspace, **arguments[0])() + try: + return commands[command](workspace, **arguments[0])() + except Exception as exc: + logger.exception( + "Exception when doing workspace/executeCommand: %s", + str(exc), + exc_info=exc, + ) + workspace.show_message( + f"pylsp-rope: {exc}", + msg_type=MessageType.Error, + ) class Command: + name: str + title: str + kind: CodeActionKind + def __init__(self, workspace, **arguments): self.workspace = workspace self.arguments = arguments self.__dict__.update(**arguments) + def __call__(self): + pass + + def validate(self, info): + pass + def is_valid(self, info): try: - is_valid = self._is_valid(info) + self.validate(info) except Exception: return False else: - return is_valid + return True return False - def _is_valid(self, info): - return True - - def get_code_action(self, title): + def get_code_action(self, title: str) -> typing.CodeAction: return { "title": title, "kind": self.kind, "command": { + "title": title, "command": self.name, "arguments": [self.arguments], }, } + @property # FIXME: backport cached_property + def project(self): + if not hasattr(self, "_project"): + self._project = get_project(self.workspace) + return self._project + class CommandRefactorExtractMethod(Command): name = commands.COMMAND_REFACTOR_EXTRACT_METHOD - kind = "refactor.extract" + kind: CodeActionKind = "refactor.extract" + + document_uri: DocumentUri + range: typing.Range # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking # def _is_valid(self, info): @@ -150,7 +187,7 @@ current_document, resource = get_resource(self.workspace, self.document_uri) refactoring = extract.ExtractMethod( - project=get_project(self.workspace), + project=self.project, resource=resource, start_offset=current_document.offset_at_position(self.range["start"]), end_offset=current_document.offset_at_position(self.range["end"]), @@ -163,18 +200,20 @@ class CommandRefactorExtractVariable(Command): name = commands.COMMAND_REFACTOR_EXTRACT_VARIABLE - kind = "refactor.extract" + kind: CodeActionKind = "refactor.extract" - def _is_valid(self, info): + document_uri: DocumentUri + range: typing.Range + + def validate(self, info): # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking ast.parse(info.selected_text, mode="eval") - return True def __call__(self): current_document, resource = get_resource(self.workspace, self.document_uri) refactoring = extract.ExtractVariable( - project=get_project(self.workspace), + project=self.project, resource=resource, start_offset=current_document.offset_at_position(self.range["start"]), end_offset=current_document.offset_at_position(self.range["end"]), @@ -187,21 +226,23 @@ class CommandRefactorInline(Command): name = commands.COMMAND_REFACTOR_INLINE - kind = "refactor.inline" + kind: CodeActionKind = "refactor.inline" + + document_uri: DocumentUri + position: typing.Range - def _is_valid(self, info): + def validate(self, info): inline.create_inline( - project=get_project(self.workspace), + project=self.project, resource=info.resource, offset=info.current_document.offset_at_position(info.position), ) - return True def __call__(self): current_document, resource = get_resource(self.workspace, self.document_uri) refactoring = inline.create_inline( - project=get_project(self.workspace), + project=self.project, resource=resource, offset=current_document.offset_at_position(self.position), ) @@ -209,15 +250,53 @@ apply_rope_changeset(self.workspace, rope_changeset) +class CommandRefactorUseFunction(Command): + name = commands.COMMAND_REFACTOR_USE_FUNCTION + kind: CodeActionKind = "refactor" + + document_uri: DocumentUri + position: typing.Range + + def validate(self, info): + usefunction.UseFunction( + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(info.position), + ) + + def __call__(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = usefunction.UseFunction( + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes( + resources=get_resources(self.workspace, getattr(self, "documents", None)), + ) + apply_rope_changeset(self.workspace, rope_changeset) + + class CommandRefactorMethodToMethodObject(Command): name = commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT - kind = "refactor" + kind: CodeActionKind = "refactor.rewrite" + + document_uri: DocumentUri + position: typing.Range + + def validate(self, info): + method_object.MethodObject( + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(self.position), + ) def __call__(self): current_document, resource = get_resource(self.workspace, self.document_uri) refactoring = method_object.MethodObject( - project=get_project(self.workspace), + project=self.project, resource=resource, offset=current_document.offset_at_position(self.position), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/project.py new/pylsp-rope-0.1.6/pylsp_rope/project.py --- old/pylsp-rope-0.1.4/pylsp_rope/project.py 2021-10-05 03:48:26.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope/project.py 2021-10-12 02:06:26.000000000 +0200 @@ -1,40 +1,54 @@ import logging from functools import lru_cache +from typing import List, Dict, Tuple -from pylsp import uris +from pylsp import uris, workspace from rope.base import libutils -from rope.base.project import Project +from pylsp_rope import rope from pylsp_rope.lsp_diff import lsp_diff +from pylsp_rope.typing import WorkspaceEdit, DocumentUri, TextEdit, Line logger = logging.getLogger(__name__) @lru_cache(maxsize=None) -def _get_project(workspace): - project = Project(workspace.root_path) +def _get_project(workspace) -> rope.Project: + project = rope.Project(workspace.root_path) return project -def get_project(workspace): +def get_project(workspace) -> rope.Project: project = _get_project(workspace) project.validate() return project -def get_resource(workspace, document_uri): +def get_resource( + workspace, document_uri: DocumentUri +) -> Tuple[workspace.Document, rope.Resource]: document = workspace.get_document(document_uri) - resource = libutils.path_to_resource(get_project(workspace), document.path) + resource = libutils.path_to_resource(_get_project(workspace), document.path) return document, resource -def get_document(workspace, resource): +def get_resources( + workspace, documents: List[workspace.Document] +) -> List[rope.Resource]: + if documents is None: + return None + return [get_resource(workspace, document_uri)[1] for document_uri in documents] + + +def get_document(workspace, resource: rope.Resource) -> workspace.Document: return workspace.get_document(uris.from_fs_path(resource.real_path)) -def rope_changeset_to_workspace_changeset(workspace, rope_changeset): - def _get_contents(change): +def rope_changeset_to_workspace_edit( + workspace, rope_changeset: rope.ChangeSet +) -> WorkspaceEdit: + def _get_contents(change: rope.Change) -> Tuple[List[Line], List[Line]]: old = change.old_contents new = change.new_contents if old is None: @@ -44,7 +58,7 @@ old = "" return old.splitlines(keepends=True), new.splitlines(keepends=True) - workspace_changeset = {} + workspace_changeset: Dict[DocumentUri, List[TextEdit]] = {} for change in rope_changeset.changes: lines_old, lines_new = _get_contents(change) @@ -52,18 +66,16 @@ document_changes = workspace_changeset.setdefault(document.uri, []) document_changes.extend(lsp_diff(lines_old, lines_new)) - return workspace_changeset + return { + "changes": workspace_changeset, + } -def apply_rope_changeset(workspace, rope_changeset): - workspace_changeset = rope_changeset_to_workspace_changeset( +def apply_rope_changeset(workspace, rope_changeset: rope.ChangeSet) -> None: + workspace_edit = rope_changeset_to_workspace_edit( workspace, rope_changeset, ) - workspace_edit = { - "changes": workspace_changeset, - } - logger.info("applying workspace edit: %s", workspace_edit) workspace.apply_edit(workspace_edit) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/rope.py new/pylsp-rope-0.1.6/pylsp_rope/rope.py --- old/pylsp-rope-0.1.4/pylsp_rope/rope.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.6/pylsp_rope/rope.py 2021-10-09 22:54:12.000000000 +0200 @@ -0,0 +1,11 @@ +from rope.base.change import ChangeSet, Change +from rope.base.project import Project +from rope.base.resources import Resource + + +__all__ = [ + "Change", + "ChangeSet", + "Project", + "Resource", +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/text.py new/pylsp-rope-0.1.6/pylsp_rope/text.py --- old/pylsp-rope-0.1.4/pylsp_rope/text.py 2021-10-02 12:38:30.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope/text.py 2021-10-12 04:04:46.000000000 +0200 @@ -1,11 +1,70 @@ -def Position(line, character=None, _default_character=0): +from typing import Tuple, Union, overload, Optional + +from pylsp_rope import typing +from pylsp_rope.typing import LineNumber, CharNumber, Literal + + +START_OF_LINE: Literal["^"] = "^" +END_OF_LINE: Literal["$"] = "$" + + +AutoLineNumber = Union[LineNumber, int] +AutoCharNumber = Union[CharNumber, int] + + +_CharNumberOrMarker = Union[AutoCharNumber, Literal["^", "$"]] +_PrimitiveLineCharNumber = Union[ + AutoLineNumber, Tuple[AutoLineNumber, Optional[_CharNumberOrMarker]] +] + + +@overload +def Position( + line: Tuple[AutoLineNumber, Optional[_CharNumberOrMarker]], + *, + _default_character: _CharNumberOrMarker = CharNumber(0), +) -> typing.Position: + ... + + +@overload +def Position( + line: AutoLineNumber, + *, + _default_character: _CharNumberOrMarker = CharNumber(0), +) -> typing.Position: + ... + + +@overload +def Position( + line: AutoLineNumber, + character: AutoCharNumber, +) -> typing.Position: + ... + + +@overload +def Position( + line: AutoLineNumber, + character: Literal["^", "$"], +) -> typing.Position: + ... + + +def Position( + line: _PrimitiveLineCharNumber, + character: Optional[_CharNumberOrMarker] = None, + *, + _default_character: _CharNumberOrMarker = CharNumber(0), +) -> typing.Position: """ Returns a [Position](https://microsoft.github.io/language-server-protocol/specification#position) object for a document. `pos` can be: - - Tuple[line, character] are passed directly to the object + - Tuple[LineNumber, CharNumber] are passed directly to the object - int selects the start of the line - "^" the first non-blank character of the line - "$" the end of the line, which is the start of the next line @@ -25,25 +84,31 @@ """ if isinstance(line, tuple): - assert ( - character is None - ), "If `line` is a tuple, then `character` must not be supplied" - line, character = line + # assert ( + # character is None + # ), "If `line` is a tuple, then `character` must not be supplied" + lineno, character = line + else: + lineno = line if character is None: character = _default_character if character == "$": - line += 1 - character = 0 + lineno = LineNumber(lineno + 1) + character = CharNumber(0) + assert character != "^", "not implemented yet" return { - "line": line, + "line": lineno, "character": character, } -def Range(start, end=None): +def Range( + start: _PrimitiveLineCharNumber, + end: Optional[_PrimitiveLineCharNumber] = None, +) -> typing.Range: """ Returns a [Range](https://microsoft.github.io/language-server-protocol/specification#range) object for a document. @@ -71,6 +136,6 @@ end = start return { - "start": Position(start, _default_character=0), - "end": Position(end, _default_character="$"), + "start": Position(start, _default_character=CharNumber(0)), + "end": Position(end, _default_character=END_OF_LINE), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope/typing.py new/pylsp-rope-0.1.6/pylsp_rope/typing.py --- old/pylsp-rope-0.1.4/pylsp_rope/typing.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.6/pylsp_rope/typing.py 2021-10-12 04:04:46.000000000 +0200 @@ -0,0 +1,87 @@ +import sys +from typing import List, Dict, Optional, NewType, Any + + +if sys.version_info >= (3, 8): + from typing import TypedDict, Literal +else: + from typing_extensions import TypedDict, Literal + + +########################## +### Standard LSP types ### +########################## + +DocumentUri = NewType("DocumentUri", str) + + +class Position(TypedDict): + line: int + character: int + + +class Range(TypedDict): + start: Position + end: Position + + +class TextEdit(TypedDict): + range: Range + newText: str + + +class WorkspaceEdit(TypedDict): + changes: Optional[Dict[DocumentUri, List[TextEdit]]] + # documentChanges: ... + # changeAnnotations: ... + + +class ApplyWorkspaceEditParams(TypedDict): + label: Optional[str] + edit: WorkspaceEdit + + +class Command(TypedDict): + title: str + command: str + arguments: Optional[List[Any]] + + +CodeActionKind = Literal[ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports", + "source.fixAll", +] + + +class CodeAction(TypedDict): + title: str + kind: Optional[CodeActionKind] + # diagnostics: Optional[List[Diagnostic]] + # isPreferred: Optional[bool] + # disabled: Optional[_CodeActionDisabledReason] + # edit: Optional[WorkspaceEdit] + command: Optional[Command] + # data: Optional[Any] + + +######################## +### pylsp-rope types ### +######################## + +DocumentContent = NewType("DocumentContent", str) +Line = NewType("Line", str) +LineNumber = NewType("LineNumber", int) +CharNumber = NewType("CharNumber", int) + + +class SimpleWorkspaceEdit(TypedDict): + """This is identical to WorkspaceEdit, but `changes` field is not optional.""" + + changes: Dict[DocumentUri, List[TextEdit]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/PKG-INFO new/pylsp-rope-0.1.6/pylsp_rope.egg-info/PKG-INFO --- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/PKG-INFO 2021-10-05 05:14:48.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/PKG-INFO 2021-10-12 04:16:42.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pylsp-rope -Version: 0.1.4 +Version: 0.1.6 Summary: Extended refactoring capabilities for Python LSP Server using Rope. Home-page: https://github.com/python-rope/pylsp-rope Author: Lie Ryan @@ -17,11 +17,14 @@ Requires-Python: >=3.6 Description-Content-Type: text/markdown Provides-Extra: dev +Provides-Extra: test License-File: LICENSE License-File: AUTHORS.txt # pylsp-rope +[](https://github.com/python-rope/pylsp-rope/actions/workflows/run-test.yml) + Extended refactoring capabilities for Python LSP Server using [Rope](https://github.com/python-rope/rope). @@ -58,6 +61,7 @@ - extract method (codeAction) - extract variable (codeAction) - inline method/variable/parameter (codeAction) +- use function (codeAction) - method to method object (codeAction) - more to come... @@ -68,19 +72,28 @@ ### Extract method -This refactoring works by triggering a CodeAction when selecting a block of code. +This refactoring works by triggering a CodeAction when selecting a block of +code. ### Extract variable -This refactoring works by triggering a CodeAction when selecting a Python expression. +This refactoring works by triggering a CodeAction when selecting a Python +expression. ### Inline -This refactoring works by triggering a CodeAction when the cursor is on a resolvable Python identifier. +This refactoring works by triggering a CodeAction when the cursor is on a +resolvable Python identifier. + +### Use function + +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ### Method to method object -This refactoring works when the cursor is on a function definition. +This works by triggering a CodeAction when the cursor is on the function name +of a `def` statement. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/SOURCES.txt new/pylsp-rope-0.1.6/pylsp_rope.egg-info/SOURCES.txt --- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/SOURCES.txt 2021-10-05 05:14:48.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/SOURCES.txt 2021-10-12 04:16:42.000000000 +0200 @@ -10,7 +10,9 @@ pylsp_rope/lsp_diff.py pylsp_rope/plugin.py pylsp_rope/project.py +pylsp_rope/rope.py pylsp_rope/text.py +pylsp_rope/typing.py pylsp_rope.egg-info/PKG-INFO pylsp_rope.egg-info/SOURCES.txt pylsp_rope.egg-info/dependency_links.txt @@ -26,10 +28,13 @@ test/test_lsp_diff.py test/test_method_to_method_object.py test/test_project.py +test/test_usefunction.py test/fixtures/function.py test/fixtures/many_changes.py test/fixtures/many_changes_inlined.py test/fixtures/method_object.py +test/fixtures/method_object_use_function.py test/fixtures/simple.py test/fixtures/simple_extract_method.py -test/fixtures/simple_extract_variable.py \ No newline at end of file +test/fixtures/simple_extract_variable.py +test/fixtures/use_function.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pylsp_rope.egg-info/requires.txt new/pylsp-rope-0.1.6/pylsp_rope.egg-info/requires.txt --- old/pylsp-rope-0.1.4/pylsp_rope.egg-info/requires.txt 2021-10-05 05:14:48.000000000 +0200 +++ new/pylsp-rope-0.1.6/pylsp_rope.egg-info/requires.txt 2021-10-12 04:16:42.000000000 +0200 @@ -1,6 +1,13 @@ python-lsp-server rope +[:python_version < "3.8"] +typing-extensions + [dev] +build pytest twine + +[test] +pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/pyproject.toml new/pylsp-rope-0.1.6/pyproject.toml --- old/pylsp-rope-0.1.4/pyproject.toml 2021-10-02 16:59:30.000000000 +0200 +++ new/pylsp-rope-0.1.6/pyproject.toml 2021-10-12 02:48:36.000000000 +0200 @@ -1,2 +1,10 @@ [tool.black] exclude = '.ropeproject|test/fixtures' + + +[tool.mypy] +python_version = "3.6" +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true +check_untyped_defs = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/setup.cfg new/pylsp-rope-0.1.6/setup.cfg --- old/pylsp-rope-0.1.4/setup.cfg 2021-10-05 05:14:48.619781300 +0200 +++ new/pylsp-rope-0.1.6/setup.cfg 2021-10-12 04:16:42.299155200 +0200 @@ -1,6 +1,6 @@ [metadata] name = pylsp-rope -version = 0.1.4 +version = 0.1.6 author = Lie Ryan author_email = lie.1...@gmail.com url = https://github.com/python-rope/pylsp-rope @@ -22,6 +22,7 @@ install_requires = python-lsp-server rope + typing-extensions; python_version < '3.8' python_requires = >= 3.6 [options.entry_points] @@ -29,8 +30,11 @@ [options.extras_require] dev = + build pytest twine +test = + pytest [egg_info] tag_build = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/function.py new/pylsp-rope-0.1.6/test/fixtures/function.py --- old/pylsp-rope-0.1.4/test/fixtures/function.py 2021-10-05 05:04:03.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/fixtures/function.py 2021-10-08 08:38:45.000000000 +0200 @@ -1,3 +1,7 @@ def add(a, b): - print(f"{a} + {b} = {a + b}") return a + b + + +def main(): + a, b = 10, 20 + print(f"{a} + {b} = {a + b}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/method_object.py new/pylsp-rope-0.1.6/test/fixtures/method_object.py --- old/pylsp-rope-0.1.4/test/fixtures/method_object.py 2021-10-05 05:04:03.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/fixtures/method_object.py 2021-10-08 08:49:51.000000000 +0200 @@ -9,5 +9,9 @@ self.b = b def __call__(self): - print(f"{self.a} + {self.b} = {self.a + self.b}") return self.a + self.b + + +def main(): + a, b = 10, 20 + print(f"{a} + {b} = {a + b}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/method_object_use_function.py new/pylsp-rope-0.1.6/test/fixtures/method_object_use_function.py --- old/pylsp-rope-0.1.4/test/fixtures/method_object_use_function.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.6/test/fixtures/method_object_use_function.py 2021-10-08 08:50:22.000000000 +0200 @@ -0,0 +1,19 @@ +import function + +def add(a, b): + return NewMethodObject(a, b)() + + +class NewMethodObject(object): + + def __init__(self, a, b): + self.a = a + self.b = b + + def __call__(self): + return function.add(self.a, self.b) + + +def main(): + a, b = 10, 20 + print(f"{a} + {b} = {function.add(a, b)}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/fixtures/use_function.py new/pylsp-rope-0.1.6/test/fixtures/use_function.py --- old/pylsp-rope-0.1.4/test/fixtures/use_function.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.6/test/fixtures/use_function.py 2021-10-08 08:39:07.000000000 +0200 @@ -0,0 +1,7 @@ +def add(a, b): + return a + b + + +def main(): + a, b = 10, 20 + print(f"{a} + {b} = {add(a, b)}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/helpers.py new/pylsp-rope-0.1.6/test/helpers.py --- old/pylsp-rope-0.1.4/test/helpers.py 2021-10-04 06:45:43.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/helpers.py 2021-10-08 13:30:16.000000000 +0200 @@ -1,48 +1,90 @@ +from typing import ( + Any, + Collection, + Dict, + List, +) from unittest.mock import ANY, call +from pylsp.workspace import Document + +from pylsp_rope.typing import ( + DocumentContent, + DocumentUri, + SimpleWorkspaceEdit, + TextEdit, +) from test.conftest import read_fixture_file -def assert_code_actions_do_not_offer(response, command): +def assert_code_actions_do_not_offer(response: Dict, command: str) -> None: for action in response: assert action["command"] != command, f"CodeAction should not offer {action}" -def assert_wholefile_changeset(document_changeset, target): - assert len(document_changeset) == 1 - (change,) = document_changeset +def assert_text_edits(document_edits: List[TextEdit], target: str) -> DocumentContent: new_text = read_fixture_file(target) - assert change["newText"].strip() == new_text.strip() - return new_text + for change in document_edits: + assert change["newText"] in new_text + return DocumentContent(new_text) -def assert_changeset(document_changeset, target): - new_text = read_fixture_file(target) - for change in document_changeset: - assert change["newText"] in new_text - return new_text +def assert_single_document_edit( + edit_request: Any, document: Document +) -> List[TextEdit]: + workspace_edit = assert_is_apply_edit_request(edit_request) + + document_uri: DocumentUri = document.uri + assert_modified_documents( + workspace_edit, + document_uris={document_uri}, + ) + + assert len(workspace_edit["changes"]) == 1 + document_edits = workspace_edit["changes"][document_uri] + assert isinstance(document_edits, list) + return document_edits -def assert_single_document_edit(edit_request, document): +def assert_is_apply_edit_request(edit_request: Any) -> SimpleWorkspaceEdit: assert edit_request == call( "workspace/applyEdit", { "edit": { - "changes": { - document.uri: ANY, - }, + "changes": ANY, }, }, ) - (document_changeset,) = edit_request[0][1]["edit"]["changes"].values() - for change in document_changeset: - assert change == { - "range": { - "start": {"line": ANY, "character": ANY}, - "end": {"line": ANY, "character": ANY}, - }, - "newText": ANY, - } + workspace_edit: SimpleWorkspaceEdit = edit_request[0][1]["edit"] + for document_uri, document_edits in workspace_edit["changes"].items(): + assert is_document_uri(document_uri) + for change in document_edits: + assert change == { + "range": { + "start": {"line": ANY, "character": ANY}, + "end": {"line": ANY, "character": ANY}, + }, + "newText": ANY, + } + + return workspace_edit + + +def is_document_uri(uri: DocumentUri) -> bool: + return isinstance(uri, str) and uri.startswith("file://") + + +def assert_modified_documents( + workspace_edit: SimpleWorkspaceEdit, + document_uris: Collection[DocumentUri], +) -> None: + assert workspace_edit["changes"].keys() == set(document_uris) + - return document_changeset +def assert_unmodified_document( + workspace_edit: SimpleWorkspaceEdit, + document_uri: DocumentUri, +) -> None: + assert is_document_uri(document_uri) + assert document_uri not in workspace_edit["changes"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_commands.py new/pylsp-rope-0.1.6/test/test_commands.py --- old/pylsp-rope-0.1.4/test/test_commands.py 2021-10-03 07:13:06.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/test_commands.py 2021-10-07 19:25:17.000000000 +0200 @@ -1,4 +1,10 @@ -from pylsp_rope.plugin import pylsp_commands +from unittest.mock import patch + +from pylsp.lsp import MessageType + +from pylsp_rope import commands +from pylsp_rope.plugin import pylsp_commands, pylsp_execute_command +from pylsp_rope.text import Position def test_command_registration(config, workspace): @@ -7,3 +13,38 @@ assert isinstance(commands, list) assert all(isinstance(cmd, str) for cmd in commands) assert all(cmd.startswith("pylsp_rope.") for cmd in commands) + + +def test_command_error_handling(caplog, config, workspace, document): + """ + pylsp_execute_command should never raise an error when executeCommand(). + + Instead, we'll show an error message to the user. + """ + + arguments = [ + { + "document_uri": document.uri, + "position": Position(1), + }, + ] + + with patch( + "pylsp_rope.plugin.CommandRefactorInline.__call__", + side_effect=Exception("some unexpected exception"), + ): + pylsp_execute_command( + config, + workspace, + command=commands.COMMAND_REFACTOR_INLINE, + arguments=arguments, + ) + + workspace._endpoint.notify.assert_called_once_with( + "window/showMessage", + params={ + "type": MessageType.Error, + "message": f"pylsp-rope: some unexpected exception", + }, + ) + assert "Traceback (most recent call last):" in caplog.text diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_extract.py new/pylsp-rope-0.1.6/test/test_extract.py --- old/pylsp-rope-0.1.4/test/test_extract.py 2021-10-05 04:03:13.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/test_extract.py 2021-10-08 15:20:25.000000000 +0200 @@ -1,7 +1,7 @@ -from pylsp_rope import commands, plugin +from pylsp_rope import commands, plugin, typing from pylsp_rope.text import Range from test.helpers import ( - assert_changeset, + assert_text_edits, assert_code_actions_do_not_offer, assert_single_document_edit, ) @@ -21,10 +21,11 @@ context=code_action_context, ) - expected = { + expected: typing.CodeAction = { "title": "Extract variable", "kind": "refactor.extract", "command": { + "title": "Extract variable", "command": commands.COMMAND_REFACTOR_EXTRACT_VARIABLE, "arguments": [ { @@ -37,6 +38,7 @@ assert expected in response + assert expected["command"] is not None command = expected["command"]["command"] arguments = expected["command"]["arguments"] @@ -49,8 +51,8 @@ edit_request = workspace._endpoint.request.call_args - document_changeset = assert_single_document_edit(edit_request, document) - new_text = assert_changeset(document_changeset, target="simple_extract_variable.py") + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="simple_extract_variable.py") assert "extracted_variable = " in new_text @@ -87,10 +89,11 @@ context=code_action_context, ) - expected = { + expected: typing.CodeAction = { "title": "Extract method", "kind": "refactor.extract", "command": { + "title": "Extract method", "command": commands.COMMAND_REFACTOR_EXTRACT_METHOD, "arguments": [ { @@ -103,6 +106,7 @@ assert expected in response + assert expected["command"] is not None command = expected["command"]["command"] arguments = expected["command"]["arguments"] @@ -115,6 +119,6 @@ edit_request = workspace._endpoint.request.call_args - document_changeset = assert_single_document_edit(edit_request, document) - new_text = assert_changeset(document_changeset, target="simple_extract_method.py") + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="simple_extract_method.py") assert "def extracted_method(" in new_text diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_inline.py new/pylsp-rope-0.1.6/test/test_inline.py --- old/pylsp-rope-0.1.4/test/test_inline.py 2021-10-04 08:39:46.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/test_inline.py 2021-10-08 18:42:20.000000000 +0200 @@ -1,8 +1,8 @@ -from pylsp_rope import plugin, commands +from pylsp_rope import commands, plugin, typing from pylsp_rope.text import Range from test.conftest import create_document from test.helpers import ( - assert_changeset, + assert_text_edits, assert_code_actions_do_not_offer, assert_single_document_edit, ) @@ -22,10 +22,11 @@ context=code_action_context, ) - expected = { - "title": "Inline method/variable", + expected: typing.CodeAction = { + "title": "Inline method/variable/parameter", "kind": "refactor.inline", "command": { + "title": "Inline method/variable/parameter", "command": commands.COMMAND_REFACTOR_INLINE, "arguments": [ { @@ -38,6 +39,7 @@ assert expected in response + assert expected["command"] is not None command = expected["command"]["command"] arguments = expected["command"]["arguments"] @@ -50,8 +52,8 @@ edit_request = workspace._endpoint.request.call_args - document_changeset = assert_single_document_edit(edit_request, document) - new_text = assert_changeset(document_changeset, target="simple.py") + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="simple.py") assert "extracted_method" not in new_text diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_method_to_method_object.py new/pylsp-rope-0.1.6/test/test_method_to_method_object.py --- old/pylsp-rope-0.1.4/test/test_method_to_method_object.py 2021-10-05 03:38:45.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/test_method_to_method_object.py 2021-10-09 13:28:00.000000000 +0200 @@ -1,8 +1,8 @@ -from pylsp_rope import commands, plugin +from pylsp_rope import commands, plugin, typing from pylsp_rope.text import Range from test.conftest import create_document from test.helpers import ( - assert_changeset, + assert_text_edits, assert_code_actions_do_not_offer, assert_single_document_edit, ) @@ -22,10 +22,11 @@ context=code_action_context, ) - expected = { + expected: typing.CodeAction = { "title": "To method object", - "kind": "refactor", + "kind": "refactor.rewrite", "command": { + "title": "To method object", "command": commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT, "arguments": [ { @@ -38,6 +39,7 @@ assert expected in response + assert expected["command"] is not None command = expected["command"]["command"] arguments = expected["command"]["arguments"] @@ -50,7 +52,29 @@ edit_request = workspace._endpoint.request.call_args - document_changeset = assert_single_document_edit(edit_request, document) - new_text = assert_changeset(document_changeset, target="method_object.py") + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="method_object.py") assert "class NewMethodObject(object)" in new_text assert "NewMethodObject(a, b)()" in new_text + + +def test_method_to_method_object_not_offered_when_selecting_unsuitable_range( + config, workspace, code_action_context +): + document = create_document(workspace, "function.py") + line = 1 + pos = document.lines[line].index("return") + selection = Range((line, pos), (line, pos)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + assert_code_actions_do_not_offer( + response, + command=commands.COMMAND_REFACTOR_INLINE, + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_project.py new/pylsp-rope-0.1.6/test/test_project.py --- old/pylsp-rope-0.1.4/test/test_project.py 2021-10-05 03:48:26.000000000 +0200 +++ new/pylsp-rope-0.1.6/test/test_project.py 2021-10-08 15:36:52.000000000 +0200 @@ -3,7 +3,7 @@ from pylsp_rope.project import ( get_project, get_resource, - rope_changeset_to_workspace_changeset, + rope_changeset_to_workspace_edit, ) from test.conftest import create_document @@ -11,12 +11,12 @@ def test_rope_changeset_to_workspace_changeset(workspace): document = create_document(workspace, "many_changes.py") rope_changeset = get_rope_changeset(workspace, document) - workspace_changeset = rope_changeset_to_workspace_changeset( + workspace_edit = rope_changeset_to_workspace_edit( workspace, rope_changeset, ) - assert workspace_changeset == { + assert workspace_edit["changes"] == { document.uri: [ { "range": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.4/test/test_usefunction.py new/pylsp-rope-0.1.6/test/test_usefunction.py --- old/pylsp-rope-0.1.4/test/test_usefunction.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.6/test/test_usefunction.py 2021-10-09 21:23:49.000000000 +0200 @@ -0,0 +1,127 @@ +from pylsp_rope import plugin, commands, typing +from pylsp_rope.text import Range +from test.conftest import create_document +from test.helpers import ( + assert_text_edits, + assert_is_apply_edit_request, + assert_modified_documents, + assert_unmodified_document, +) + + +def test_use_function_globally(config, workspace, code_action_context): + document = create_document(workspace, "function.py") + document2 = create_document(workspace, "method_object.py") + line = 0 + pos = document.lines[line].index("def add") + 4 + selection = Range((line, pos), (line, pos)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + expected: typing.CodeAction = { + "title": "Use function", + "kind": "refactor", + "command": { + "title": "Use function", + "command": commands.COMMAND_REFACTOR_USE_FUNCTION, + "arguments": [ + { + "document_uri": document.uri, + "position": selection["start"], + }, + ], + }, + } + + assert expected in response + + assert expected["command"] is not None + command = expected["command"]["command"] + arguments = expected["command"]["arguments"] + + response = plugin.pylsp_execute_command( + config=config, + workspace=workspace, + command=command, + arguments=arguments, + ) + + edit_request = workspace._endpoint.request.call_args + + workspace_edit = assert_is_apply_edit_request(edit_request) + assert_modified_documents(workspace_edit, {document.uri, document2.uri}) + + new_text = assert_text_edits( + workspace_edit["changes"][document.uri], target="use_function.py" + ) + assert "{add(a, b)}" in new_text + + new_text = assert_text_edits( + workspace_edit["changes"][document2.uri], target="method_object_use_function.py" + ) + assert "import function" in new_text + assert "{function.add(a, b)}" in new_text + + +def test_use_function_in_current_file(config, workspace, code_action_context): + document = create_document(workspace, "function.py") + document2 = create_document(workspace, "method_object.py") + + line = 0 + pos = document.lines[line].index("def add") + 4 + selection = Range((line, pos), (line, pos)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + expected: typing.CodeAction = { + "title": "Use function for current file only", + "kind": "refactor", + "command": { + "title": "Use function for current file only", + "command": commands.COMMAND_REFACTOR_USE_FUNCTION, + "arguments": [ + { + "document_uri": document.uri, + "position": selection["start"], + "documents": [document.uri], + }, + ], + }, + } + + assert expected in response + + assert expected["command"] is not None + command = expected["command"]["command"] + arguments = expected["command"]["arguments"] + + response = plugin.pylsp_execute_command( + config=config, + workspace=workspace, + command=command, + arguments=arguments, + ) + + edit_request = workspace._endpoint.request.call_args + + workspace_edit = assert_is_apply_edit_request(edit_request) + assert_modified_documents(workspace_edit, {document.uri}) + + new_text = assert_text_edits( + workspace_edit["changes"][document.uri], target="use_function.py" + ) + assert "{add(a, b)}" in new_text + + assert_unmodified_document(workspace_edit, document2.uri)