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 2023-01-17 17:35:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pylsp-rope (Old) and /work/SRC/openSUSE:Factory/.python-pylsp-rope.new.32243 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pylsp-rope" Tue Jan 17 17:35:24 2023 rev:8 rq:1058819 version:0.1.11 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pylsp-rope/python-pylsp-rope.changes 2022-10-06 07:41:54.148647015 +0200 +++ /work/SRC/openSUSE:Factory/.python-pylsp-rope.new.32243/python-pylsp-rope.changes 2023-01-17 17:35:35.901280129 +0100 @@ -1,0 +2,7 @@ +Tue Jan 17 08:12:33 UTC 2023 - Matej Cepl <[email protected]> + +- Update to 0.1.11: + - Move codeAction Command* to pylsp_rope.refactoring + - Implement generate.{GenerateVariable(),GenerateClass(),get_code_actions(),GenerateFunction()}. + +------------------------------------------------------------------- Old: ---- pylsp-rope-0.1.10.tar.gz New: ---- pylsp-rope-0.1.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pylsp-rope.spec ++++++ --- /var/tmp/diff_new_pack.Yb13TV/_old 2023-01-17 17:35:36.497283586 +0100 +++ /var/tmp/diff_new_pack.Yb13TV/_new 2023-01-17 17:35:36.505283632 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pylsp-rope # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,10 +16,9 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-pylsp-rope -Version: 0.1.10 +Version: 0.1.11 Release: 0 Summary: Extended refactoring capabilities for Python LSP Server using Rope License: MIT @@ -66,6 +65,7 @@ %files %{python_files} %doc AUTHORS.txt README.md %license LICENSE -%{python_sitelib}/pylsp_rope* +%{python_sitelib}/pylsp_rope +%{python_sitelib}/pylsp_rope-%{version}*-info %changelog ++++++ pylsp-rope-0.1.10.tar.gz -> pylsp-rope-0.1.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/PKG-INFO new/pylsp-rope-0.1.11/PKG-INFO --- old/pylsp-rope-0.1.10/PKG-INFO 2022-08-25 05:29:01.430132900 +0200 +++ new/pylsp-rope-0.1.11/PKG-INFO 2023-01-17 08:06:05.202077000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pylsp-rope -Version: 0.1.10 +Version: 0.1.11 Summary: Extended refactoring capabilities for Python LSP Server using Rope. Home-page: https://github.com/python-rope/pylsp-rope Author: Lie Ryan @@ -67,6 +67,7 @@ - convert local variable to field (codeAction) - organize imports (codeAction) - introduce parameter (codeAction) +- generate variable/function/class from undefined variable (codeAction) - more to come... Refer to [Rope documentation](https://github.com/python-rope/rope/blob/master/docs/overview.rst) @@ -83,47 +84,67 @@ - Extract method including similar statements - Extract global method including similar statements -This refactoring works by triggering CodeAction when selecting a block of code. -Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on any block of code, extract +that expression into a method. Optionally, similar statements can also be +extracted. ### Extract variable +Variants: + - Extract variable - Extract global variable - Extract variable including similar statements - Extract global variable including similar statements -This refactoring works by triggering CodeAction when selecting a Python -expression. Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on a expression, extract that +expression into a variable. Optionally, similar statements can also be +extracted. ### Inline -This refactoring works by triggering CodeAction when the cursor is on a -resolvable Python identifier. +When CodeAction is triggered and the cursor is on a resolvable Python variable, +replace all calls to that method with the method body. ### Use function -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, try to replace code whose AST matches the selected function with a +call to the function. ### Method to method object -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, create a callable class to replace that method. You may want to +inline the method afterwards to remove the indirection. ### Convert local variable to field -This refactoring works by triggering CodeAction when the cursor is on a local -variable. +When CodeAction is triggered wand the cursor is on a local variable inside a +method, convert that local variable to an attribute. ### Organize import -This refactoring works by triggering CodeAction anywhere. +Trigger CodeAction anywhere in a Python file to organize imports. ### Introduce parameter -This refactoring works by triggering CodeAction when the cursor is selecting -a Python identifier, including attribute access. +When CodeAction is triggered and the cursor is selecting a Python variable or +attribute, make that variable/attribute a parameter. + +### Generate code + +Variants: + +- [x] Generate variable +- [x] Generate function +- [x] Generate class +- [ ] Generate module +- [ ] Generate package + +When CodeAction is triggered and the cursor is on an undefined Python +variable, generate an empty variable/function/class/module/package for that +name. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/README.md new/pylsp-rope-0.1.11/README.md --- old/pylsp-rope-0.1.10/README.md 2022-03-31 17:15:48.000000000 +0200 +++ new/pylsp-rope-0.1.11/README.md 2023-01-17 08:01:07.000000000 +0100 @@ -45,6 +45,7 @@ - convert local variable to field (codeAction) - organize imports (codeAction) - introduce parameter (codeAction) +- generate variable/function/class from undefined variable (codeAction) - more to come... Refer to [Rope documentation](https://github.com/python-rope/rope/blob/master/docs/overview.rst) @@ -61,47 +62,67 @@ - Extract method including similar statements - Extract global method including similar statements -This refactoring works by triggering CodeAction when selecting a block of code. -Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on any block of code, extract +that expression into a method. Optionally, similar statements can also be +extracted. ### Extract variable +Variants: + - Extract variable - Extract global variable - Extract variable including similar statements - Extract global variable including similar statements -This refactoring works by triggering CodeAction when selecting a Python -expression. Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on a expression, extract that +expression into a variable. Optionally, similar statements can also be +extracted. ### Inline -This refactoring works by triggering CodeAction when the cursor is on a -resolvable Python identifier. +When CodeAction is triggered and the cursor is on a resolvable Python variable, +replace all calls to that method with the method body. ### Use function -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, try to replace code whose AST matches the selected function with a +call to the function. ### Method to method object -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, create a callable class to replace that method. You may want to +inline the method afterwards to remove the indirection. ### Convert local variable to field -This refactoring works by triggering CodeAction when the cursor is on a local -variable. +When CodeAction is triggered wand the cursor is on a local variable inside a +method, convert that local variable to an attribute. ### Organize import -This refactoring works by triggering CodeAction anywhere. +Trigger CodeAction anywhere in a Python file to organize imports. ### Introduce parameter -This refactoring works by triggering CodeAction when the cursor is selecting -a Python identifier, including attribute access. +When CodeAction is triggered and the cursor is selecting a Python variable or +attribute, make that variable/attribute a parameter. + +### Generate code + +Variants: + +- [x] Generate variable +- [x] Generate function +- [x] Generate class +- [ ] Generate module +- [ ] Generate package + +When CodeAction is triggered and the cursor is on an undefined Python +variable, generate an empty variable/function/class/module/package for that +name. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/pylsp_rope/commands.py new/pylsp-rope-0.1.11/pylsp_rope/commands.py --- old/pylsp-rope-0.1.10/pylsp_rope/commands.py 2022-03-31 16:04:51.000000000 +0200 +++ new/pylsp-rope-0.1.11/pylsp_rope/commands.py 2023-01-17 01:30:00.000000000 +0100 @@ -6,3 +6,4 @@ COMMAND_REFACTOR_LOCAL_TO_FIELD = "pylsp_rope.refactor.local_to_field" COMMAND_SOURCE_ORGANIZE_IMPORT = "pylsp_rope.source.organize_import" COMMAND_INTRODUCE_PARAMETER = "pylsp_rope.refactor.introduce_parameter" +COMMAND_GENERATE_CODE = "pylsp_rope.quickfix.generate" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/pylsp_rope/plugin.py new/pylsp-rope-0.1.11/pylsp_rope/plugin.py --- old/pylsp-rope-0.1.10/pylsp_rope/plugin.py 2022-03-31 16:40:32.000000000 +0200 +++ new/pylsp-rope-0.1.11/pylsp_rope/plugin.py 2023-01-17 08:04:28.000000000 +0100 @@ -1,27 +1,11 @@ -import ast import logging from typing import List from pylsp import hookimpl from pylsp.lsp import MessageType -from rope.refactor import ( - extract, - inline, - method_object, - usefunction, - localtofield, - importutils, - introduce_parameter, -) - -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 + +from pylsp_rope import refactoring, typing, commands +from pylsp_rope.project import get_project, get_resource, get_resources logger = logging.getLogger(__name__) @@ -82,14 +66,14 @@ commands = {} commands.update( - CommandRefactorExtractMethod.get_code_actions( + refactoring.CommandRefactorExtractMethod.get_code_actions( workspace, document=document, range=range, ), ) commands.update( - CommandRefactorExtractVariable.get_code_actions( + refactoring.CommandRefactorExtractVariable.get_code_actions( workspace, document=document, range=range, @@ -97,43 +81,50 @@ ) commands.update( { - "Inline method/variable/parameter": CommandRefactorInline( + "Inline method/variable/parameter": refactoring.CommandRefactorInline( workspace, document_uri=document.uri, position=info.position, ), - "Use function": CommandRefactorUseFunction( + "Use function": refactoring.CommandRefactorUseFunction( workspace, document_uri=document.uri, position=info.position, ), - "Use function for current file only": CommandRefactorUseFunction( + "Use function for current file only": refactoring.CommandRefactorUseFunction( workspace, document_uri=document.uri, position=info.position, documents=[document.uri], ), - "To method object": CommandRefactorMethodToMethodObject( + "To method object": refactoring.CommandRefactorMethodToMethodObject( workspace, document_uri=document.uri, position=info.position, ), - "Convert local variable to field": CommandRefactorLocalToField( + "Convert local variable to field": refactoring.CommandRefactorLocalToField( workspace, document_uri=document.uri, position=info.position, ), - "Organize import": CommandSourceOrganizeImport( + "Organize import": refactoring.CommandSourceOrganizeImport( workspace, document_uri=document.uri, ), - "Introduce parameter": CommandIntroduceParameter( + "Introduce parameter": refactoring.CommandIntroduceParameter( workspace, document_uri=document.uri, position=info.position, ), } ) + commands.update( + refactoring.GenerateCode.get_code_actions( + workspace, + document=document, + position=info.position, + ), + ) return [ cmd.get_code_action(title=title) @@ -146,7 +137,7 @@ def pylsp_execute_command(config, workspace, command, arguments): logger.info("workspace/executeCommand: %s %s", command, arguments) - commands = {cmd.name: cmd for cmd in Command.__subclasses__()} + commands = {cmd.name: cmd for cmd in refactoring.Command.__subclasses__()} try: return commands[command](workspace, **arguments[0])() @@ -160,333 +151,3 @@ 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): - rope_changeset = self.get_changes() - if rope_changeset is not None: - apply_rope_changeset(self.workspace, rope_changeset) - - def get_changes(self): - """ - Calculate the rope changeset to perform this refactoring. - """ - - def validate(self, info) -> None: - """ - Override this method to raise an exception if this refactoring command - cannot be performed - """ - - def is_valid(self, info): - try: - self.validate(info) - except Exception: - return False - else: - return True - return False - - 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: CodeActionKind = "refactor.extract" - - document_uri: DocumentUri - range: typing.Range - similar: bool - global_: bool - - # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking - # def _is_valid(self, info): - # ... - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = extract.ExtractMethod( - 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"]), - ) - rope_changeset = refactoring.get_changes( - extracted_name="extracted_method", - similar=self.similar, - global_=self.global_, - ) - return rope_changeset - - @classmethod - def get_code_actions(cls, workspace, document, range): - return { - "Extract method including similar statements": cls( - workspace, - document_uri=document.uri, - range=range, - global_=False, - similar=True, - ), - "Extract method": cls( - workspace, - document_uri=document.uri, - range=range, - global_=False, - similar=False, - ), - "Extract global method including similar statements": cls( - workspace, - document_uri=document.uri, - range=range, - global_=True, - similar=True, - ), - "Extract global method": cls( - workspace, - document_uri=document.uri, - range=range, - global_=True, - similar=False, - ), - } - - -class CommandRefactorExtractVariable(Command): - name = commands.COMMAND_REFACTOR_EXTRACT_VARIABLE - kind: CodeActionKind = "refactor.extract" - - document_uri: DocumentUri - range: typing.Range - similar: bool - global_: bool - - def validate(self, info): - # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking - ast.parse(info.selected_text, mode="eval") - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = extract.ExtractVariable( - 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"]), - ) - rope_changeset = refactoring.get_changes( - extracted_name="extracted_variable", - similar=self.similar, - global_=self.global_, - ) - return rope_changeset - - @classmethod - def get_code_actions(cls, workspace, document, range): - return { - "Extract variable including similar statements": cls( - workspace, - document_uri=document.uri, - range=range, - global_=False, - similar=True, - ), - "Extract variable": cls( - workspace, - document_uri=document.uri, - range=range, - global_=False, - similar=False, - ), - "Extract global variable including similar statements": cls( - workspace, - document_uri=document.uri, - range=range, - global_=True, - similar=True, - ), - "Extract global variable": cls( - workspace, - document_uri=document.uri, - range=range, - global_=True, - similar=False, - ), - } - -class CommandRefactorInline(Command): - name = commands.COMMAND_REFACTOR_INLINE - kind: CodeActionKind = "refactor.inline" - - document_uri: DocumentUri - position: typing.Range - - def validate(self, info): - inline.create_inline( - project=self.project, - resource=info.resource, - offset=info.current_document.offset_at_position(info.position), - ) - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = inline.create_inline( - project=self.project, - resource=resource, - offset=current_document.offset_at_position(self.position), - ) - rope_changeset = refactoring.get_changes() - return 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 get_changes(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)), - ) - return rope_changeset - - -class CommandRefactorMethodToMethodObject(Command): - name = commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT - 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 get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = method_object.MethodObject( - project=self.project, - resource=resource, - offset=current_document.offset_at_position(self.position), - ) - rope_changeset = refactoring.get_changes(classname="NewMethodObject") - return rope_changeset - - -class CommandRefactorLocalToField(Command): - name = commands.COMMAND_REFACTOR_LOCAL_TO_FIELD - kind: CodeActionKind = "refactor.rewrite" - - document_uri: DocumentUri - position: typing.Range - - def validate(self, info): - localtofield.LocalToField( - project=self.project, - resource=info.resource, - offset=info.current_document.offset_at_position(self.position), - ) - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = localtofield.LocalToField( - project=self.project, - resource=resource, - offset=current_document.offset_at_position(self.position), - ) - rope_changeset = refactoring.get_changes() - return rope_changeset - - -class CommandSourceOrganizeImport(Command): - name = commands.COMMAND_SOURCE_ORGANIZE_IMPORT - kind: CodeActionKind = "source.organizeImports" - - document_uri: DocumentUri - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - organizer = importutils.ImportOrganizer( - project=self.project, - ) - rope_changeset = organizer.organize_imports( - resource=resource, - ) - return rope_changeset - - -class CommandIntroduceParameter(Command): - name = commands.COMMAND_INTRODUCE_PARAMETER - kind: CodeActionKind = "refactor" - - document_uri: DocumentUri - position: typing.Range - - def validate(self, info): - introduce_parameter.IntroduceParameter( - project=self.project, - resource=info.resource, - offset=info.current_document.offset_at_position(self.position), - ) - - def get_changes(self): - current_document, resource = get_resource(self.workspace, self.document_uri) - - refactoring = introduce_parameter.IntroduceParameter( - project=self.project, - resource=resource, - offset=current_document.offset_at_position(self.position), - ) - rope_changeset = refactoring.get_changes( - new_parameter="new_parameter", - ) - return rope_changeset diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/pylsp_rope/refactoring.py new/pylsp-rope-0.1.11/pylsp_rope/refactoring.py --- old/pylsp-rope-0.1.10/pylsp_rope/refactoring.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/pylsp_rope/refactoring.py 2023-01-17 07:48:42.000000000 +0100 @@ -0,0 +1,397 @@ +import ast + +from rope.base import evaluate +from rope.contrib import generate +from rope.refactor import ( + extract, + inline, + method_object, + usefunction, + localtofield, + importutils, + introduce_parameter, +) + +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 + + +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): + rope_changeset = self.get_changes() + if rope_changeset is not None: + apply_rope_changeset(self.workspace, rope_changeset) + + def get_changes(self): + """ + Calculate the rope changeset to perform this refactoring. + """ + + def validate(self, info) -> None: + """ + Override this method to raise an exception if this refactoring command + cannot be performed + """ + + def is_valid(self, info): + try: + self.validate(info) + except Exception: + return False + else: + return True + return False + + 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: CodeActionKind = "refactor.extract" + + document_uri: DocumentUri + range: typing.Range + similar: bool + global_: bool + + # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking + # def _is_valid(self, info): + # ... + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = extract.ExtractMethod( + 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"]), + ) + rope_changeset = refactoring.get_changes( + extracted_name="extracted_method", + similar=self.similar, + global_=self.global_, + ) + return rope_changeset + + @classmethod + def get_code_actions(cls, workspace, document, range): + return { + "Extract method including similar statements": cls( + workspace, + document_uri=document.uri, + range=range, + global_=False, + similar=True, + ), + "Extract method": cls( + workspace, + document_uri=document.uri, + range=range, + global_=False, + similar=False, + ), + "Extract global method including similar statements": cls( + workspace, + document_uri=document.uri, + range=range, + global_=True, + similar=True, + ), + "Extract global method": cls( + workspace, + document_uri=document.uri, + range=range, + global_=True, + similar=False, + ), + } + + +class CommandRefactorExtractVariable(Command): + name = commands.COMMAND_REFACTOR_EXTRACT_VARIABLE + kind: CodeActionKind = "refactor.extract" + + document_uri: DocumentUri + range: typing.Range + similar: bool + global_: bool + + def validate(self, info): + # FIXME: requires rope.refactor.extract._ExceptionalConditionChecker for proper checking + ast.parse(info.selected_text, mode="eval") + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = extract.ExtractVariable( + 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"]), + ) + rope_changeset = refactoring.get_changes( + extracted_name="extracted_variable", + similar=self.similar, + global_=self.global_, + ) + return rope_changeset + + @classmethod + def get_code_actions(cls, workspace, document, range): + return { + "Extract variable including similar statements": cls( + workspace, + document_uri=document.uri, + range=range, + global_=False, + similar=True, + ), + "Extract variable": cls( + workspace, + document_uri=document.uri, + range=range, + global_=False, + similar=False, + ), + "Extract global variable including similar statements": cls( + workspace, + document_uri=document.uri, + range=range, + global_=True, + similar=True, + ), + "Extract global variable": cls( + workspace, + document_uri=document.uri, + range=range, + global_=True, + similar=False, + ), + } + + +class CommandRefactorInline(Command): + name = commands.COMMAND_REFACTOR_INLINE + kind: CodeActionKind = "refactor.inline" + + document_uri: DocumentUri + position: typing.Range + + def validate(self, info): + inline.create_inline( + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(info.position), + ) + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = inline.create_inline( + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes() + return 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 get_changes(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)), + ) + return rope_changeset + + +class CommandRefactorMethodToMethodObject(Command): + name = commands.COMMAND_REFACTOR_METHOD_TO_METHOD_OBJECT + 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 get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = method_object.MethodObject( + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes(classname="NewMethodObject") + return rope_changeset + + +class CommandRefactorLocalToField(Command): + name = commands.COMMAND_REFACTOR_LOCAL_TO_FIELD + kind: CodeActionKind = "refactor.rewrite" + + document_uri: DocumentUri + position: typing.Range + + def validate(self, info): + localtofield.LocalToField( + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(self.position), + ) + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = localtofield.LocalToField( + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes() + return rope_changeset + + +class CommandSourceOrganizeImport(Command): + name = commands.COMMAND_SOURCE_ORGANIZE_IMPORT + kind: CodeActionKind = "source.organizeImports" + + document_uri: DocumentUri + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + organizer = importutils.ImportOrganizer( + project=self.project, + ) + rope_changeset = organizer.organize_imports( + resource=resource, + ) + return rope_changeset + + +class CommandIntroduceParameter(Command): + name = commands.COMMAND_INTRODUCE_PARAMETER + kind: CodeActionKind = "refactor" + + document_uri: DocumentUri + position: typing.Range + + def validate(self, info): + introduce_parameter.IntroduceParameter( + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(self.position), + ) + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = introduce_parameter.IntroduceParameter( + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes( + new_parameter="new_parameter", + ) + return rope_changeset + + +class GenerateCode(Command): + """ + Given an undefined symbol under cursor, generate an empty + variable/function/class/module/package + """ + name = commands.COMMAND_GENERATE_CODE + kind: CodeActionKind = "quickfix" + + document_uri: DocumentUri + position: typing.Range + generate_kind: str + + def validate(self, info): + generate.create_generate( + kind=self.generate_kind, + project=self.project, + resource=info.resource, + offset=info.current_document.offset_at_position(self.position), + ) + + def get_changes(self): + current_document, resource = get_resource(self.workspace, self.document_uri) + + refactoring = generate.create_generate( + kind=self.generate_kind, + project=self.project, + resource=resource, + offset=current_document.offset_at_position(self.position), + ) + rope_changeset = refactoring.get_changes() + return rope_changeset + + @classmethod + def get_code_actions(cls, workspace, document, position): + return { + f"Generate {generate_kind}": cls( + workspace, + document_uri=document.uri, + position=position, + generate_kind=generate_kind, + ) for generate_kind in ["variable", "function", "class", "module", "package"] + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/pylsp_rope.egg-info/PKG-INFO new/pylsp-rope-0.1.11/pylsp_rope.egg-info/PKG-INFO --- old/pylsp-rope-0.1.10/pylsp_rope.egg-info/PKG-INFO 2022-08-25 05:29:01.000000000 +0200 +++ new/pylsp-rope-0.1.11/pylsp_rope.egg-info/PKG-INFO 2023-01-17 08:06:05.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pylsp-rope -Version: 0.1.10 +Version: 0.1.11 Summary: Extended refactoring capabilities for Python LSP Server using Rope. Home-page: https://github.com/python-rope/pylsp-rope Author: Lie Ryan @@ -67,6 +67,7 @@ - convert local variable to field (codeAction) - organize imports (codeAction) - introduce parameter (codeAction) +- generate variable/function/class from undefined variable (codeAction) - more to come... Refer to [Rope documentation](https://github.com/python-rope/rope/blob/master/docs/overview.rst) @@ -83,47 +84,67 @@ - Extract method including similar statements - Extract global method including similar statements -This refactoring works by triggering CodeAction when selecting a block of code. -Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on any block of code, extract +that expression into a method. Optionally, similar statements can also be +extracted. ### Extract variable +Variants: + - Extract variable - Extract global variable - Extract variable including similar statements - Extract global variable including similar statements -This refactoring works by triggering CodeAction when selecting a Python -expression. Optionally, similar statements can also be extracted. +When CodeAction is triggered and the cursor is on a expression, extract that +expression into a variable. Optionally, similar statements can also be +extracted. ### Inline -This refactoring works by triggering CodeAction when the cursor is on a -resolvable Python identifier. +When CodeAction is triggered and the cursor is on a resolvable Python variable, +replace all calls to that method with the method body. ### Use function -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, try to replace code whose AST matches the selected function with a +call to the function. ### Method to method object -This refactoring works by triggering CodeAction when the cursor is on the -function name of a `def` statement. +When CodeAction is triggered and the cursor is on the function name of a `def` +statement, create a callable class to replace that method. You may want to +inline the method afterwards to remove the indirection. ### Convert local variable to field -This refactoring works by triggering CodeAction when the cursor is on a local -variable. +When CodeAction is triggered wand the cursor is on a local variable inside a +method, convert that local variable to an attribute. ### Organize import -This refactoring works by triggering CodeAction anywhere. +Trigger CodeAction anywhere in a Python file to organize imports. ### Introduce parameter -This refactoring works by triggering CodeAction when the cursor is selecting -a Python identifier, including attribute access. +When CodeAction is triggered and the cursor is selecting a Python variable or +attribute, make that variable/attribute a parameter. + +### Generate code + +Variants: + +- [x] Generate variable +- [x] Generate function +- [x] Generate class +- [ ] Generate module +- [ ] Generate package + +When CodeAction is triggered and the cursor is on an undefined Python +variable, generate an empty variable/function/class/module/package for that +name. ## Caveat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/pylsp_rope.egg-info/SOURCES.txt new/pylsp-rope-0.1.11/pylsp_rope.egg-info/SOURCES.txt --- old/pylsp-rope-0.1.10/pylsp_rope.egg-info/SOURCES.txt 2022-08-25 05:29:01.000000000 +0200 +++ new/pylsp-rope-0.1.11/pylsp_rope.egg-info/SOURCES.txt 2023-01-17 08:06:05.000000000 +0100 @@ -10,6 +10,7 @@ pylsp_rope/lsp_diff.py pylsp_rope/plugin.py pylsp_rope/project.py +pylsp_rope/refactoring.py pylsp_rope/rope.py pylsp_rope/text.py pylsp_rope/typing.py @@ -24,6 +25,7 @@ test/helpers.py test/test_commands.py test/test_extract.py +test/test_generate.py test/test_import_utils.py test/test_inline.py test/test_introduce_parameter.py @@ -33,6 +35,9 @@ test/test_project.py test/test_usefunction.py test/fixtures/function.py +test/fixtures/generate_class.py +test/fixtures/generate_function.py +test/fixtures/generate_variable.py test/fixtures/introduce_parameter.py test/fixtures/many_changes.py test/fixtures/many_changes_inlined.py @@ -50,4 +55,5 @@ test/fixtures/simple_extract_method_with_similar.py test/fixtures/simple_extract_variable.py test/fixtures/simple_extract_variable_with_similar.py +test/fixtures/undefined_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.10/setup.cfg new/pylsp-rope-0.1.11/setup.cfg --- old/pylsp-rope-0.1.10/setup.cfg 2022-08-25 05:29:01.434133000 +0200 +++ new/pylsp-rope-0.1.11/setup.cfg 2023-01-17 08:06:05.202077000 +0100 @@ -1,6 +1,6 @@ [metadata] name = pylsp-rope -version = 0.1.10 +version = 0.1.11 author = Lie Ryan author_email = [email protected] url = https://github.com/python-rope/pylsp-rope diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/fixtures/generate_class.py new/pylsp-rope-0.1.11/test/fixtures/generate_class.py --- old/pylsp-rope-0.1.10/test/fixtures/generate_class.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/fixtures/generate_class.py 2023-01-17 07:21:20.000000000 +0100 @@ -0,0 +1,4 @@ +def foo(): + class undef_var(object): + pass + print(undef_var) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/fixtures/generate_function.py new/pylsp-rope-0.1.11/test/fixtures/generate_function.py --- old/pylsp-rope-0.1.10/test/fixtures/generate_function.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/fixtures/generate_function.py 2023-01-17 07:37:46.000000000 +0100 @@ -0,0 +1,4 @@ +def foo(): + def undef_var(): + pass + print(undef_var) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/fixtures/generate_variable.py new/pylsp-rope-0.1.11/test/fixtures/generate_variable.py --- old/pylsp-rope-0.1.10/test/fixtures/generate_variable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/fixtures/generate_variable.py 2023-01-17 02:39:09.000000000 +0100 @@ -0,0 +1,3 @@ +def foo(): + undef_var = None + print(undef_var) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/fixtures/undefined_variable.py new/pylsp-rope-0.1.11/test/fixtures/undefined_variable.py --- old/pylsp-rope-0.1.10/test/fixtures/undefined_variable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/fixtures/undefined_variable.py 2023-01-17 02:36:24.000000000 +0100 @@ -0,0 +1,2 @@ +def foo(): + print(undef_var) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/test_commands.py new/pylsp-rope-0.1.11/test/test_commands.py --- old/pylsp-rope-0.1.10/test/test_commands.py 2021-11-06 08:59:22.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/test_commands.py 2023-01-17 01:03:44.000000000 +0100 @@ -32,7 +32,7 @@ ] with patch( - "pylsp_rope.plugin.CommandRefactorInline.__call__", + "pylsp_rope.refactoring.CommandRefactorInline.__call__", side_effect=Exception("some unexpected exception"), ): pylsp_execute_command( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/test_generate.py new/pylsp-rope-0.1.11/test/test_generate.py --- old/pylsp-rope-0.1.10/test/test_generate.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pylsp-rope-0.1.11/test/test_generate.py 2023-01-17 07:37:10.000000000 +0100 @@ -0,0 +1,154 @@ +from pylsp_rope import commands, plugin, typing +from pylsp_rope.text import Range +from test.conftest import create_document +from test.helpers import assert_single_document_edit, assert_text_edits + + +def test_generate_variable(config, workspace, code_action_context): + document = create_document(workspace, "undefined_variable.py") + line = 1 + start_col = end_col = document.lines[line].index("undef_var") + selection = Range((line, start_col), (line, end_col)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + expected: typing.CodeAction = { + "title": "Generate variable", + "kind": "quickfix", + "command": { + "title": "Generate variable", + "command": commands.COMMAND_GENERATE_CODE, + "arguments": [ + { + "document_uri": document.uri, + "position": selection["start"], + "generate_kind": "variable", + } + ], + }, + } + + 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 + + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="generate_variable.py") + assert "undef_var = None" in new_text + + +def test_generate_function(config, workspace, code_action_context): + document = create_document(workspace, "undefined_variable.py") + line = 1 + start_col = end_col = document.lines[line].index("undef_var") + selection = Range((line, start_col), (line, end_col)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + expected: typing.CodeAction = { + "title": "Generate function", + "kind": "quickfix", + "command": { + "title": "Generate function", + "command": commands.COMMAND_GENERATE_CODE, + "arguments": [ + { + "document_uri": document.uri, + "position": selection["start"], + "generate_kind": "function", + } + ], + }, + } + + 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 + + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="generate_function.py") + assert "def undef_var():" in new_text + + +def test_generate_class(config, workspace, code_action_context): + document = create_document(workspace, "undefined_variable.py") + line = 1 + start_col = end_col = document.lines[line].index("undef_var") + selection = Range((line, start_col), (line, end_col)) + + response = plugin.pylsp_code_actions( + config=config, + workspace=workspace, + document=document, + range=selection, + context=code_action_context, + ) + + expected: typing.CodeAction = { + "title": "Generate class", + "kind": "quickfix", + "command": { + "title": "Generate class", + "command": commands.COMMAND_GENERATE_CODE, + "arguments": [ + { + "document_uri": document.uri, + "position": selection["start"], + "generate_kind": "class", + } + ], + }, + } + + 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 + + document_edits = assert_single_document_edit(edit_request, document) + new_text = assert_text_edits(document_edits, target="generate_class.py") + assert "class undef_var(object):" in new_text diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pylsp-rope-0.1.10/test/test_introduce_parameter.py new/pylsp-rope-0.1.11/test/test_introduce_parameter.py --- old/pylsp-rope-0.1.10/test/test_introduce_parameter.py 2022-03-31 16:41:58.000000000 +0200 +++ new/pylsp-rope-0.1.11/test/test_introduce_parameter.py 2023-01-17 01:03:44.000000000 +0100 @@ -1,4 +1,3 @@ - from pylsp_rope import commands, plugin, typing from pylsp_rope.text import Range from test.conftest import create_document
