diff --git a/bundled/tool/lsp_server.py b/bundled/tool/lsp_server.py index 5617cef3..de83672c 100644 --- a/bundled/tool/lsp_server.py +++ b/bundled/tool/lsp_server.py @@ -245,10 +245,7 @@ def __init__(self): Callable[[workspace.Document, List[lsp.Diagnostic]], List[lsp.CodeAction]], ] = {} - def quick_fix( - self, - codes: Union[str, List[str]], - ): + def quick_fix(self, codes: Union[str, List[str]]): """Decorator used for registering quick fixes.""" def decorator( @@ -275,11 +272,7 @@ def solutions( ]: """Given a pylint error code returns a function, if available, that provides quick fix code actions.""" - - try: - return self._solutions[code] - except KeyError: - return None + return self._solutions.get(code, None) QUICK_FIXES = QuickFixSolutions() @@ -287,7 +280,9 @@ def solutions( @LSP_SERVER.feature( lsp.TEXT_DOCUMENT_CODE_ACTION, - lsp.CodeActionOptions(code_action_kinds=[lsp.CodeActionKind.QuickFix]), + lsp.CodeActionOptions( + code_action_kinds=[lsp.CodeActionKind.QuickFix], resolve_provider=True + ), ) def code_action(params: lsp.CodeActionParams) -> List[lsp.CodeAction]: """LSP handler for textDocument/codeAction request.""" @@ -347,44 +342,48 @@ def organize_imports( ] -REPLACEMENTS = { +REPLACEMENTS: Dict[str, re.Pattern] = { "C0117:unnecessary-negation": [ { - "pattern": r"\snot\s+not", + "pattern": re.compile(r"\snot\s+not"), "repl": r"", } ], "C0121:singleton-comparison": [ { - "pattern": r"(\w+)\s+(?:==\s+True|!=\s+False)|(?:True\s+==|False\s+!=)\s+(\w+)", + "pattern": re.compile( + r"(\w+)\s+(?:==\s+True|!=\s+False)|(?:True\s+==|False\s+!=)\s+(\w+)" + ), "repl": r"\1\2", }, { - "pattern": r"(\w+)\s+(?:!=\s+True|==\s+False)|(?:True\s+!=|False\s+==)\s+(\w+)", + "pattern": re.compile( + r"(\w+)\s+(?:!=\s+True|==\s+False)|(?:True\s+!=|False\s+==)\s+(\w+)" + ), "repl": r"not \1\2", }, ], "C0123:unidiomatic-typecheck": [ { - "pattern": r"type\((\w+)\)\s+is\s+(\w+)", + "pattern": re.compile(r"type\((\w+)\)\s+is\s+(\w+)"), "repl": r"isinstance(\1, \2)", } ], "R0205:useless-object-inheritance": [ { - "pattern": r"class (\w+)\(object\):", + "pattern": re.compile(r"class (\w+)\(object\):"), "repl": r"class \1:", } ], "R1721:unnecessary-comprehension": [ { - "pattern": r"\{([\w\s,]+) for [\w\s,]+ in ([\w\s,]+)\}", + "pattern": re.compile(r"\{([\w\s,]+) for [\w\s,]+ in ([\w\s,]+)\}"), "repl": r"set(\2)", } ], "E1141:dict-iter-missing-items": [ { - "pattern": r"for\s+(\w+),\s+(\w+)\s+in\s+(\w+)\s*:", + "pattern": re.compile(r"for\s+(\w+),\s+(\w+)\s+in\s+(\w+)\s*:"), "repl": r"for \1, \2 in \3.items():", } ], @@ -420,18 +419,28 @@ def fix_with_replacement( title=f"{TOOL_DISPLAY}: Run autofix code action", kind=lsp.CodeActionKind.QuickFix, diagnostics=diagnostics, - edit=_create_workspace_edits( - document, - [ - _get_replacement_edit(diagnostic, document.lines) - for diagnostic in diagnostics - if diagnostic.code in REPLACEMENTS - ], - ), + edit=None, + data=document.uri, ) ] +@LSP_SERVER.feature(lsp.CODE_ACTION_RESOLVE) +def code_action_resolve(params: lsp.CodeAction) -> lsp.CodeAction: + """LSP handler for codeAction/resolve request.""" + if params.data: + document = LSP_SERVER.workspace.get_document(params.data) + params.edit = _create_workspace_edits( + document, + [ + _get_replacement_edit(diagnostic, document.lines) + for diagnostic in params.diagnostics + if diagnostic.source == TOOL_DISPLAY and diagnostic.code in REPLACEMENTS + ], + ) + return params + + def _command_quick_fix( diagnostics: List[lsp.Diagnostic], title: str, diff --git a/src/test/python_tests/test_code_actions.py b/src/test/python_tests/test_code_actions.py index 31d5195f..62baa19c 100644 --- a/src/test/python_tests/test_code_actions.py +++ b/src/test/python_tests/test_code_actions.py @@ -307,12 +307,15 @@ def _handler(params): "context": {"diagnostics": diagnostics}, } ) - text_document = actual_code_actions[0]["edit"]["documentChanges"][0][ - "textDocument" - ] - text_range = actual_code_actions[0]["edit"]["documentChanges"][0]["edits"][ - 0 - ]["range"] + + assert_that( + all("edit" not in action for action in actual_code_actions), + is_(True), + ) + + actual_code_action = ls_session.code_action_resolve(actual_code_actions[0]) + + changes = actual_code_action["edit"]["documentChanges"] expected = [ { "title": f"{LINTER}: Run autofix code action", @@ -321,10 +324,10 @@ def _handler(params): "edit": { "documentChanges": [ { - "textDocument": text_document, + "textDocument": changes[0]["textDocument"], "edits": [ { - "range": text_range, + "range": changes[0]["edits"][0]["range"], "newText": new_text, } ], @@ -336,6 +339,6 @@ def _handler(params): ] assert_that( - actual_code_actions[0]["edit"]["documentChanges"], + actual_code_action["edit"]["documentChanges"], is_(expected[0]["edit"]["documentChanges"]), )