eric6/Plugins/PluginCodeStyleChecker.py

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

eric ide

mercurial