eric7/Plugins/PluginCodeStyleChecker.py

branch
eric7
changeset 8312
800c432b34c8
parent 8240
93b8a353c4bf
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the code style checker plug-in.
8 """
9
10 import os
11 import textwrap
12 import contextlib
13
14 from PyQt5.QtCore import QObject, pyqtSignal, QCoreApplication
15
16 from E5Gui.E5Application import e5App
17 from E5Gui.E5Action import E5Action
18 from Project.ProjectBrowserModel import ProjectBrowserFileItem
19 from Utilities import determinePythonVersion
20
21 import Preferences
22 import UI.Info
23
24 # Start-Of-Header
25 name = "Code Style Checker Plugin"
26 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
27 autoactivate = True
28 deactivateable = True
29 version = UI.Info.VersionOnly
30 className = "CodeStyleCheckerPlugin"
31 packageName = "__core__"
32 shortDescription = "Show the Python Code Style Checker dialog."
33 longDescription = (
34 """This plugin implements the Python Code Style"""
35 """ Checker dialog. A PEP-8 checker is used to check Python source"""
36 """ files for compliance to the code style conventions given in PEP-8."""
37 """ A PEP-257 checker is used to check Python source files for"""
38 """ compliance to docstring conventions given in PEP-257 and an"""
39 """ eric variant is used to check against eric conventions."""
40 )
41 pyqtApi = 2
42 # End-Of-Header
43
44
45 error = ""
46
47
48 class CodeStyleCheckerPlugin(QObject):
49 """
50 Class implementing the code style checker plug-in.
51
52 @signal styleChecked(str, dict, int, list) emitted when the style check was
53 done for a file.
54 @signal batchFinished() emitted when a style check batch is done
55 @signal error(str, str) emitted in case of an error
56 """
57 styleChecked = pyqtSignal(str, dict, int, list)
58 batchFinished = pyqtSignal()
59 error = pyqtSignal(str, str)
60
61 def __init__(self, ui):
62 """
63 Constructor
64
65 @param ui reference to the user interface object (UI.UserInterface)
66 """
67 super().__init__(ui)
68 self.__ui = ui
69 self.__initialize()
70
71 self.backgroundService = e5App().getObject("BackgroundService")
72
73 path = os.path.join(
74 os.path.dirname(__file__), 'CheckerPlugins', 'CodeStyleChecker')
75 self.backgroundService.serviceConnect(
76 'style', 'Python3', path, 'CodeStyleChecker',
77 self.__translateStyleCheck,
78 onErrorCallback=self.serviceErrorPy3,
79 onBatchDone=self.batchJobDone)
80
81 self.queuedBatches = []
82 self.batchesFinished = True
83
84 self.__wrapper = textwrap.TextWrapper(width=80)
85
86 def __serviceError(self, fn, msg):
87 """
88 Private slot handling service errors.
89
90 @param fn file name (string)
91 @param msg message text (string)
92 """
93 self.error.emit(fn, msg)
94
95 def serviceErrorPy3(self, fx, lang, fn, msg):
96 """
97 Public slot handling service errors for Python 3.
98
99 @param fx service name (string)
100 @param lang language (string)
101 @param fn file name (string)
102 @param msg message text (string)
103 """
104 if fx in ['style', 'batch_style'] and lang == 'Python3':
105 if fx == 'style':
106 self.__serviceError(fn, msg)
107 else:
108 self.__serviceError(self.tr("Python 3 batch check"), msg)
109 self.batchJobDone(fx, lang)
110
111 def batchJobDone(self, fx, lang):
112 """
113 Public slot handling the completion of a batch job.
114
115 @param fx service name (string)
116 @param lang language (string)
117 """
118 if fx in ['style', 'batch_style']:
119 if lang in self.queuedBatches:
120 self.queuedBatches.remove(lang)
121 # prevent sending the signal multiple times
122 if len(self.queuedBatches) == 0 and not self.batchesFinished:
123 self.batchFinished.emit()
124 self.batchesFinished = True
125
126 def __initialize(self):
127 """
128 Private slot to (re)initialize the plugin.
129 """
130 self.__projectAct = None
131 self.__projectCodeStyleCheckerDialog = None
132
133 self.__projectBrowserAct = None
134 self.__projectBrowserMenu = None
135 self.__projectBrowserCodeStyleCheckerDialog = None
136
137 self.__editors = []
138 self.__editorAct = None
139 self.__editorCodeStyleCheckerDialog = None
140
141 def styleCheck(self, lang, filename, source, args):
142 """
143 Public method to prepare a style check on one Python source file.
144
145 @param lang language of the file or None to determine by internal
146 algorithm
147 @type str or None
148 @param filename source filename
149 @type str
150 @param source string containing the code to check
151 @type str
152 @param args arguments used by the codeStyleCheck function (list of
153 excludeMessages, includeMessages, repeatMessages, fixCodes,
154 noFixCodes, fixIssues, maxLineLength, blankLines, hangClosing,
155 docType, codeComplexityArgs, miscellaneousArgs, errors, eol,
156 encoding, backup)
157 @type list of (str, str, bool, str, str, bool, int, list of (int, int),
158 bool, str, dict, dict, list of str, str, str, bool)
159 """
160 if lang is None:
161 lang = 'Python{0}'.format(determinePythonVersion(filename, source))
162 if lang != 'Python3':
163 return
164
165 data = [source, args]
166 self.backgroundService.enqueueRequest('style', lang, filename, data)
167
168 def styleBatchCheck(self, argumentsList):
169 """
170 Public method to prepare a style check on multiple Python source files.
171
172 @param argumentsList list of arguments tuples with each tuple
173 containing filename, source and args as given in styleCheck()
174 method
175 @type list of tuple of (str, str, list)
176 """
177 data = {
178 "Python3": [],
179 }
180 for filename, source, args in argumentsList:
181 lang = 'Python{0}'.format(determinePythonVersion(filename, source))
182 if lang != 'Python3':
183 continue
184 else:
185 data[lang].append((filename, source, args))
186
187 self.queuedBatches = []
188 if data['Python3']:
189 self.queuedBatches.append('Python3')
190 self.backgroundService.enqueueRequest('batch_style', 'Python3', "",
191 data['Python3'])
192 self.batchesFinished = False
193
194 def cancelStyleBatchCheck(self):
195 """
196 Public method to cancel all batch jobs.
197 """
198 self.backgroundService.requestCancel('batch_style', 'Python3')
199
200 def __translateStyleCheck(self, fn, codeStyleCheckerStats, results):
201 """
202 Private slot called after perfoming a style check on one file.
203
204 @param fn filename of the just checked file
205 @type str
206 @param codeStyleCheckerStats stats of style and name check
207 @type dict
208 @param results dictionary containing the check result data
209 (see CodesStyleChecker.__checkCodeStyle for details)
210 @type dict
211 """
212 from CheckerPlugins.CodeStyleChecker.translations import (
213 getTranslatedMessage
214 )
215
216 fixes = 0
217 for result in results:
218 msg = getTranslatedMessage(result["code"], result["args"])
219
220 if result["fixcode"]:
221 fixes += 1
222 trFixedMsg = getTranslatedMessage(result["fixcode"],
223 result["fixargs"])
224
225 msg += "\n" + QCoreApplication.translate(
226 'CodeStyleCheckerDialog', "Fix: {0}").format(trFixedMsg)
227
228 result["display"] = "\n".join(self.__wrapper.wrap(msg))
229 self.styleChecked.emit(fn, codeStyleCheckerStats, fixes, results)
230
231 def activate(self):
232 """
233 Public method to activate this plugin.
234
235 @return tuple of None and activation status (boolean)
236 """
237 menu = e5App().getObject("Project").getMenu("Checks")
238 if menu:
239 self.__projectAct = E5Action(
240 self.tr('Check Code Style'),
241 self.tr('&Code Style...'), 0, 0,
242 self, 'project_check_pep8')
243 self.__projectAct.setStatusTip(
244 self.tr('Check code style.'))
245 self.__projectAct.setWhatsThis(self.tr(
246 """<b>Check Code Style...</b>"""
247 """<p>This checks Python files for compliance to the"""
248 """ code style conventions given in various PEPs.</p>"""
249 ))
250 self.__projectAct.triggered.connect(
251 self.__projectCodeStyleCheck)
252 e5App().getObject("Project").addE5Actions([self.__projectAct])
253 menu.addAction(self.__projectAct)
254
255 self.__editorAct = E5Action(
256 self.tr('Check Code Style'),
257 self.tr('&Code Style...'), 0, 0,
258 self, "")
259 self.__editorAct.setWhatsThis(self.tr(
260 """<b>Check Code Style...</b>"""
261 """<p>This checks Python files for compliance to the"""
262 """ code style conventions given in various PEPs.</p>"""
263 ))
264 self.__editorAct.triggered.connect(self.__editorCodeStyleCheck)
265
266 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu)
267 e5App().getObject("ProjectBrowser").getProjectBrowser(
268 "sources").showMenu.connect(self.__projectBrowserShowMenu)
269 e5App().getObject("ViewManager").editorOpenedEd.connect(
270 self.__editorOpened)
271 e5App().getObject("ViewManager").editorClosedEd.connect(
272 self.__editorClosed)
273
274 for editor in e5App().getObject("ViewManager").getOpenEditors():
275 self.__editorOpened(editor)
276
277 return None, True
278
279 def deactivate(self):
280 """
281 Public method to deactivate this plugin.
282 """
283 e5App().getObject("Project").showMenu.disconnect(
284 self.__projectShowMenu)
285 e5App().getObject("ProjectBrowser").getProjectBrowser(
286 "sources").showMenu.disconnect(self.__projectBrowserShowMenu)
287 e5App().getObject("ViewManager").editorOpenedEd.disconnect(
288 self.__editorOpened)
289 e5App().getObject("ViewManager").editorClosedEd.disconnect(
290 self.__editorClosed)
291
292 menu = e5App().getObject("Project").getMenu("Checks")
293 if menu:
294 menu.removeAction(self.__projectAct)
295
296 if self.__projectBrowserMenu and self.__projectBrowserAct:
297 self.__projectBrowserMenu.removeAction(
298 self.__projectBrowserAct)
299
300 for editor in self.__editors:
301 editor.showMenu.disconnect(self.__editorShowMenu)
302 menu = editor.getMenu("Checks")
303 if menu is not None:
304 menu.removeAction(self.__editorAct)
305
306 self.__initialize()
307
308 def __projectShowMenu(self, menuName, menu):
309 """
310 Private slot called, when the the project menu or a submenu is
311 about to be shown.
312
313 @param menuName name of the menu to be shown (string)
314 @param menu reference to the menu (QMenu)
315 """
316 if menuName == "Checks" and self.__projectAct is not None:
317 self.__projectAct.setEnabled(
318 e5App().getObject("Project").getProjectLanguage() in
319 ["Python3", "MicroPython"])
320
321 def __projectBrowserShowMenu(self, menuName, menu):
322 """
323 Private slot called, when the the project browser menu or a submenu is
324 about to be shown.
325
326 @param menuName name of the menu to be shown (string)
327 @param menu reference to the menu (QMenu)
328 """
329 if (
330 menuName == "Checks" and
331 e5App().getObject("Project").getProjectLanguage() in
332 ["Python3", "MicroPython"]
333 ):
334 self.__projectBrowserMenu = menu
335 if self.__projectBrowserAct is None:
336 self.__projectBrowserAct = E5Action(
337 self.tr('Check Code Style'),
338 self.tr('&Code Style...'), 0, 0,
339 self, "")
340 self.__projectBrowserAct.setWhatsThis(self.tr(
341 """<b>Check Code Style...</b>"""
342 """<p>This checks Python files for compliance to the"""
343 """ code style conventions given in various PEPs.</p>"""
344 ))
345 self.__projectBrowserAct.triggered.connect(
346 self.__projectBrowserCodeStyleCheck)
347 if self.__projectBrowserAct not in menu.actions():
348 menu.addAction(self.__projectBrowserAct)
349
350 def __projectCodeStyleCheck(self):
351 """
352 Private slot used to check the project files for code style.
353 """
354 project = e5App().getObject("Project")
355 project.saveAllScripts()
356 ppath = project.getProjectPath()
357 files = [os.path.join(ppath, file)
358 for file in project.pdata["SOURCES"]
359 if file.endswith(
360 tuple(Preferences.getPython("Python3Extensions")))]
361
362 from CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog
363 self.__projectCodeStyleCheckerDialog = (
364 CodeStyleCheckerDialog.CodeStyleCheckerDialog(self)
365 )
366 self.__projectCodeStyleCheckerDialog.show()
367 self.__projectCodeStyleCheckerDialog.prepare(files, project)
368
369 def __projectBrowserCodeStyleCheck(self):
370 """
371 Private method to handle the code style check context menu action of
372 the project sources browser.
373 """
374 browser = (
375 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")
376 )
377 if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
378 fn = []
379 for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
380 fn.append(itm.fileName())
381 isDir = False
382 else:
383 itm = browser.model().item(browser.currentIndex())
384 try:
385 fn = itm.fileName()
386 isDir = False
387 except AttributeError:
388 fn = itm.dirName()
389 isDir = True
390
391 from CheckerPlugins.CodeStyleChecker import CodeStyleCheckerDialog
392 self.__projectBrowserCodeStyleCheckerDialog = (
393 CodeStyleCheckerDialog.CodeStyleCheckerDialog(self)
394 )
395 self.__projectBrowserCodeStyleCheckerDialog.show()
396 if isDir:
397 self.__projectBrowserCodeStyleCheckerDialog.start(
398 fn, save=True)
399 else:
400 self.__projectBrowserCodeStyleCheckerDialog.start(
401 fn, save=True, repeat=True)
402
403 def __editorOpened(self, editor):
404 """
405 Private slot called, when a new editor was opened.
406
407 @param editor reference to the new editor (QScintilla.Editor)
408 """
409 menu = editor.getMenu("Checks")
410 if menu is not None:
411 menu.addAction(self.__editorAct)
412 editor.showMenu.connect(self.__editorShowMenu)
413 self.__editors.append(editor)
414
415 def __editorClosed(self, editor):
416 """
417 Private slot called, when an editor was closed.
418
419 @param editor reference to the editor (QScintilla.Editor)
420 """
421 with contextlib.suppress(ValueError):
422 self.__editors.remove(editor)
423
424 def __editorShowMenu(self, menuName, menu, editor):
425 """
426 Private slot called, when the the editor context menu or a submenu is
427 about to be shown.
428
429 @param menuName name of the menu to be shown (string)
430 @param menu reference to the menu (QMenu)
431 @param editor reference to the editor
432 """
433 if menuName == "Checks":
434 if self.__editorAct not in menu.actions():
435 menu.addAction(self.__editorAct)
436 self.__editorAct.setEnabled(editor.isPyFile())
437
438 def __editorCodeStyleCheck(self):
439 """
440 Private slot to handle the code style check context menu action
441 of the editors.
442 """
443 editor = e5App().getObject("ViewManager").activeWindow()
444 if (
445 editor is not None and
446 editor.checkDirty() and
447 editor.getFileName() is not None
448 ):
449 from CheckerPlugins.CodeStyleChecker import (
450 CodeStyleCheckerDialog
451 )
452 self.__editorCodeStyleCheckerDialog = (
453 CodeStyleCheckerDialog.CodeStyleCheckerDialog(self)
454 )
455 self.__editorCodeStyleCheckerDialog.show()
456 self.__editorCodeStyleCheckerDialog.start(
457 editor.getFileName(),
458 save=True,
459 repeat=True)

eric ide

mercurial