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. |
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. |
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) |