325 |
285 |
326 if save: |
286 if save: |
327 self.__fileOrFileList = fn |
287 self.__fileOrFileList = fn |
328 |
288 |
329 if isinstance(fn, list): |
289 if isinstance(fn, list): |
330 files = fn[:] |
290 self.files = fn[:] |
331 elif os.path.isdir(fn): |
291 elif os.path.isdir(fn): |
332 files = [] |
292 self.files = [] |
333 extensions = set(Preferences.getPython("PythonExtensions") + |
293 extensions = set(Preferences.getPython("PythonExtensions") + |
334 Preferences.getPython("Python3Extensions")) |
294 Preferences.getPython("Python3Extensions")) |
335 for ext in extensions: |
295 for ext in extensions: |
336 files.extend(Utilities.direntries( |
296 self.files.extend(Utilities.direntries( |
337 fn, True, '*{0}'.format(ext), 0)) |
297 fn, True, '*{0}'.format(ext), 0)) |
338 else: |
298 else: |
339 files = [fn] |
299 self.files = [fn] |
340 |
300 |
341 # filter the list depending on the filter string |
301 # filter the list depending on the filter string |
342 if files: |
302 if self.files: |
343 filterString = self.excludeFilesEdit.text() |
303 filterString = self.excludeFilesEdit.text() |
344 filterList = [f.strip() for f in filterString.split(",") |
304 filterList = [f.strip() for f in filterString.split(",") |
345 if f.strip()] |
305 if f.strip()] |
346 for filter in filterList: |
306 for filter in filterList: |
347 files = \ |
307 self.files = \ |
348 [f for f in files |
308 [f for f in self.files |
349 if not fnmatch.fnmatch(f, filter.strip())] |
309 if not fnmatch.fnmatch(f, filter.strip())] |
350 |
310 |
351 self.__resetStatistics() |
311 self.__resetStatistics() |
352 self.__clearErrors(files) |
312 self.__clearErrors(self.files) |
353 |
313 |
354 py3files = [f for f in files |
314 if len(self.files) > 0: |
355 if f.endswith( |
315 self.checkProgress.setMaximum(len(self.files)) |
356 tuple(Preferences.getPython("Python3Extensions")))] |
316 self.checkProgressLabel.setVisible(len(self.files) > 1) |
357 py2files = [f for f in files |
317 self.checkProgress.setVisible(len(self.files) > 1) |
358 if f.endswith( |
|
359 tuple(Preferences.getPython("PythonExtensions")))] |
|
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() |
318 QApplication.processEvents() |
368 |
319 |
369 # extract the configuration values |
320 # extract the configuration values |
370 excludeMessages = self.excludeMessagesEdit.text() |
321 excludeMessages = self.excludeMessagesEdit.text() |
371 includeMessages = self.includeMessagesEdit.text() |
322 includeMessages = self.includeMessagesEdit.text() |
376 maxLineLength = self.lineLengthSpinBox.value() |
327 maxLineLength = self.lineLengthSpinBox.value() |
377 hangClosing = self.hangClosingCheckBox.isChecked() |
328 hangClosing = self.hangClosingCheckBox.isChecked() |
378 docType = self.docTypeComboBox.itemData( |
329 docType = self.docTypeComboBox.itemData( |
379 self.docTypeComboBox.currentIndex()) |
330 self.docTypeComboBox.currentIndex()) |
380 |
331 |
381 try: |
332 self.__options = [excludeMessages, includeMessages, repeatMessages, |
382 # disable updates of the list for speed |
333 fixCodes, noFixCodes, fixIssues, maxLineLength, |
383 self.resultList.setUpdatesEnabled(False) |
334 hangClosing, docType] |
384 self.resultList.setSortingEnabled(False) |
335 |
385 |
336 # now go through all the files |
386 # now go through all the files |
337 self.progress = 0 |
387 progress = 0 |
338 self.files.sort() |
388 for file in sorted(py3files + py2files): |
339 self.check() |
389 self.checkProgress.setValue(progress) |
340 |
390 self.checkProgressLabel.setPath(file) |
341 def check(self, codestring='', onlyFixes={}): |
391 QApplication.processEvents() |
342 """ |
392 |
343 Start a style check for one file. |
393 if self.cancelled: |
344 |
394 self.__resort() |
345 The results are reported to the __processResult slot. |
395 return |
346 @keyparam codestring optional sourcestring (str) |
396 |
347 @keyparam onlyFixes dict which violations should be fixed (dict) |
397 self.__lastFileItem = None |
348 """ |
398 |
349 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("") |
350 self.checkProgressLabel.setPath("") |
526 QApplication.processEvents() |
|
527 self.__resort() |
|
528 else: |
|
529 self.checkProgress.setMaximum(1) |
351 self.checkProgress.setMaximum(1) |
530 self.checkProgress.setValue(1) |
352 self.checkProgress.setValue(1) |
531 self.__finish() |
353 self.__finish() |
|
354 return |
|
355 |
|
356 self.filename = self.files.pop(0) |
|
357 self.checkProgress.setValue(self.progress) |
|
358 self.checkProgressLabel.setPath(self.filename) |
|
359 QApplication.processEvents() |
|
360 |
|
361 if self.cancelled: |
|
362 self.__resort() |
|
363 return |
|
364 |
|
365 self.__lastFileItem = None |
|
366 |
|
367 if codestring: |
|
368 self.source = codestring |
|
369 else: |
|
370 try: |
|
371 self.source, encoding = Utilities.readEncodedFile( |
|
372 self.filename) |
|
373 if encoding.endswith( |
|
374 ('-selected', '-default', '-guessed', '-ignore')): |
|
375 encoding = encoding.rsplit('-', 1)[0] |
|
376 |
|
377 self.source = self.source.splitlines(True) |
|
378 except (UnicodeError, IOError) as msg: |
|
379 self.noResults = False |
|
380 self.__createResultItem( |
|
381 self.filename, 1, 1, |
|
382 self.trUtf8("Error: {0}").format(str(msg)) |
|
383 .rstrip()[1:-1], False, False) |
|
384 self.progress += 1 |
|
385 # Continue with next file |
|
386 self.check() |
|
387 return |
|
388 |
|
389 errors = [] |
|
390 self.__itms = [] |
|
391 for error, itm in onlyFixes.get(self.filename, []): |
|
392 errors.append(error) |
|
393 self.__itms.append(itm) |
|
394 |
|
395 eol = self.__getEol(self.filename) |
|
396 args = self.__options + [errors, eol, encoding] |
|
397 self.styleCheckService.styleCheck( |
|
398 None, self.filename, self.source, args) |
|
399 |
|
400 def __processResult(self, fn, codeStyleCheckerStats, fixes, results): |
|
401 """ |
|
402 Privat slot called after perfoming a style check on one file. |
|
403 |
|
404 @param fn filename of the just checked file (str) |
|
405 @param codeStyleCheckerStats stats of style and name check (dict) |
|
406 @param fixes number of applied fixes (int) |
|
407 @param results tuple for each found violation of style (tuple of |
|
408 lineno (int), position (int), text (str), fixed (bool), |
|
409 autofixing (bool)) |
|
410 """ |
|
411 # Check if it's the requested file, otherwise ignore signal |
|
412 if fn != self.filename: |
|
413 return |
|
414 |
|
415 # disable updates of the list for speed |
|
416 self.resultList.setUpdatesEnabled(False) |
|
417 self.resultList.setSortingEnabled(False) |
|
418 |
|
419 fixed = None |
|
420 if self.__itms: |
|
421 for itm, (lineno, position, text, fixed, autofixing) in zip( |
|
422 self.__itms, results): |
|
423 self.__modifyFixedResultItem(itm, text, fixed) |
|
424 self.__updateFixerStatistics(fixes) |
|
425 else: |
|
426 for lineno, position, text, fixed, autofixing in results: |
|
427 self.noResults = False |
|
428 self.__createResultItem( |
|
429 fn, lineno, position, text, fixed, autofixing) |
|
430 |
|
431 self.__updateStatistics(codeStyleCheckerStats, fixes) |
|
432 |
|
433 if fixed: |
|
434 vm = e5App().getObject("ViewManager") |
|
435 editor = vm.getOpenEditor(fn) |
|
436 if editor: |
|
437 editor.refresh() |
|
438 |
|
439 self.progress += 1 |
|
440 |
|
441 self.__resort() |
|
442 # reenable updates of the list |
|
443 self.resultList.setSortingEnabled(True) |
|
444 self.resultList.setUpdatesEnabled(True) |
|
445 |
|
446 self.checkProgress.setValue(self.progress) |
|
447 QApplication.processEvents() |
|
448 |
|
449 self.check() |
532 |
450 |
533 def __finish(self): |
451 def __finish(self): |
534 """ |
452 """ |
535 Private slot called when the code style check finished or the user |
453 Private slot called when the code style check finished or the user |
536 pressed the cancel button. |
454 pressed the cancel button. |
817 |
748 |
818 @pyqtSlot() |
749 @pyqtSlot() |
819 def on_fixButton_clicked(self): |
750 def on_fixButton_clicked(self): |
820 """ |
751 """ |
821 Private slot to fix selected issues. |
752 Private slot to fix selected issues. |
822 """ |
753 |
823 from .CodeStyleFixer import CodeStyleFixer |
754 Build a dictionary of issues to fix. Update the initialized __options. |
824 |
755 Then call check with the dict as keyparam to fix selected issues. |
825 # build a dictionary of issues to fix |
756 """ |
826 fixableItems = self.__getSelectedFixableItems() |
757 fixableItems = self.__getSelectedFixableItems() |
827 fixesDict = {} # dictionary of lists of tuples containing |
758 # dictionary of lists of tuples containing the issue and the item |
828 # the issue and the item |
759 fixesDict = {} |
829 for itm in fixableItems: |
760 for itm in fixableItems: |
830 filename = itm.data(0, self.filenameRole) |
761 filename = itm.data(0, self.filenameRole) |
831 if filename not in fixesDict: |
762 if filename not in fixesDict: |
832 fixesDict[filename] = [] |
763 fixesDict[filename] = [] |
833 fixesDict[filename].append(( |
764 fixesDict[filename].append(( |
834 (itm.data(0, self.lineRole), |
765 (filename, itm.data(0, self.lineRole), |
835 itm.data(0, self.positionRole), |
766 itm.data(0, self.positionRole), |
836 "{0} {1}".format(itm.data(0, self.codeRole), |
767 "{0} {1}".format(itm.data(0, self.codeRole), |
837 itm.data(0, self.messageRole))), |
768 itm.data(0, self.messageRole))), |
838 itm |
769 itm |
839 )) |
770 )) |
840 |
771 |
841 # extract the configuration values |
772 # update the configuration values (3: fixCodes, 4: noFixCodes, |
842 fixCodes = self.fixIssuesEdit.text() |
773 # 5: fixIssues, 6: maxLineLength) |
843 noFixCodes = self.noFixIssuesEdit.text() |
774 self.__options[3] = self.fixIssuesEdit.text() |
844 maxLineLength = self.lineLengthSpinBox.value() |
775 self.__options[4] = self.noFixIssuesEdit.text() |
845 |
776 self.__options[5] = True |
|
777 self.__options[6] = self.lineLengthSpinBox.value() |
|
778 |
|
779 self.files = list(fixesDict.keys()) |
846 # now go through all the files |
780 # now go through all the files |
847 if fixesDict: |
781 self.progress = 0 |
848 self.checkProgress.setMaximum(len(fixesDict)) |
782 self.files.sort() |
849 progress = 0 |
783 self.cancelled = False |
850 for file in fixesDict: |
784 self.check(onlyFixes=fixesDict) |
851 self.checkProgress.setValue(progress) |
785 |
852 QApplication.processEvents() |
|
853 |
|
854 try: |
|
855 source, encoding = Utilities.readEncodedFile(file) |
|
856 source = source.splitlines(True) |
|
857 except (UnicodeError, IOError) as msg: |
|
858 # skip silently because that should not happen |
|
859 progress += 1 |
|
860 continue |
|
861 |
|
862 deferredFixes = {} |
|
863 fixer = CodeStyleFixer( |
|
864 self.__project, file, source, fixCodes, noFixCodes, |
|
865 maxLineLength, True) # always fix in place |
|
866 errors = fixesDict[file] |
|
867 errors.sort(key=lambda a: a[0][0]) |
|
868 for error in errors: |
|
869 (lineno, position, text), itm = error |
|
870 if lineno > len(source): |
|
871 lineno = len(source) |
|
872 fixed, msg, id_ = fixer.fixIssue(lineno, position, text) |
|
873 if fixed == 1: |
|
874 text = "\n" + self.trUtf8("Fix: {0}").format(msg) |
|
875 self.__modifyFixedResultItem(itm, text, True) |
|
876 elif fixed == 0: |
|
877 self.__modifyFixedResultItem(itm, "", False) |
|
878 else: |
|
879 # remember item for the deferred fix |
|
880 deferredFixes[id_] = itm |
|
881 deferredResults = fixer.finalize() |
|
882 for id_ in deferredResults: |
|
883 fixed, msg = deferredResults[id_] |
|
884 itm = deferredFixes[id_] |
|
885 if fixed == 1: |
|
886 text = "\n" + self.trUtf8("Fix: {0}").format(msg) |
|
887 self.__modifyFixedResultItem(itm, text, True) |
|
888 else: |
|
889 self.__modifyFixedResultItem(itm, "", False) |
|
890 fixer.saveFile(encoding) |
|
891 |
|
892 self.__updateFixerStatistics(fixer) |
|
893 progress += 1 |
|
894 |
|
895 self.checkProgress.setValue(progress) |
|
896 QApplication.processEvents() |
|
897 |
|
898 def __getSelectedFixableItems(self): |
786 def __getSelectedFixableItems(self): |
899 """ |
787 """ |
900 Private method to extract all selected items for fixable issues. |
788 Private method to extract all selected items for fixable issues. |
901 |
789 |
902 @return selected items for fixable issues (list of QTreeWidgetItem) |
790 @return selected items for fixable issues (list of QTreeWidgetItem) |