Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py

branch
BgService
changeset 3209
c5432abceb25
parent 3145
a9de05d4a22f
child 3228
f489068e51e8
equal deleted inserted replaced
3177:5af61402d74d 3209:c5432abceb25
23 import UI.PixmapCache 23 import UI.PixmapCache
24 import Preferences 24 import Preferences
25 import Utilities 25 import Utilities
26 26
27 from . import pep8 27 from . import pep8
28 from .NamingStyleChecker import NamingStyleChecker 28 #from .NamingStyleChecker import NamingStyleChecker
29 29 #
30 # register the name checker 30 ## register the name checker
31 pep8.register_check(NamingStyleChecker, NamingStyleChecker.Codes) 31 #pep8.register_check(NamingStyleChecker, NamingStyleChecker.Codes)
32
33 from .DocStyleChecker import DocStyleChecker
34
35
36 class CodeStyleCheckerReport(pep8.BaseReport):
37 """
38 Class implementing a special report to be used with our dialog.
39 """
40 def __init__(self, options):
41 """
42 Constructor
43
44 @param options options for the report (optparse.Values)
45 """
46 super(CodeStyleCheckerReport, self).__init__(options)
47
48 self.__repeat = options.repeat
49 self.errors = []
50
51 def error_args(self, line_number, offset, code, check, *args):
52 """
53 Public method to collect the error messages.
54
55 @param line_number line number of the issue (integer)
56 @param offset position within line of the issue (integer)
57 @param code message code (string)
58 @param check reference to the checker function (function)
59 @param args arguments for the message (list)
60 @return error code (string)
61 """
62 code = super(CodeStyleCheckerReport, self).error_args(
63 line_number, offset, code, check, *args)
64 if code and (self.counters[code] == 1 or self.__repeat):
65 if code in NamingStyleChecker.Codes:
66 text = NamingStyleChecker.getMessage(code, *args)
67 else:
68 text = pep8.getMessage(code, *args)
69 self.errors.append(
70 (self.filename, line_number, offset, text)
71 )
72 return code
73 32
74 33
75 class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog): 34 class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog):
76 """ 35 """
77 Class implementing a dialog to show the results of the code style check. 36 Class implementing a dialog to show the results of the code style check.
112 self.resultList.header().setSortIndicator(0, Qt.AscendingOrder) 71 self.resultList.header().setSortIndicator(0, Qt.AscendingOrder)
113 72
114 self.checkProgress.setVisible(False) 73 self.checkProgress.setVisible(False)
115 self.checkProgressLabel.setVisible(False) 74 self.checkProgressLabel.setVisible(False)
116 self.checkProgressLabel.setMaximumWidth(600) 75 self.checkProgressLabel.setMaximumWidth(600)
76
77 self.internalServices = e5App().getObject('InternalServices')
78 self.internalServices.styleChecked.connect(self.__processResult)
117 79
118 self.noResults = True 80 self.noResults = True
119 self.cancelled = False 81 self.cancelled = False
120 self.__lastFileItem = None 82 self.__lastFileItem = None
121 83
200 @param itm reference to the item to modify (QTreeWidgetItem) 162 @param itm reference to the item to modify (QTreeWidgetItem)
201 @param text text to be appended (string) 163 @param text text to be appended (string)
202 @param fixed flag indicating a fixed issue (boolean) 164 @param fixed flag indicating a fixed issue (boolean)
203 """ 165 """
204 if fixed: 166 if fixed:
205 message = itm.data(0, self.messageRole) + text 167 code, message = text.split(None, 1)
206 itm.setText(2, message) 168 itm.setText(2, message)
207 itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed.png")) 169 itm.setIcon(0, UI.PixmapCache.getIcon("issueFixed.png"))
208 170
209 itm.setData(0, self.messageRole, message) 171 itm.setData(0, self.messageRole, message)
210 else: 172 else:
226 for key in statistics: 188 for key in statistics:
227 if key in self.__statistics: 189 if key in self.__statistics:
228 self.__statistics[key] += statistics[key] 190 self.__statistics[key] += statistics[key]
229 else: 191 else:
230 self.__statistics[key] = statistics[key] 192 self.__statistics[key] = statistics[key]
231 if fixer: 193 self.__statistics["_IssuesFixed"] += fixer
232 self.__statistics["_IssuesFixed"] += fixer.fixed
233 194
234 def __updateFixerStatistics(self, fixer): 195 def __updateFixerStatistics(self, fixer):
235 """ 196 """
236 Private method to update the collected fixer related statistics. 197 Private method to update the collected fixer related statistics.
237 198
238 @param fixer reference to the code style fixer (CodeStyleFixer) 199 @param fixer reference to the code style fixer (CodeStyleFixer)
239 """ 200 """
240 self.__statistics["_IssuesFixed"] += fixer.fixed 201 self.__statistics["_IssuesFixed"] += fixer
241 202
242 def __resetStatistics(self): 203 def __resetStatistics(self):
243 """ 204 """
244 Private slot to reset the statistics data. 205 Private slot to reset the statistics data.
245 """ 206 """
322 self.repeatCheckBox.setChecked(repeat) 283 self.repeatCheckBox.setChecked(repeat)
323 self.checkProgress.setVisible(True) 284 self.checkProgress.setVisible(True)
324 QApplication.processEvents() 285 QApplication.processEvents()
325 286
326 self.__resetStatistics() 287 self.__resetStatistics()
327 self.__clearErrors()
328 288
329 if save: 289 if save:
330 self.__fileOrFileList = fn 290 self.__fileOrFileList = fn
331 291
332 if isinstance(fn, list): 292 if isinstance(fn, list):
333 files = fn[:] 293 self.files = fn[:]
334 elif os.path.isdir(fn): 294 elif os.path.isdir(fn):
335 files = [] 295 self.files = []
336 extensions = set(Preferences.getPython("PythonExtensions") + 296 extensions = set(Preferences.getPython("PythonExtensions") +
337 Preferences.getPython("Python3Extensions")) 297 Preferences.getPython("Python3Extensions"))
338 for ext in extensions: 298 for ext in extensions:
339 files.extend(Utilities.direntries( 299 self.files.extend(Utilities.direntries(
340 fn, True, '*{0}'.format(ext), 0)) 300 fn, True, '*{0}'.format(ext), 0))
341 else: 301 else:
342 files = [fn] 302 self.files = [fn]
343 303
344 # filter the list depending on the filter string 304 # filter the list depending on the filter string
345 if files: 305 if self.files:
346 filterString = self.excludeFilesEdit.text() 306 filterString = self.excludeFilesEdit.text()
347 filterList = [f.strip() for f in filterString.split(",") 307 filterList = [f.strip() for f in filterString.split(",")
348 if f.strip()] 308 if f.strip()]
349 for filter in filterList: 309 for filter in filterList:
350 files = \ 310 self.files = \
351 [f for f in files 311 [f for f in self.files
352 if not fnmatch.fnmatch(f, filter.strip())] 312 if not fnmatch.fnmatch(f, filter.strip())]
353 313
354 py3files = [f for f in files 314 self.__clearErrors()
355 if f.endswith( 315
356 tuple(Preferences.getPython("Python3Extensions")))] 316 if len(self.files) > 0:
357 py2files = [f for f in files 317 self.checkProgress.setMaximum(len(self.files))
358 if f.endswith( 318 self.checkProgressLabel.setVisible(len(self.files) > 1)
359 tuple(Preferences.getPython("PythonExtensions")))] 319 self.checkProgress.setVisible(len(self.files) > 1)
360
361 if len(py3files) + len(py2files) > 0:
362 self.checkProgress.setMaximum(len(py3files) + len(py2files))
363 self.checkProgressLabel.setVisible(
364 len(py3files) + len(py2files) > 1)
365 self.checkProgress.setVisible(
366 len(py3files) + len(py2files) > 1)
367 QApplication.processEvents() 320 QApplication.processEvents()
368 321
369 # extract the configuration values 322 # extract the configuration values
370 excludeMessages = self.excludeMessagesEdit.text() 323 excludeMessages = self.excludeMessagesEdit.text()
371 includeMessages = self.includeMessagesEdit.text() 324 includeMessages = self.includeMessagesEdit.text()
376 maxLineLength = self.lineLengthSpinBox.value() 329 maxLineLength = self.lineLengthSpinBox.value()
377 hangClosing = self.hangClosingCheckBox.isChecked() 330 hangClosing = self.hangClosingCheckBox.isChecked()
378 docType = self.docTypeComboBox.itemData( 331 docType = self.docTypeComboBox.itemData(
379 self.docTypeComboBox.currentIndex()) 332 self.docTypeComboBox.currentIndex())
380 333
381 try: 334 self.__options = [excludeMessages, includeMessages, repeatMessages,
382 # disable updates of the list for speed 335 fixCodes, noFixCodes, fixIssues, maxLineLength,
383 self.resultList.setUpdatesEnabled(False) 336 hangClosing, docType]
384 self.resultList.setSortingEnabled(False) 337
385 338 # now go through all the files
386 # now go through all the files 339 self.progress = 0
387 progress = 0 340 self.files.sort()
388 for file in sorted(py3files + py2files): 341 self.check()
389 self.checkProgress.setValue(progress) 342
390 self.checkProgressLabel.setPath(file) 343 def check(self, codestring='', onlyFixes={}):
391 QApplication.processEvents() 344 """
392 345 Start a style check for one file.
393 if self.cancelled: 346
394 self.__resort() 347 The results are reported to the __processResult slot.
395 return 348 @keyparam codestring optional sourcestring (str)
396 349 @keyparam onlyFixes dict which violations should be fixed (dict)
397 self.__lastFileItem = None 350 """
398 351 if not self.files:
399 try:
400 source, encoding = Utilities.readEncodedFile(file)
401 source = source.splitlines(True)
402 except (UnicodeError, IOError) as msg:
403 self.noResults = False
404 self.__createResultItem(
405 file, 1, 1,
406 self.trUtf8("Error: {0}").format(str(msg))
407 .rstrip()[1:-1], False, False)
408 progress += 1
409 continue
410
411 stats = {}
412 flags = Utilities.extractFlags(source)
413 ext = os.path.splitext(file)[1]
414 if fixIssues:
415 from .CodeStyleFixer import CodeStyleFixer
416 fixer = CodeStyleFixer(
417 self.__project, file, source, fixCodes, noFixCodes,
418 maxLineLength, True) # always fix in place
419 else:
420 fixer = None
421 if ("FileType" in flags and
422 flags["FileType"] in ["Python", "Python2"]) or \
423 file in py2files or \
424 (ext in [".py", ".pyw"] and
425 Preferences.getProject("DeterminePyFromProject") and
426 self.__project.isOpen() and
427 self.__project.isProjectFile(file) and
428 self.__project.getProjectLanguage() in ["Python",
429 "Python2"]):
430 from .CodeStyleChecker import CodeStyleCheckerPy2
431 report = CodeStyleCheckerPy2(
432 file, [],
433 repeat=repeatMessages,
434 select=includeMessages,
435 ignore=excludeMessages,
436 max_line_length=maxLineLength,
437 hang_closing=hangClosing,
438 docType=docType,
439 )
440 errors = report.errors[:]
441 stats.update(report.counters)
442 else:
443 if includeMessages:
444 select = [s.strip() for s in
445 includeMessages.split(',') if s.strip()]
446 else:
447 select = []
448 if excludeMessages:
449 ignore = [i.strip() for i in
450 excludeMessages.split(',') if i.strip()]
451 else:
452 ignore = []
453
454 # check coding style
455 styleGuide = pep8.StyleGuide(
456 reporter=CodeStyleCheckerReport,
457 repeat=repeatMessages,
458 select=select,
459 ignore=ignore,
460 max_line_length=maxLineLength,
461 hang_closing=hangClosing,
462 )
463 report = styleGuide.check_files([file])
464 stats.update(report.counters)
465
466 # check documentation style
467 docStyleChecker = DocStyleChecker(
468 source, file, select, ignore, [], repeatMessages,
469 maxLineLength=maxLineLength, docType=docType)
470 docStyleChecker.run()
471 stats.update(docStyleChecker.counters)
472
473 errors = report.errors + docStyleChecker.errors
474
475 deferredFixes = {}
476 for error in errors:
477 fname, lineno, position, text = error
478 if lineno > len(source):
479 lineno = len(source)
480 if "__IGNORE_WARNING__" not in \
481 Utilities.extractLineFlags(
482 source[lineno - 1].strip()):
483 self.noResults = False
484 if fixer:
485 res, msg, id_ = fixer.fixIssue(lineno,
486 position, text)
487 if res == 1:
488 text += "\n" + \
489 self.trUtf8("Fix: {0}").format(msg)
490 self.__createResultItem(
491 fname, lineno, position, text, True,
492 True)
493 elif res == 0:
494 self.__createResultItem(
495 fname, lineno, position, text, False,
496 True)
497 else:
498 itm = self.__createResultItem(
499 fname, lineno, position,
500 text, False, False)
501 deferredFixes[id_] = itm
502 else:
503 self.__createResultItem(
504 fname, lineno, position, text, False,
505 False)
506 if fixer:
507 deferredResults = fixer.finalize()
508 for id_ in deferredResults:
509 fixed, msg = deferredResults[id_]
510 itm = deferredFixes[id_]
511 if fixed == 1:
512 text = "\n" + \
513 self.trUtf8("Fix: {0}").format(msg)
514 self.__modifyFixedResultItem(itm, text, True)
515 else:
516 self.__modifyFixedResultItem(itm, "", False)
517 fixer.saveFile(encoding)
518 self.__updateStatistics(stats, fixer)
519 progress += 1
520 finally:
521 # reenable updates of the list
522 self.resultList.setSortingEnabled(True)
523 self.resultList.setUpdatesEnabled(True)
524 self.checkProgress.setValue(progress)
525 self.checkProgressLabel.setPath("") 352 self.checkProgressLabel.setPath("")
526 QApplication.processEvents()
527 self.__resort()
528 else:
529 self.checkProgress.setMaximum(1) 353 self.checkProgress.setMaximum(1)
530 self.checkProgress.setValue(1) 354 self.checkProgress.setValue(1)
531 self.__finish() 355 self.__finish()
356 return
357
358 self.filename = self.files.pop(0)
359 self.checkProgress.setValue(self.progress)
360 self.checkProgressLabel.setPath(self.filename)
361 QApplication.processEvents()
362
363 if self.cancelled:
364 self.__resort()
365 return
366
367 self.__lastFileItem = None
368
369 if codestring:
370 self.source = codestring
371 else:
372 try:
373 self.source, encoding = Utilities.readEncodedFile(
374 self.filename)
375 if encoding.endswith(
376 ('-selected', '-default', '-guessed', '-ignore')):
377 encoding = encoding.rsplit('-', 1)[0]
378
379 self.source = self.source.splitlines(True)
380 except (UnicodeError, IOError) as msg:
381 self.noResults = False
382 self.__createResultItem(
383 self.filename, 1, 1,
384 self.trUtf8("Error: {0}").format(str(msg))
385 .rstrip()[1:-1], False, False)
386 self.progress += 1
387 # Continue with next file
388 self.check()
389 return
390
391 errors = []
392 self.__itms = []
393 for error, itm in onlyFixes.get(self.filename, []):
394 errors.append(error)
395 self.__itms.append(itm)
396
397 eol = self.__getEol(self.filename)
398 args = self.__options + [errors, eol, encoding]
399 self.internalServices.styleCheck(
400 self.filename, self.source, args)
401
402 def __processResult(self, fn, codeStyleCheckerStats, fixes, results):
403 """
404 Privat slot called after perfoming a style check on one file.
405
406 @param fn filename of the just checked file (str)
407 @param codeStyleCheckerStats stats of style and name check (dict)
408 @param fixes number of applied fixes (int)
409 @param results tuple for each found violation of style (tuple of
410 lineno (int), position (int), text (str), fixed (bool),
411 autofixing (bool))
412 """
413 # Check if it's the requested file, otherwise ignore signal
414 if fn != self.filename:
415 return
416
417 # disable updates of the list for speed
418 self.resultList.setUpdatesEnabled(False)
419 self.resultList.setSortingEnabled(False)
420
421 fixed = None
422 if self.__itms:
423 for itm, (lineno, position, text, fixed, autofixing) in zip(
424 self.__itms, results):
425 self.__modifyFixedResultItem(itm, text, fixed)
426 self.__updateFixerStatistics(fixes)
427 else:
428 for lineno, position, text, fixed, autofixing in results:
429 self.noResults = False
430 self.__createResultItem(
431 fn, lineno, position, text, fixed, autofixing)
432
433 self.__updateStatistics(codeStyleCheckerStats, fixes)
434
435 if fixed:
436 vm = e5App().getObject("ViewManager")
437 editor = vm.getOpenEditor(fn)
438 if editor:
439 editor.refresh()
440
441 self.progress += 1
442
443 self.__resort()
444 # reenable updates of the list
445 self.resultList.setSortingEnabled(True)
446 self.resultList.setUpdatesEnabled(True)
447
448 self.checkProgress.setValue(self.progress)
449 QApplication.processEvents()
450
451 self.check()
532 452
533 def __finish(self): 453 def __finish(self):
534 """ 454 """
535 Private slot called when the code style check finished or the user 455 Private slot called when the code style check finished or the user
536 pressed the cancel button. 456 pressed the cancel button.
555 self.resultList.header().resizeSections(QHeaderView.ResizeToContents) 475 self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
556 self.resultList.header().setStretchLastSection(True) 476 self.resultList.header().setStretchLastSection(True)
557 477
558 self.checkProgress.setVisible(False) 478 self.checkProgress.setVisible(False)
559 self.checkProgressLabel.setVisible(False) 479 self.checkProgressLabel.setVisible(False)
480
481 def __getEol(self, fn):
482 """
483 Private method to get the applicable eol string.
484
485 @param fn filename where to determine the line ending (str)
486 @return eol string (string)
487 """
488 if self.__project.isOpen() and self.__project.isProjectFile(fn):
489 eol = self.__project.getEolString()
490 else:
491 eol = Utilities.linesep()
492 return eol
560 493
561 @pyqtSlot() 494 @pyqtSlot()
562 def on_startButton_clicked(self): 495 def on_startButton_clicked(self):
563 """ 496 """
564 Private slot to start a code style check run. 497 Private slot to start a code style check run.
815 748
816 @pyqtSlot() 749 @pyqtSlot()
817 def on_fixButton_clicked(self): 750 def on_fixButton_clicked(self):
818 """ 751 """
819 Private slot to fix selected issues. 752 Private slot to fix selected issues.
820 """ 753
821 from .CodeStyleFixer import CodeStyleFixer 754 Build a dictionary of issues to fix. Update the initialized __options.
822 755 Then call check with the dict as keyparam to fix selected issues.
823 # build a dictionary of issues to fix 756 """
824 fixableItems = self.__getSelectedFixableItems() 757 fixableItems = self.__getSelectedFixableItems()
825 fixesDict = {} # dictionary of lists of tuples containing 758 # dictionary of lists of tuples containing the issue and the item
826 # the issue and the item 759 fixesDict = {}
827 for itm in fixableItems: 760 for itm in fixableItems:
828 filename = itm.data(0, self.filenameRole) 761 filename = itm.data(0, self.filenameRole)
829 if filename not in fixesDict: 762 if filename not in fixesDict:
830 fixesDict[filename] = [] 763 fixesDict[filename] = []
831 fixesDict[filename].append(( 764 fixesDict[filename].append((
832 (itm.data(0, self.lineRole), 765 (filename, itm.data(0, self.lineRole),
833 itm.data(0, self.positionRole), 766 itm.data(0, self.positionRole),
834 "{0} {1}".format(itm.data(0, self.codeRole), 767 "{0} {1}".format(itm.data(0, self.codeRole),
835 itm.data(0, self.messageRole))), 768 itm.data(0, self.messageRole))),
836 itm 769 itm
837 )) 770 ))
838 771
839 # extract the configuration values 772 # update the configuration values (3: fixCodes, 4: noFixCodes,
840 fixCodes = self.fixIssuesEdit.text() 773 # 5: fixIssues, 6: maxLineLength)
841 noFixCodes = self.noFixIssuesEdit.text() 774 self.__options[3] = self.fixIssuesEdit.text()
842 maxLineLength = self.lineLengthSpinBox.value() 775 self.__options[4] = self.noFixIssuesEdit.text()
843 776 self.__options[5] = True
777 self.__options[6] = self.lineLengthSpinBox.value()
778
779 self.files = fixesDict.keys()
844 # now go through all the files 780 # now go through all the files
845 if fixesDict: 781 self.progress = 0
846 self.checkProgress.setMaximum(len(fixesDict)) 782 self.files.sort()
847 progress = 0 783 self.cancelled = False
848 for file in fixesDict: 784 self.check(onlyFixes=fixesDict)
849 self.checkProgress.setValue(progress) 785
850 QApplication.processEvents()
851
852 try:
853 source, encoding = Utilities.readEncodedFile(file)
854 source = source.splitlines(True)
855 except (UnicodeError, IOError) as msg:
856 # skip silently because that should not happen
857 progress += 1
858 continue
859
860 deferredFixes = {}
861 fixer = CodeStyleFixer(
862 self.__project, file, source, fixCodes, noFixCodes,
863 maxLineLength, True) # always fix in place
864 errors = fixesDict[file]
865 errors.sort(key=lambda a: a[0][0])
866 for error in errors:
867 (lineno, position, text), itm = error
868 if lineno > len(source):
869 lineno = len(source)
870 fixed, msg, id_ = fixer.fixIssue(lineno, position, text)
871 if fixed == 1:
872 text = "\n" + self.trUtf8("Fix: {0}").format(msg)
873 self.__modifyFixedResultItem(itm, text, True)
874 elif fixed == 0:
875 self.__modifyFixedResultItem(itm, "", False)
876 else:
877 # remember item for the deferred fix
878 deferredFixes[id_] = itm
879 deferredResults = fixer.finalize()
880 for id_ in deferredResults:
881 fixed, msg = deferredResults[id_]
882 itm = deferredFixes[id_]
883 if fixed == 1:
884 text = "\n" + self.trUtf8("Fix: {0}").format(msg)
885 self.__modifyFixedResultItem(itm, text, True)
886 else:
887 self.__modifyFixedResultItem(itm, "", False)
888 fixer.saveFile(encoding)
889
890 self.__updateFixerStatistics(fixer)
891 progress += 1
892
893 self.checkProgress.setValue(progress)
894 QApplication.processEvents()
895
896 def __getSelectedFixableItems(self): 786 def __getSelectedFixableItems(self):
897 """ 787 """
898 Private method to extract all selected items for fixable issues. 788 Private method to extract all selected items for fixable issues.
899 789
900 @return selected items for fixable issues (list of QTreeWidgetItem) 790 @return selected items for fixable issues (list of QTreeWidgetItem)

eric ide

mercurial