Coverage for src/debputy/lsp/lsp_debian_copyright.py: 75%

100 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-01-27 13:59 +0000

1import re 

2from typing import ( 

3 Union, 

4 Sequence, 

5 Tuple, 

6 Optional, 

7 List, 

8 Dict, 

9) 

10 

11from debputy.linting.lint_util import LintState 

12from debputy.lsp.debputy_ls import DebputyLanguageServer 

13from debputy.lsp.lsp_debian_control_reference_data import ( 

14 Deb822KnownField, 

15 Dep5FileMetadata, 

16 StanzaMetadata, 

17) 

18from debputy.lsp.lsp_features import ( 

19 lint_diagnostics, 

20 lsp_completer, 

21 lsp_hover, 

22 lsp_standard_handler, 

23 lsp_folding_ranges, 

24 lsp_semantic_tokens_full, 

25 lsp_will_save_wait_until, 

26 lsp_format_document, 

27 SecondaryLanguage, 

28 LanguageDispatchRule, 

29) 

30from debputy.lsp.lsp_generic_deb822 import ( 

31 deb822_completer, 

32 deb822_hover, 

33 deb822_folding_ranges, 

34 deb822_semantic_tokens_full, 

35 deb822_token_iter, 

36 deb822_format_file, 

37) 

38from debputy.lsp.quickfixes import ( 

39 propose_correct_text_quick_fix, 

40) 

41from debputy.lsp.vendoring._deb822_repro import ( 

42 Deb822FileElement, 

43 Deb822ParagraphElement, 

44) 

45from debputy.lsp.vendoring._deb822_repro.parsing import ( 

46 Deb822KeyValuePairElement, 

47) 

48from debputy.lsprotocol.types import ( 

49 Range, 

50 CompletionItem, 

51 CompletionList, 

52 CompletionParams, 

53 DiagnosticRelatedInformation, 

54 Location, 

55 HoverParams, 

56 Hover, 

57 TEXT_DOCUMENT_CODE_ACTION, 

58 SemanticTokens, 

59 SemanticTokensParams, 

60 FoldingRangeParams, 

61 FoldingRange, 

62 WillSaveTextDocumentParams, 

63 TextEdit, 

64 DocumentFormattingParams, 

65) 

66from debputy.util import detect_possible_typo 

67 

68try: 

69 from debputy.lsp.vendoring._deb822_repro.locatable import ( 

70 Position as TEPosition, 

71 Range as TERange, 

72 ) 

73 

74 from pygls.server import LanguageServer 

75 from pygls.workspace import TextDocument 

76except ImportError: 

77 pass 

78 

79 

80_CONTAINS_SPACE_OR_COLON = re.compile(r"[\s:]") 

81 

82_DISPATCH_RULE = LanguageDispatchRule.new_rule( 

83 "debian/copyright", 

84 "debian/copyright", 

85 [ 

86 # emacs's name 

87 SecondaryLanguage("debian-copyright"), 

88 # vim's name 

89 SecondaryLanguage("debcopyright"), 

90 ], 

91) 

92 

93_DEP5_FILE_METADATA = Dep5FileMetadata() 

94 

95lsp_standard_handler(_DISPATCH_RULE, TEXT_DOCUMENT_CODE_ACTION) 

96 

97 

98@lsp_hover(_DISPATCH_RULE) 

99def _debian_copyright_hover( 

100 ls: "DebputyLanguageServer", 

101 params: HoverParams, 

102) -> Optional[Hover]: 

103 return deb822_hover(ls, params, _DEP5_FILE_METADATA) 

104 

105 

106@lsp_completer(_DISPATCH_RULE) 

107def _debian_copyright_completions( 

108 ls: "DebputyLanguageServer", 

109 params: CompletionParams, 

110) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]: 

111 return deb822_completer(ls, params, _DEP5_FILE_METADATA) 

112 

113 

114@lsp_folding_ranges(_DISPATCH_RULE) 

115def _debian_copyright_folding_ranges( 

116 ls: "DebputyLanguageServer", 

117 params: FoldingRangeParams, 

118) -> Optional[Sequence[FoldingRange]]: 

119 return deb822_folding_ranges(ls, params, _DEP5_FILE_METADATA) 

120 

121 

122def _scan_for_syntax_errors_and_token_level_diagnostics( 

123 deb822_file: Deb822FileElement, 

124 lint_state: LintState, 

125) -> int: 

126 first_error = len(lint_state.lines) + 1 

127 spell_checker = lint_state.spellchecker() 

128 for ( 

129 token, 

130 start_line, 

131 start_offset, 

132 end_line, 

133 end_offset, 

134 ) in deb822_token_iter(deb822_file.iter_tokens()): 

135 if token.is_error: 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true

136 first_error = min(first_error, start_line) 

137 start_pos = TEPosition( 

138 start_line, 

139 start_offset, 

140 ) 

141 end_pos = TEPosition( 

142 end_line, 

143 end_offset, 

144 ) 

145 token_range = TERange.between(start_pos, end_pos) 

146 lint_state.emit_diagnostic( 

147 token_range, 

148 "Syntax error", 

149 "error", 

150 "debputy", 

151 ) 

