diff --git a/phpcs.py b/phpcs.py index 2898ca4..94ff274 100644 --- a/phpcs.py +++ b/phpcs.py @@ -14,8 +14,8 @@ from html.parser import HTMLParser from os.path import expanduser -class Pref: +class Pref: project_file = None keys = [ @@ -54,18 +54,21 @@ class Pref: "scheck_run", "scheck_command_on_save", "scheck_executable_path", - "scheck_additional_args" + "scheck_additional_args", ] def load(self): - self.settings = sublime.load_settings('phpcs.sublime-settings') + self.settings = sublime.load_settings("phpcs.sublime-settings") - if sublime.active_window() is not None and sublime.active_window().active_view() is not None: + if ( + sublime.active_window() is not None + and sublime.active_window().active_view() is not None + ): project_settings = sublime.active_window().active_view().settings() if project_settings.has("phpcs"): - project_settings.clear_on_change('phpcs') - self.project_settings = project_settings.get('phpcs') - project_settings.add_on_change('phpcs', pref.load) + project_settings.clear_on_change("phpcs") + self.project_settings = project_settings.get("phpcs") + project_settings.add_on_change("phpcs", pref.load) else: self.project_settings = {} else: @@ -92,22 +95,25 @@ def set_setting(self, key, value): pref = Pref() st_version = 2 -if sublime.version() == '' or int(sublime.version()) > 3000: +if sublime.version() == "" or int(sublime.version()) > 3000: st_version = 3 if st_version == 2: pref.load() + def plugin_loaded(): pref.load() + def debug_message(msg): if pref.show_debug == True: print("[Phpcs] " + str(msg)) -class CheckstyleError(): +class CheckstyleError: """Represents an error that needs to be displayed on the UI for the user""" + def __init__(self, line, message): self.line = line self.message = message @@ -122,9 +128,14 @@ def get_message(self): return HTMLParser().unescape(data) else: try: - data = data.decode('utf-8') + data = data.decode("utf-8") except UnicodeDecodeError: - data = data.decode(sublime.active_window().active_view().settings().get('fallback_encoding')) + data = data.decode( + sublime.active_window() + .active_view() + .settings() + .get("fallback_encoding") + ) return HTMLParser().unescape(data) def set_point(self, point): @@ -134,8 +145,9 @@ def get_point(self): return self.point -class ShellCommand(): +class ShellCommand: """Base class for shelling out a command to the terminal""" + def __init__(self): self.error_list = [] @@ -151,30 +163,36 @@ def get_errors(self, path): def shell_out(self, cmd): data = None - + for i, arg in enumerate(cmd): - if isinstance(arg, str) and arg.startswith('~'): + if isinstance(arg, str) and arg.startswith("~"): cmd[i] = os.path.expanduser(arg) if st_version == 3: - debug_message(' '.join(cmd)) + debug_message(" ".join(cmd)) else: for index, arg in enumerate(cmd[:]): cmd[index] = arg.encode(sys.getfilesystemencoding()) - debug_message(' '.join(cmd)) + debug_message(" ".join(cmd)) - debug_message(' '.join(cmd)) + debug_message(" ".join(cmd)) info = None - if os.name == 'nt': + if os.name == "nt": info = subprocess.STARTUPINFO() info.dwFlags |= subprocess.STARTF_USESHOWWINDOW info.wShowWindow = subprocess.SW_HIDE debug_message("cwd: " + self.workingDir) - proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=info, cwd=self.workingDir) - + proc = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + startupinfo=info, + cwd=self.workingDir, + ) if proc.stdout: data = proc.communicate()[0] @@ -185,26 +203,30 @@ def shell_out(self, cmd): return data def execute(self, path): - debug_message('Command not implemented') + debug_message("Command not implemented") class Sniffer(ShellCommand): """Concrete class for PHP_CodeSniffer""" + def execute(self, path): if pref.phpcs_sniffer_run != True: return args = [] - if pref.phpcs_php_prefix_path != "" and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix: + if ( + pref.phpcs_php_prefix_path != "" + and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix + ): args = [pref.phpcs_php_prefix_path] if pref.phpcs_executable_path != "": application_path = pref.phpcs_executable_path else: - application_path = 'phpcs' + application_path = "phpcs" - if (len(args) > 0): + if len(args) > 0: args.append(application_path) else: args = [application_path] @@ -233,43 +255,54 @@ def execute(self, path): def parse_report(self, args): report = self.shell_out(args) debug_message(report) - lines = re.finditer('.*line="(?P\d+)" column="(?P\d+)" severity="(?P\w+)" message="(?P.*)" source', report) + lines = re.finditer( + '.*line="(?P\d+)" column="(?P\d+)" severity="(?P\w+)" message="(?P.*)" source', + report, + ) for line in lines: - error = CheckstyleError(line.group('line'), line.group('message')) + error = CheckstyleError(line.group("line"), line.group("message")) self.error_list.append(error) def get_standards_available(self): if pref.phpcs_executable_path != "": application_path = pref.phpcs_executable_path else: - application_path = 'phpcs' + application_path = "phpcs" args = [] args.append(application_path) - args.append('-i') + args.append("-i") output = self.shell_out(args) - standards = output[35:].replace('and', ',').strip().split(', ') + standards = output[35:].replace("and", ",").strip().split(", ") return standards + class Fixer(ShellCommand): """Concrete class for PHP-CS-Fixer""" - def execute(self, path): + def execute(self, path): args = [] - if pref.phpcs_php_prefix_path != "" and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix: + if ( + pref.phpcs_php_prefix_path != "" + and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix + ): args = [pref.phpcs_php_prefix_path] if pref.php_cs_fixer_executable_path != "": - if (len(args) > 0): + if len(args) > 0: args.append(pref.php_cs_fixer_executable_path) else: args = [pref.php_cs_fixer_executable_path] else: - debug_message("php_cs_fixer_executable_path is not set, therefore cannot execute") - sublime.error_message('The "php_cs_fixer_executable_path" is not set, therefore cannot execute this command') + debug_message( + "php_cs_fixer_executable_path is not set, therefore cannot execute" + ) + sublime.error_message( + 'The "php_cs_fixer_executable_path" is not set, therefore cannot execute this command' + ) return args.append("fix") @@ -288,29 +321,35 @@ def execute(self, path): def parse_report(self, args): report = self.shell_out(args) debug_message(report) - lines = re.finditer('.*(?P\d+)\) (?P.*)', report) + lines = re.finditer(".*(?P\d+)\) (?P.*)", report) for line in lines: - error = CheckstyleError(line.group('line'), line.group('file')) + error = CheckstyleError(line.group("line"), line.group("file")) self.error_list.append(error) + class CodeBeautifier(ShellCommand): """Concrete class for phpcbf""" - def execute(self, path): + def execute(self, path): args = [] - if pref.phpcs_php_prefix_path != "" and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix: + if ( + pref.phpcs_php_prefix_path != "" + and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix + ): args = [pref.phpcs_php_prefix_path] if pref.phpcbf_executable_path != "": - if (len(args) > 0): + if len(args) > 0: args.append(pref.phpcbf_executable_path) else: args = [pref.phpcbf_executable_path] else: debug_message("phpcbf_executable_path is not set, therefore cannot execute") - sublime.error_message('The "phpcbf_executable_path" is not set, therefore cannot execute this command') + sublime.error_message( + 'The "phpcbf_executable_path" is not set, therefore cannot execute this command' + ) return args.append(os.path.normpath(path)) @@ -327,35 +366,40 @@ def execute(self, path): def parse_report(self, args): report = self.shell_out(args) debug_message(report) - lines = re.finditer('.*\((?P\d+) fixable violations\)', report) + lines = re.finditer(".*\((?P\d+) fixable violations\)", report) for line in lines: - error = CheckstyleError(0, line.group('number') + " fixed violations") + error = CheckstyleError(0, line.group("number") + " fixed violations") self.error_list.append(error) + class MessDetector(ShellCommand): """Concrete class for PHP Mess Detector""" + def execute(self, path): if pref.phpmd_run != True: return args = [] - if pref.phpcs_php_prefix_path != "" and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix: + if ( + pref.phpcs_php_prefix_path != "" + and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix + ): args = [pref.phpcs_php_prefix_path] if pref.phpmd_executable_path != "": application_path = pref.phpmd_executable_path else: - application_path = 'phpmd' + application_path = "phpmd" - if (len(args) > 0): + if len(args) > 0: args.append(application_path) else: args = [application_path] args.append(os.path.normpath(path)) - args.append('text') + args.append("text") for key, value in pref.phpmd_additional_args.items(): arg = key @@ -368,30 +412,34 @@ def execute(self, path): def parse_report(self, args): report = self.shell_out(args) debug_message(report) - lines = re.finditer('.*:(?P\d+)[ \t]+(?P.*)', report) + lines = re.finditer(".*:(?P\d+)[ \t]+(?P.*)", report) for line in lines: - error = CheckstyleError(line.group('line'), line.group('message')) + error = CheckstyleError(line.group("line"), line.group("message")) self.error_list.append(error) class Scheck(ShellCommand): """Concrete class for Scheck""" + def execute(self, path): if pref.scheck_run != True: return args = [] - if pref.phpcs_php_prefix_path != "" and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix: + if ( + pref.phpcs_php_prefix_path != "" + and self.__class__.__name__ in pref.phpcs_commands_to_php_prefix + ): args = [pref.phpcs_php_prefix_path] if pref.scheck_executable_path != "": application_path = pref.scheck_executable_path else: - application_path = 'scheck' + application_path = "scheck" - if (len(args) > 0): + if len(args) > 0: args.append(application_path) else: args = [application_path] @@ -408,15 +456,18 @@ def execute(self, path): def parse_report(self, args): report = self.shell_out(args) debug_message(report) - lines = re.finditer('.*:(?P\d+):(?P\d+): CHECK: (?P.*)', report) + lines = re.finditer( + ".*:(?P\d+):(?P\d+): CHECK: (?P.*)", report + ) for line in lines: - error = CheckstyleError(line.group('line'), line.group('message')) + error = CheckstyleError(line.group("line"), line.group("message")) self.error_list.append(error) class Linter(ShellCommand): """Content class for php -l""" + def execute(self, path): if pref.phpcs_linter_run != True: return @@ -424,7 +475,7 @@ def execute(self, path): if pref.phpcs_php_path != "": args = [pref.phpcs_php_path] else: - args = ['php'] + args = ["php"] args.append("-l") args.append("-d display_errors=On") @@ -437,11 +488,11 @@ def parse_report(self, args): debug_message(report) line = re.search(pref.phpcs_linter_regex, report) if line != None: - error = CheckstyleError(line.group('line'), line.group('message')) + error = CheckstyleError(line.group("line"), line.group("message")) self.error_list.append(error) -class PhpcsCommand(): +class PhpcsCommand: """Main plugin class for building the checkstyle report""" # Class variable, stores the instances. @@ -449,7 +500,7 @@ class PhpcsCommand(): @staticmethod def instance(view, allow_new=True): - '''Return the last-used instance for a given view.''' + """Return the last-used instance for a given view.""" view_id = view.id() if view_id not in PhpcsCommand.instances: if not allow_new: @@ -464,7 +515,7 @@ def __init__(self, view): self.event = None self.error_lines = {} self.error_list = [] - self.shell_commands = ['Linter', 'Sniffer', 'MessDetector'] + self.shell_commands = ["Linter", "Sniffer", "MessDetector"] self.standards = [] def run(self, path, event=None): @@ -472,24 +523,40 @@ def run(self, path, event=None): self.checkstyle_reports = [] self.report = [] - if event != 'on_save': + if event != "on_save": if pref.phpcs_linter_run: - self.checkstyle_reports.append(['Linter', Linter().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Linter", Linter().get_errors(path), "dot"] + ) if pref.phpcs_sniffer_run: - self.checkstyle_reports.append(['Sniffer', Sniffer().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Sniffer", Sniffer().get_errors(path), "dot"] + ) if pref.phpmd_run: - self.checkstyle_reports.append(['MessDetector', MessDetector().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["MessDetector", MessDetector().get_errors(path), "dot"] + ) if pref.scheck_run: - self.checkstyle_reports.append(['Scheck', Scheck().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Scheck", Scheck().get_errors(path), "dot"] + ) else: if pref.phpcs_linter_command_on_save and pref.phpcs_linter_run: - self.checkstyle_reports.append(['Linter', Linter().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Linter", Linter().get_errors(path), "dot"] + ) if pref.phpcs_command_on_save and pref.phpcs_sniffer_run: - self.checkstyle_reports.append(['Sniffer', Sniffer().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Sniffer", Sniffer().get_errors(path), "dot"] + ) if pref.phpmd_command_on_save and pref.phpmd_run: - self.checkstyle_reports.append(['MessDetector', MessDetector().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["MessDetector", MessDetector().get_errors(path), "dot"] + ) if pref.scheck_command_on_save and pref.scheck_run: - self.checkstyle_reports.append(['Scheck', Scheck().get_errors(path), 'dot']) + self.checkstyle_reports.append( + ["Scheck", Scheck().get_errors(path), "dot"] + ) sublime.set_timeout(self.generate, 0) @@ -507,9 +574,9 @@ def set_status_bar(self): line = self.view.rowcol(self.view.sel()[0].end())[0] errors = self.get_errors(line) if errors: - self.view.set_status('Phpcs', errors) + self.view.set_status("Phpcs", errors) else: - self.view.erase_status('Phpcs') + self.view.erase_status("Phpcs") def generate(self): self.error_list = [] @@ -517,32 +584,44 @@ def generate(self): self.error_lines = {} for shell_command, report, icon in self.checkstyle_reports: - self.view.erase_regions('checkstyle') + self.view.erase_regions("checkstyle") self.view.erase_regions(shell_command) - debug_message(shell_command + ' found ' + str(len(report)) + ' errors') + debug_message(shell_command + " found " + str(len(report)) + " errors") for error in report: line = int(error.get_line()) pt = self.view.text_point(line - 1, 0) region_line = self.view.line(pt) region_set.append(region_line) - self.error_list.append('(' + str(line) + ') ' + error.get_message()) + self.error_list.append("(" + str(line) + ") " + error.get_message()) error.set_point(pt) self.report.append(error) self.error_lines[line] = error.get_message() if len(self.error_list) > 0: - icon = icon if pref.phpcs_show_gutter_marks else '' - outline = sublime.DRAW_OUTLINED if pref.phpcs_outline_for_errors else sublime.HIDDEN + icon = icon if pref.phpcs_show_gutter_marks else "" + outline = ( + sublime.DRAW_OUTLINED + if pref.phpcs_outline_for_errors + else sublime.HIDDEN + ) if pref.phpcs_show_gutter_marks or pref.phpcs_outline_for_errors: if pref.phpcs_icon_scope_color == None: - debug_message("WARN: phpcs_icon_scope_color is not defined, so resorting to phpcs colour scope") + debug_message( + "WARN: phpcs_icon_scope_color is not defined, so resorting to phpcs colour scope" + ) pref.phpcs_icon_scope_color = "phpcs" - self.view.add_regions(shell_command, region_set, pref.phpcs_icon_scope_color, icon, outline) + self.view.add_regions( + shell_command, + region_set, + pref.phpcs_icon_scope_color, + icon, + outline, + ) if pref.phpcs_show_quick_panel == True: # Skip showing the errors if we ran on save, and the option isn't set. - if self.event == 'on_save' and not pref.phpcs_show_errors_on_save: + if self.event == "on_save" and not pref.phpcs_show_errors_on_save: return self.show_quick_panel() @@ -567,23 +646,25 @@ def fix_standards_errors(self, tool, path): def display_coding_standards(self): self.standards = Sniffer().get_standards_available() - self.view.window().show_quick_panel(self.standards, self.on_coding_standard_change) + self.view.window().show_quick_panel( + self.standards, self.on_coding_standard_change + ) def on_coding_standard_change(self, picked): if picked == -1: return - current_additional_args = pref.get_setting('phpcs_additional_args') - current_additional_args['--standard'] = self.standards[picked].replace(' ', '') + current_additional_args = pref.get_setting("phpcs_additional_args") + current_additional_args["--standard"] = self.standards[picked].replace(" ", "") - pref.set_setting('phpcs_additional_args', current_additional_args) + pref.set_setting("phpcs_additional_args", current_additional_args) debug_message(current_additional_args) def on_quick_panel_done(self, picked): if picked == -1: return - if (len(self.report) > 0): + if len(self.report) > 0: pt = self.report[picked].get_point() self.view.sel().clear() self.view.sel().add(sublime.Region(pt)) @@ -599,16 +680,18 @@ def get_errors(self, line): def get_next_error(self, line): current_line = line + 1 - cache_error=None + cache_error = None # todo: Need a way of getting the line count of the current file! - cache_line=1000000 + cache_line = 1000000 for error in self.report: error_line = error.get_line() if cache_error != None: cache_line = cache_error.get_line() - if int(error_line) > int(current_line) and int(error_line) < int(cache_line): + if int(error_line) > int(current_line) and int(error_line) < int( + cache_line + ): cache_error = error if cache_error != None: @@ -620,10 +703,11 @@ def get_next_error(self, line): class PhpcsTextBase(sublime_plugin.TextCommand): """Base class for Text commands in the plugin, mainly here to check php files""" - description = '' + + description = "" def run(self, args): - debug_message('Not implemented') + debug_message("Not implemented") def description(self): if not PhpcsTextBase.should_execute(self.view): @@ -634,7 +718,6 @@ def description(self): @staticmethod def should_execute(view): if view.file_name() != None: - try: ext = os.path.splitext(view.file_name())[1] result = ext[1:] in pref.extensions_to_execute @@ -654,7 +737,8 @@ def should_execute(view): class PhpcsSniffThisFile(PhpcsTextBase): """Command to sniff the open file""" - description = 'Sniff this file...' + + description = "Sniff this file..." def run(self, args): cmd = PhpcsCommand.instance(self.view) @@ -665,22 +749,26 @@ def is_enabled(self): class PhpcsShowPreviousErrors(PhpcsTextBase): - '''Command to show the previous sniff errors.''' - description = 'Display sniff errors...' + """Command to show the previous sniff errors.""" + + description = "Display sniff errors..." def run(self, args): cmd = PhpcsCommand.instance(self.view, False) cmd.show_quick_panel() def is_enabled(self): - '''This command is only enabled if it's a PHP buffer with previous errors.''' - return PhpcsTextBase.should_execute(self.view) \ - and PhpcsCommand.instance(self.view, False) \ + """This command is only enabled if it's a PHP buffer with previous errors.""" + return ( + PhpcsTextBase.should_execute(self.view) + and PhpcsCommand.instance(self.view, False) and len(PhpcsCommand.instance(self.view, False).error_list) > 0 + ) class PhpcsGotoNextErrorCommand(PhpcsTextBase): """Go to the next error from the current position""" + def run(self, args): line = self.view.rowcol(self.view.sel()[0].end())[0] @@ -688,16 +776,19 @@ def run(self, args): next_line = cmd.get_next_error(line) def is_enabled(self): - '''This command is only enabled if it's a PHP buffer with previous errors.''' + """This command is only enabled if it's a PHP buffer with previous errors.""" - return PhpcsTextBase.should_execute(self.view) \ - and PhpcsCommand.instance(self.view, False) \ + return ( + PhpcsTextBase.should_execute(self.view) + and PhpcsCommand.instance(self.view, False) and len(PhpcsCommand.instance(self.view, False).error_list) > 0 + ) class PhpcsClearSnifferMarksCommand(PhpcsTextBase): """Command to clear the sniffer marks from the view""" - description = 'Clear sniffer marks...' + + description = "Clear sniffer marks..." def run(self, args): cmd = PhpcsCommand.instance(self.view) @@ -709,7 +800,8 @@ def is_enabled(self): class PhpcsFixThisFileCommand(PhpcsTextBase): """Command to use php-cs-fixer to 'fix' the file""" - description = 'Fix coding standard issues (php-cs-fixer)' + + description = "Fix coding standard issues (php-cs-fixer)" def run(self, args, tool="Fixer"): debug_message(tool) @@ -722,12 +814,13 @@ def is_enabled(self): class PhpcsFixThisDirectoryCommand(sublime_plugin.WindowCommand): """Command to use php-cs-fixer to 'fix' the directory""" + def run(self, tool="Fixer", paths=[]): cmd = PhpcsCommand.instance(self.window.active_view()) cmd.fix_standards_errors(tool, os.path.normpath(paths[0])) def is_enabled(self): - if pref.php_cs_fixer_executable_path != '': + if pref.php_cs_fixer_executable_path != "": return True else: return False @@ -736,11 +829,12 @@ def is_visible(self, paths=[]): return True def description(self, paths=[]): - return 'Fix this directory (PHP-CS-Fixer)' + return "Fix this directory (PHP-CS-Fixer)" class PhpcsTogglePlugin(PhpcsTextBase): """Command to toggle if plugin should execute on save""" + def run(self, edit, toggle=None): if toggle == None: if pref.phpcs_execute_on_save == True: @@ -748,7 +842,7 @@ def run(self, edit, toggle=None): else: pref.phpcs_execute_on_save = True else: - if toggle : + if toggle: pref.phpcs_execute_on_save = True else: pref.phpcs_execute_on_save = False @@ -758,14 +852,15 @@ def is_enabled(self): def description(self, paths=[]): if pref.phpcs_execute_on_save == True: - description = 'Turn Execute On Save Off' + description = "Turn Execute On Save Off" else: - description = 'Turn Execute On Save On' + description = "Turn Execute On Save On" return description class PhpcsSwitchCodingStandard(PhpcsTextBase): """Ability to switch the coding standard for this session""" + def run(self, args): cmd = PhpcsCommand.instance(self.view) cmd.display_coding_standards() @@ -776,11 +871,14 @@ def is_enabled(self): class PhpcsEventListener(sublime_plugin.EventListener): """Event listener for the plugin""" + def on_post_save(self, view): if PhpcsTextBase.should_execute(view): if pref.phpcs_execute_on_save == True: cmd = PhpcsCommand.instance(view) - thread = threading.Thread(target=cmd.run, args=(view.file_name(), 'on_save')) + thread = threading.Thread( + target=cmd.run, args=(view.file_name(), "on_save") + ) thread.start() if pref.phpcs_execute_on_save == True and pref.php_cs_fixer_on_save == True: @@ -800,22 +898,22 @@ def on_selection_modified(self, view): cmd.set_status_bar() def on_pre_save(self, view): - """ Project based settings, currently able to see an API based way of doing this! """ + """Project based settings, currently able to see an API based way of doing this!""" if not PhpcsTextBase.should_execute(view) or st_version == 2: return - current_project_file = view.window().project_file_name(); - debug_message('Project files:') - debug_message(' Current: ' + str(current_project_file)) - debug_message(' Last Known: ' + str(pref.project_file)) + current_project_file = view.window().project_file_name() + debug_message("Project files:") + debug_message(" Current: " + str(current_project_file)) + debug_message(" Last Known: " + str(pref.project_file)) if current_project_file == None: - debug_message('No project file defined, therefore skipping reload') + debug_message("No project file defined, therefore skipping reload") return if pref.project_file == current_project_file: - debug_message('Project files are the same, skipping reload') + debug_message("Project files are the same, skipping reload") else: - debug_message('Project files have changed, commence the reload') - pref.load(); + debug_message("Project files have changed, commence the reload") + pref.load() pref.project_file = current_project_file