Coverage for src/debputy/lsp/quickfixes.py: 42%

63 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-07 12:14 +0200

1from typing import ( 

2 Literal, 

3 TypedDict, 

4 Callable, 

5 Iterable, 

6 Union, 

7 TypeVar, 

8 Mapping, 

9 Dict, 

10 Optional, 

11 List, 

12 cast, 

13) 

14 

15from lsprotocol.types import ( 

16 CodeAction, 

17 Command, 

18 CodeActionParams, 

19 Diagnostic, 

20 CodeActionDisabledType, 

21 TextEdit, 

22 WorkspaceEdit, 

23 TextDocumentEdit, 

24 OptionalVersionedTextDocumentIdentifier, 

25 Range, 

26 Position, 

27 CodeActionKind, 

28) 

29 

30from debputy.util import _warn 

31 

32try: 

33 from debian._deb822_repro.locatable import Position as TEPosition, Range as TERange 

34 

35 from pygls.server import LanguageServer 

36 from pygls.workspace import TextDocument 

37except ImportError: 

38 pass 

39 

40 

41CodeActionName = Literal["correct-text", "remove-line"] 

42 

43 

44class CorrectTextCodeAction(TypedDict): 

45 code_action: Literal["correct-text"] 

46 correct_value: str 

47 

48 

49class RemoveLineCodeAction(TypedDict): 

50 code_action: Literal["remove-line"] 

51 

52 

53def propose_correct_text_quick_fix(correct_value: str) -> CorrectTextCodeAction: 

54 return { 

55 "code_action": "correct-text", 

56 "correct_value": correct_value, 

57 } 

58 

59 

60def propose_remove_line_quick_fix() -> RemoveLineCodeAction: 

61 return { 

62 "code_action": "remove-line", 

63 } 

64 

65 

66CODE_ACTION_HANDLERS: Dict[ 

67 CodeActionName, 

68 Callable[ 

69 [Mapping[str, str], CodeActionParams, Diagnostic], 

70 Iterable[Union[CodeAction, Command]], 

71 ], 

72] = {} 

73M = TypeVar("M", bound=Mapping[str, str]) 

74Handler = Callable[ 

75 [M, CodeActionParams, Diagnostic], 

76 Iterable[Union[CodeAction, Command]], 

77] 

78 

79 

80def _code_handler_for(action_name: CodeActionName) -> Callable[[Handler], Handler]: 

81 def _wrapper(func: Handler) -> Handler: 

82 assert action_name not in CODE_ACTION_HANDLERS 

83 CODE_ACTION_HANDLERS[action_name] = func 

84 return func 

85 

86 return _wrapper 

87 

88 

89@_code_handler_for("correct-text") 

90def _correct_value_code_action( 

91 code_action_data: CorrectTextCodeAction, 

92 code_action_params: CodeActionParams, 

93 diagnostic: Diagnostic, 

94) -> Iterable[Union[CodeAction, Command]]: 

95 corrected_value = code_action_data["correct_value"] 

96 edits = [ 

97 TextEdit( 

98 diagnostic.range, 

99 corrected_value, 

100 ), 

101 ] 

102 yield CodeAction( 

103 title=f'Replace with "{corrected_value}"', 

104 kind=CodeActionKind.QuickFix, 

105 diagnostics=[diagnostic], 

106 edit=WorkspaceEdit( 

107 changes={code_action_params.text_document.uri: edits}, 

108 document_changes=[ 

109 TextDocumentEdit( 

110 text_document=OptionalVersionedTextDocumentIdentifier( 

111 uri=code_action_params.text_document.uri, 

112 ), 

113 edits=edits, 

114 ) 

115 ], 

116 ), 

117 ) 

118 

119 

120def range_compatible_with_remove_line_fix(range_: Range) -> bool: 

121 start = range_.start 

122 end = range_.end 

123 if start.line != end.line and (start.line + 1 != end.line or end.character > 0): 

124 return False 

125 return True 

126 

127 

128@_code_handler_for("remove-line") 

129def _correct_value_code_action( 

130 _code_action_data: RemoveLineCodeAction, 

131 code_action_params: CodeActionParams, 

132 diagnostic: Diagnostic, 

133) -> Iterable[Union[CodeAction, Command]]: 

134 start = code_action_params.range.start 

135 if range_compatible_with_remove_line_fix(code_action_params.range): 

136 _warn( 

137 "Bug: the quick was used for a diagnostic that spanned multiple lines and would corrupt the file." 

138 ) 

139 return 

140 

141 edits = [ 

142 TextEdit( 

143 Range( 

144 start=Position( 

145 line=start.line, 

146 character=0, 

147 ), 

148 end=Position( 

149 line=start.line + 1, 

150 character=0, 

151 ), 

152 ), 

153 "", 

154 ), 

155 ] 

156 yield CodeAction( 

157 title="Remove the line", 

158 kind=CodeActionKind.QuickFix, 

159 diagnostics=[diagnostic], 

160 edit=WorkspaceEdit( 

161 changes={code_action_params.text_document.uri: edits}, 

162 document_changes=[ 

163 TextDocumentEdit( 

164 text_document=OptionalVersionedTextDocumentIdentifier( 

165 uri=code_action_params.text_document.uri, 

166 ), 

167 edits=edits, 

168 ) 

169 ], 

170 ), 

171 ) 

172 

173 

174def provide_standard_quickfixes_from_diagnostics( 

175 code_action_params: CodeActionParams, 

176) -> Optional[List[Union[Command, CodeAction]]]: 

177 actions = [] 

178 for diagnostic in code_action_params.context.diagnostics: 

179 data = diagnostic.data 

180 if not isinstance(data, list): 

181 data = [data] 

182 for action_suggestion in data: 

183 if ( 

184 action_suggestion 

185 and isinstance(action_suggestion, Mapping) 

186 and "code_action" in action_suggestion 

187 ): 

188 action_name: CodeActionName = action_suggestion["code_action"] 

189 handler = CODE_ACTION_HANDLERS.get(action_name) 

190 if handler is not None: 

191 actions.extend( 

192 handler( 

193 cast("Mapping[str, str]", action_suggestion), 

194 code_action_params, 

195 diagnostic, 

196 ) 

197 ) 

198 else: 

199 _warn(f"No codeAction handler for {action_name} !?") 

200 if not actions: 

201 return None 

202 return actions