152 elif token.is_comment: 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

153 for word, col_pos, end_col_pos in spell_checker.iter_words(token.text): 

154 corrections = spell_checker.provide_corrections_for(word) 

155 if not corrections: 

156 continue 

157 start_pos = TEPosition( 

158 start_line, 

159 col_pos, 

160 ) 

161 end_pos = TEPosition( 

162 start_line, 

163 end_col_pos, 

164 ) 

165 word_range = TERange.between(start_pos, end_pos) 

166 lint_state.emit_diagnostic( 

167 word_range, 

168 f'Spelling "{word}"', 

169 "spelling", 

170 "debputy", 

171 quickfixes=[propose_correct_text_quick_fix(c) for c in corrections], 

172 enable_non_interactive_auto_fix=False, 

173 ) 

174 return first_error 

175 

176 

177def _looks_like_a_dep5_file( 

178 deb822_file: Deb822FileElement, 

179 stanzas: List[Deb822ParagraphElement], 

180) -> bool: 

181 if not stanzas or "Format" not in stanzas[0]: 

182 # No parseable stanzas or the first one did not have a Format, which is necessary. 

183 return False 

184 

185 for part in deb822_file.iter_parts(): 185 ↛ 191line 185 didn't jump to line 191 because the loop on line 185 didn't complete

186 if part.is_error: 

187 # Error first, then it might just be a "Format:" in the middle of a free-text file. 

188 return False 

189 if isinstance(part, Deb822ParagraphElement): 189 ↛ 185line 189 didn't jump to line 185 because the condition on line 189 was always true

190 break 

191 return True 

192 

193 

194@lint_diagnostics(_DISPATCH_RULE) 

195def _lint_debian_copyright(lint_state: LintState) -> None: 

196 doc_reference = lint_state.doc_uri 

197 deb822_file = lint_state.parsed_deb822_file_content 

198 stanzas = list(deb822_file) 

199 

200 if not _looks_like_a_dep5_file(deb822_file, stanzas): 

201 return 

202 

203 first_error = _scan_for_syntax_errors_and_token_level_diagnostics( 

204 deb822_file, 

205 lint_state, 

206 ) 

207 header_stanza, files_stanza, _ = _DEP5_FILE_METADATA.stanza_types() 

208 

209 for paragraph_no, paragraph in enumerate(stanzas, start=1): 

210 paragraph_pos = paragraph.position_in_file() 

211 if paragraph_pos.line_position >= first_error: 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true

212 break 

213 is_files_or_license_stanza = paragraph_no != 1 

214 if is_files_or_license_stanza: 

215 stanza_metadata = _DEP5_FILE_METADATA.classify_stanza( 

216 paragraph, 

217 paragraph_no, 

218 ) 

219 other_stanza_metadata = header_stanza 

220 other_stanza_name = "Header" 

221 elif "Format" in paragraph: 221 ↛ 227line 221 didn't jump to line 227 because the condition on line 221 was always true

222 is_dep5 = True 

223 stanza_metadata = header_stanza 

224 other_stanza_metadata = files_stanza 

225 other_stanza_name = "Files/License" 

226 else: 

227 break 

228 

229 stanza_metadata.stanza_diagnostics( 

230 deb822_file, 

231 paragraph, 

232 paragraph_pos, 

233 doc_reference, 

234 lint_state, 

235 confusable_with_stanza_name=other_stanza_name, 

236 confusable_with_stanza_metadata=other_stanza_metadata, 

237 ) 

238 

239 

240@lsp_will_save_wait_until(_DISPATCH_RULE) 

241def _debian_copyright_on_save_formatting( 

242 ls: "DebputyLanguageServer", 

243 params: WillSaveTextDocumentParams, 

244) -> Optional[Sequence[TextEdit]]: 

245 doc = ls.workspace.get_text_document(params.text_document.uri) 

246 lint_state = ls.lint_state(doc) 

247 return deb822_format_file(lint_state, _DEP5_FILE_METADATA) 

248 

249 

250def _reformat_debian_copyright( 

251 lint_state: LintState, 

252) -> Optional[Sequence[TextEdit]]: 

253 return deb822_format_file(lint_state, _DEP5_FILE_METADATA) 

254 

255 

256@lsp_format_document(_DISPATCH_RULE) 

257def _debian_copyright_on_save_formatting( 

258 ls: "DebputyLanguageServer", 

259 params: DocumentFormattingParams, 

260) -> Optional[Sequence[TextEdit]]: 

261 doc = ls.workspace.get_text_document(params.text_document.uri) 

262 lint_state = ls.lint_state(doc) 

263 return deb822_format_file(lint_state, _DEP5_FILE_METADATA) 

264 

265 

266@lsp_semantic_tokens_full(_DISPATCH_RULE) 

267def _debian_copyright_semantic_tokens_full( 

268 ls: "DebputyLanguageServer", 

269 request: SemanticTokensParams, 

270) -> Optional[SemanticTokens]: 

271 return deb822_semantic_tokens_full( 

272 ls, 

273 request, 

274 _DEP5_FILE_METADATA, 

275 )