eric6/Plugins/CheckerPlugins/CodeStyleChecker/eradicate.py

changeset 7040
f89952e5fc11
child 7639
422fd05e9c91
diff -r 73cb6384a71f -r f89952e5fc11 eric6/Plugins/CheckerPlugins/CodeStyleChecker/eradicate.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/eradicate.py	Wed Jun 26 19:41:11 2019 +0200
@@ -0,0 +1,234 @@
+# Copyright (C) 2012-2015 Steven Myint
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Removes commented-out Python code."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import difflib
+import io
+import os
+import re
+import tokenize
+
+__version__ = '1.0'
+
+
+MULTILINE_ASSIGNMENT_REGEX = re.compile(r'^\s*\w+\s*=.*[(\[{]$')
+PARTIAL_DICTIONARY_REGEX = re.compile(r'^\s*[\'"]\w+[\'"]\s*:.+[,{]\s*$')
+
+
+def comment_contains_code(line, aggressive=True):
+    """Return True comment contains code."""
+    line = line.lstrip()
+    if not line.startswith('#'):
+        return False
+
+    line = line.lstrip(' \t\v\n#').strip()
+
+    # Ignore non-comment related hashes. For example, "# Issue #999".
+    if re.search('#[0-9]', line):
+        return False
+
+    if line.startswith('pylint:'):
+        return False
+
+    if re.match(r'.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)', line):
+        return False
+
+    # Check that this is possibly code.
+    for symbol in list('()[]{}:=%') + ['print', 'return', 'break', 'continue',
+                                       'import']:
+        if symbol in line:
+            break
+    else:
+        return False
+
+    if multiline_case(line, aggressive=aggressive):
+        return True
+
+    symbol_list = [r'elif\s+.*', 'else', 'try',
+                   'finally', r'except\s+.*']
+    if aggressive:
+        symbol_list.append(r'if\s+.*')
+
+    for symbol in symbol_list:
+        if re.match(r'^\s*' + symbol + r'\s*:\s*$', line):
+            return True
+
+    line = re.sub(r'^(print|return)\b\s*', '', line)
+
+    if re.match(PARTIAL_DICTIONARY_REGEX, line):
+        return True
+
+    try:
+        compile(line, '<string>', 'exec')
+        return True
+    except (SyntaxError, TypeError, UnicodeDecodeError):
+        return False
+
+
+def multiline_case(line, aggressive=True):
+    """Return True if line is probably part of some multiline code."""
+    if aggressive:
+        for ending in ')]}':
+            if line.endswith(ending + ':'):
+                return True
+
+            if line.strip() == ending + ',':
+                return True
+
+        # Check whether a function/method definition with return value
+        # annotation
+        if re.search(r"def .+\)[\s]+->[\s]+[a-zA-Z_][a-zA-Z0-9_]*:$", line):
+            return True
+
+        # Check weather a with statement
+        if re.search(r"with .+ as [a-zA-Z_][a-zA-Z0-9_]*:$", line):
+            return True
+
+        # Check weather a for statement
+        if re.search(r"for [a-zA-Z_][a-zA-Z0-9_]* in .+:$", line):
+            return True
+
+    if line.endswith('\\'):
+        return True
+
+    if re.match(MULTILINE_ASSIGNMENT_REGEX, line):
+        return True
+
+    if re.match(r'^[()\[\]{}\s]+$', line):
+        return True
+
+    return False
+
+
+def commented_out_code_line_numbers(source, aggressive=True):
+    """Yield line numbers of commented-out code."""
+    sio = io.StringIO(source)
+    try:
+        for token in tokenize.generate_tokens(sio.readline):
+            token_type = token[0]
+            start_row = token[2][0]
+            line = token[4]
+
+            if (token_type == tokenize.COMMENT and
+                    line.lstrip().startswith('#') and
+                    not line.lstrip().startswith('##') and
+                    # modified from original file (line added)
+                    comment_contains_code(line, aggressive)):
+                yield start_row
+    except (tokenize.TokenError, IndentationError):
+        pass
+
+
+def filter_commented_out_code(source, aggressive=True):
+    """Yield code with commented out code removed."""
+    marked_lines = list(commented_out_code_line_numbers(source,
+                                                        aggressive))
+    sio = io.StringIO(source)
+    previous_line = ''
+    for line_number, line in enumerate(sio.readlines(), start=1):
+        if (line_number not in marked_lines or
+                previous_line.rstrip().endswith('\\')):
+            yield line
+        previous_line = line
+
+
+def fix_file(filename, args, standard_out):
+    """Run filter_commented_out_code() on file."""
+    encoding = detect_encoding(filename)
+    with open_with_encoding(filename, encoding=encoding) as input_file:
+        source = input_file.read()
+
+    filtered_source = ''.join(filter_commented_out_code(source,
+                                                        args.aggressive))
+
+    if source != filtered_source:
+        if args.in_place:
+            with open_with_encoding(filename, mode='w',
+                                    encoding=encoding) as output_file:
+                output_file.write(filtered_source)
+        else:
+            diff = difflib.unified_diff(
+                source.splitlines(),
+                filtered_source.splitlines(),
+                'before/' + filename,
+                'after/' + filename,
+                lineterm='')
+            standard_out.write('\n'.join(list(diff) + ['']))
+
+
+def open_with_encoding(filename, encoding, mode='r'):
+    """Return opened file with a specific encoding."""
+    return io.open(filename, mode=mode, encoding=encoding,
+                   newline='')  # Preserve line endings
+
+
+def detect_encoding(filename):
+    """Return file encoding."""
+    try:
+        with open(filename, 'rb') as input_file:
+            from lib2to3.pgen2 import tokenize as lib2to3_tokenize
+            encoding = lib2to3_tokenize.detect_encoding(input_file.readline)[0]
+
+            # Check for correctness of encoding.
+            with open_with_encoding(filename, encoding) as input_file:
+                input_file.read()
+
+        return encoding
+    except (SyntaxError, LookupError, UnicodeDecodeError):
+        return 'latin-1'
+
+
+def main(argv, standard_out, standard_error):
+    """Main entry point."""
+    import argparse
+    parser = argparse.ArgumentParser(description=__doc__, prog='eradicate')
+    parser.add_argument('-i', '--in-place', action='store_true',
+                        help='make changes to files instead of printing diffs')
+    parser.add_argument('-r', '--recursive', action='store_true',
+                        help='drill down directories recursively')
+    parser.add_argument('-a', '--aggressive', action='store_true',
+                        help='make more aggressive changes; '
+                             'this may result in false positives')
+    parser.add_argument('--version', action='version',
+                        version='%(prog)s ' + __version__)
+    parser.add_argument('files', nargs='+', help='files to format')
+
+    args = parser.parse_args(argv[1:])
+
+    filenames = list(set(args.files))
+    while filenames:
+        name = filenames.pop(0)
+        if args.recursive and os.path.isdir(name):
+            for root, directories, children in os.walk('{}'.format(name)):
+                filenames += [os.path.join(root, f) for f in children
+                              if f.endswith('.py') and
+                              not f.startswith('.')]
+                directories[:] = [d for d in directories
+                                  if not d.startswith('.')]
+        else:
+            try:
+                fix_file(name, args=args, standard_out=standard_out)
+            except IOError as exception:
+                print('{}'.format(exception), file=standard_error)

eric ide

mercurial