src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/pycodestyle.py

branch
eric7
changeset 10168
8312e0e76795
parent 9653
e67609152c5e
child 10353
babe9d606903
equal deleted inserted replaced
10167:0a62a4bf749c 10168:8312e0e76795
80 # this is a performance hack. see https://bugs.python.org/issue43014 80 # this is a performance hack. see https://bugs.python.org/issue43014
81 if ( 81 if (
82 sys.version_info < (3, 10) and 82 sys.version_info < (3, 10) and
83 callable(getattr(tokenize, '_compile', None)) 83 callable(getattr(tokenize, '_compile', None))
84 ): # pragma: no cover (<py310) 84 ): # pragma: no cover (<py310)
85 tokenize._compile = lru_cache()(tokenize._compile) # type: ignore 85 tokenize._compile = lru_cache(tokenize._compile) # type: ignore
86 86
87 __version__ = '2.10.0-eric' 87 __version__ = '2.11.0-eric'
88 88
89 DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' 89 DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox'
90 DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504' 90 DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504'
91 try: 91 try:
92 if sys.platform == 'win32': 92 if sys.platform == 'win32': # pragma: win32 cover
93 USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') 93 USER_CONFIG = os.path.expanduser(r'~\.pycodestyle')
94 else: 94 else: # pragma: win32 no cover
95 USER_CONFIG = os.path.join( 95 USER_CONFIG = os.path.join(
96 os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 96 os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'),
97 'pycodestyle' 97 'pycodestyle'
98 ) 98 )
99 except ImportError: 99 except ImportError:
100 USER_CONFIG = None 100 USER_CONFIG = None
101 101
102 PROJECT_CONFIG = ('setup.cfg', 'tox.ini') 102 PROJECT_CONFIG = ('setup.cfg', 'tox.ini')
103 TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite')
104 MAX_LINE_LENGTH = 79 103 MAX_LINE_LENGTH = 79
105 # Number of blank lines between various code parts. 104 # Number of blank lines between various code parts.
106 BLANK_LINES_CONFIG = { 105 BLANK_LINES_CONFIG = {
107 # Top level class and function. 106 # Top level class and function.
108 'top_level': 2, 107 'top_level': 2,
120 SINGLETONS = frozenset(['False', 'None', 'True']) 119 SINGLETONS = frozenset(['False', 'None', 'True'])
121 KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS 120 KEYWORDS = frozenset(keyword.kwlist + ['print', 'async']) - SINGLETONS
122 UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) 121 UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-'])
123 ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@']) 122 ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-', '@'])
124 WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) 123 WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%'])
125 ASSIGNMENT_EXPRESSION_OP = [':='] if sys.version_info >= (3, 8) else []
126 WS_NEEDED_OPERATORS = frozenset([ 124 WS_NEEDED_OPERATORS = frozenset([
127 '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', 125 '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<', '>',
128 '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=', 126 '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=',
129 'and', 'in', 'is', 'or', '->'] + 127 'and', 'in', 'is', 'or', '->', ':='])
130 ASSIGNMENT_EXPRESSION_OP)
131 WHITESPACE = frozenset(' \t\xa0') 128 WHITESPACE = frozenset(' \t\xa0')
132 NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) 129 NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE])
133 SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) 130 SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT])
134 # ERRORTOKEN is triggered by backticks in Python 3 131 # ERRORTOKEN is triggered by backticks in Python 3
135 SKIP_COMMENTS = SKIP_TOKENS.union([tokenize.COMMENT, tokenize.ERRORTOKEN]) 132 SKIP_COMMENTS = SKIP_TOKENS.union([tokenize.COMMENT, tokenize.ERRORTOKEN])
136 BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] 133 BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines']
137 134
138 INDENT_REGEX = re.compile(r'([ \t]*)') 135 INDENT_REGEX = re.compile(r'([ \t]*)')
139 RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,')
140 RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$')
141 ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') 136 ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b')
142 DOCSTRING_REGEX = re.compile(r'u?r?["\']') 137 DOCSTRING_REGEX = re.compile(r'u?r?["\']')
143 EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)') 138 EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[\[({][ \t]|[ \t][\]}),;:](?!=)')
144 WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') 139 WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)')
145 COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' 140 COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)'
146 r'\s*(?(1)|(None|False|True))\b') 141 r'\s*(?(1)|(None|False|True))\b')
147 COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?<!is\s)(not)\s+[^][)(}{ ]+\s+' 142 COMPARE_NEGATIVE_REGEX = re.compile(r'\b(?<!is\s)(not)\s+[^][)(}{ ]+\s+'
148 r'(in|is)\s') 143 r'(in|is)\s')
149 COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s+type(?:s.\w+Type' 144 COMPARE_TYPE_REGEX = re.compile(
150 r'|\s*\(\s*([^)]*[^ )])\s*\))') 145 r'[=!]=\s+type(?:\s*\(\s*([^)]*[^ )])\s*\))'
146 r'|\btype(?:\s*\(\s*([^)]*[^ )])\s*\))\s+[=!]='
147 )
151 KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) 148 KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS))
152 OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)') 149 OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+|:=)(\s*)')
153 LAMBDA_REGEX = re.compile(r'\blambda\b') 150 LAMBDA_REGEX = re.compile(r'\blambda\b')
154 HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') 151 HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$')
155 STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') 152 STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b')
166 ))) 163 )))
167 ) 164 )
168 DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ") 165 DUNDER_REGEX = re.compile(r"^__([^\s]+)__(?::\s*[a-zA-Z.0-9_\[\]\"]+)? = ")
169 BLANK_EXCEPT_REGEX = re.compile(r"except\s*:") 166 BLANK_EXCEPT_REGEX = re.compile(r"except\s*:")
170 167
168 if sys.version_info >= (3, 12): # pragma: >=3.12 cover
169 FSTRING_START = tokenize.FSTRING_START
170 FSTRING_MIDDLE = tokenize.FSTRING_MIDDLE
171 FSTRING_END = tokenize.FSTRING_END
172 else: # pragma: <3.12 cover
173 FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1
174
171 _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} 175 _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}}
172 176
173 177
174 def _get_parameters(function): 178 def _get_parameters(function):
175 return [parameter.name 179 return [parameter.name
212 the -t option, it issues warnings about code that illegally mixes 216 the -t option, it issues warnings about code that illegally mixes
213 tabs and spaces. When using -tt these warnings become errors. 217 tabs and spaces. When using -tt these warnings become errors.
214 These options are highly recommended! 218 These options are highly recommended!
215 219
216 Okay: if a == 0:\n a = 1\n b = 1 220 Okay: if a == 0:\n a = 1\n b = 1
217 E101: if a == 0:\n a = 1\n\tb = 1
218 """ 221 """
219 indent = INDENT_REGEX.match(physical_line).group(1) 222 indent = INDENT_REGEX.match(physical_line).group(1)
220 for offset, char in enumerate(indent): 223 for offset, char in enumerate(indent):
221 if char != indent_char: 224 if char != indent_char:
222 return offset, "E101 indentation contains mixed spaces and tabs" 225 return offset, "E101 indentation contains mixed spaces and tabs"
515 # appear e.g. as "if x is None:", and async/await, which were 518 # appear e.g. as "if x is None:", and async/await, which were
516 # valid identifier names in old Python versions. 519 # valid identifier names in old Python versions.
517 if (tok0.end == tok1.start and 520 if (tok0.end == tok1.start and
518 keyword.iskeyword(tok0.string) and 521 keyword.iskeyword(tok0.string) and
519 tok0.string not in SINGLETONS and 522 tok0.string not in SINGLETONS and
520 tok0.string not in ('async', 'await') and
521 not (tok0.string == 'except' and tok1.string == '*') and 523 not (tok0.string == 'except' and tok1.string == '*') and
522 not (tok0.string == 'yield' and tok1.string == ')') and 524 not (tok0.string == 'yield' and tok1.string == ')') and
523 tok1.string not in ':\n'): 525 tok1.string not in ':\n'):
524 yield tok0.end, "E275 missing whitespace after keyword" 526 yield tok0.end, "E275 missing whitespace after keyword"
525
526
527 @register_check
528 def missing_whitespace(logical_line):
529 r"""Each comma, semicolon or colon should be followed by whitespace.
530
531 Okay: [a, b]
532 Okay: (3,)
533 Okay: a[3,] = 1
534 Okay: a[1:4]
535 Okay: a[:4]
536 Okay: a[1:]
537 Okay: a[1:4:2]
538 E231: ['a','b']
539 E231: foo(bar,baz)
540 E231: [{'a':'b'}]
541 """
542 line = logical_line
543 for index in range(len(line) - 1):
544 char = line[index]
545 next_char = line[index + 1]
546 if char in ',;:' and next_char not in WHITESPACE:
547 before = line[:index]
548 if char == ':' and before.count('[') > before.count(']') and \
549 before.rfind('{') < before.rfind('['):
550 continue # Slice syntax, no space required
551 if char == ',' and next_char in ')]':
552 continue # Allow tuple with only one element: (3,)
553 if char == ':' and next_char == '=' and sys.version_info >= (3, 8):
554 continue # Allow assignment expression
555 yield index, "E231 missing whitespace after '%s'", char
556 527
557 528
558 @register_check 529 @register_check
559 def indentation(logical_line, previous_logical, indent_char, 530 def indentation(logical_line, previous_logical, indent_char,
560 indent_level, previous_indent_level, 531 indent_level, previous_indent_level,
741 indent[depth] = start[1] 712 indent[depth] = start[1]
742 indent_chances[start[1]] = True 713 indent_chances[start[1]] = True
743 if verbose >= 4: 714 if verbose >= 4:
744 print(f"bracket depth {depth} indent to {start[1]}") 715 print(f"bracket depth {depth} indent to {start[1]}")
745 # deal with implicit string concatenation 716 # deal with implicit string concatenation
746 elif (token_type in (tokenize.STRING, tokenize.COMMENT) or 717 elif token_type in (tokenize.STRING, tokenize.COMMENT, FSTRING_START):
747 text in ('u', 'ur', 'b', 'br')):
748 indent_chances[start[1]] = str 718 indent_chances[start[1]] = str
749 # visual indent after assert/raise/with 719 # visual indent after assert/raise/with
750 elif not row and not depth and text in ["assert", "raise", "with"]: 720 elif not row and not depth and text in ["assert", "raise", "with"]:
751 indent_chances[end[1] + 1] = True 721 indent_chances[end[1] + 1] = True
752 # special case for the "if" statement because len("if (") == 4 722 # special case for the "if" statement because len("if (") == 4
830 (prev_type == tokenize.NAME or prev_text in '}])') and 800 (prev_type == tokenize.NAME or prev_text in '}])') and
831 # Syntax "class A (B):" is allowed, but avoid it 801 # Syntax "class A (B):" is allowed, but avoid it
832 (index < 2 or tokens[index - 2][1] != 'class') and 802 (index < 2 or tokens[index - 2][1] != 'class') and
833 # Allow "return (a.foo for a in range(5))" 803 # Allow "return (a.foo for a in range(5))"
834 not keyword.iskeyword(prev_text) and 804 not keyword.iskeyword(prev_text) and
835 # 'match' and 'case' are only soft keywords
836 ( 805 (
837 sys.version_info < (3, 9) or 806 sys.version_info < (3, 9) or
807 # 3.12+: type is a soft keyword but no braces after
808 prev_text == 'type' or
838 not keyword.issoftkeyword(prev_text) 809 not keyword.issoftkeyword(prev_text)
839 ) 810 )
840 ): 811 ):
841 yield prev_end, "E211 whitespace before '%s'", text 812 yield prev_end, "E211 whitespace before '%s'", text
842 prev_type = token_type 813 prev_type = token_type
867 elif len(after) > 1: 838 elif len(after) > 1:
868 yield match.start(2), "E222 multiple spaces after operator" 839 yield match.start(2), "E222 multiple spaces after operator"
869 840
870 841
871 @register_check 842 @register_check
872 def missing_whitespace_around_operator(logical_line, tokens): 843 def missing_whitespace(logical_line, tokens):
873 r"""Surround operators with a single space on either side. 844 r"""Surround operators with the correct amount of whitespace.
874 845
875 - Always surround these binary operators with a single space on 846 - Always surround these binary operators with a single space on
876 either side: assignment (=), augmented assignment (+=, -= etc.), 847 either side: assignment (=), augmented assignment (+=, -= etc.),
877 comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), 848 comparisons (==, <, >, !=, <=, >=, in, not in, is, is not),
878 Booleans (and, or, not). 849 Booleans (and, or, not).
850
851 - Each comma, semicolon or colon should be followed by whitespace.
879 852
880 - If operators with different priorities are used, consider adding 853 - If operators with different priorities are used, consider adding
881 whitespace around the operators with the lowest priorities. 854 whitespace around the operators with the lowest priorities.
882 855
883 Okay: i = i + 1 856 Okay: i = i + 1
885 Okay: x = x * 2 - 1 858 Okay: x = x * 2 - 1
886 Okay: hypot2 = x * x + y * y 859 Okay: hypot2 = x * x + y * y
887 Okay: c = (a + b) * (a - b) 860 Okay: c = (a + b) * (a - b)
888 Okay: foo(bar, key='word', *args, **kwargs) 861 Okay: foo(bar, key='word', *args, **kwargs)
889 Okay: alpha[:-i] 862 Okay: alpha[:-i]
863 Okay: [a, b]
864 Okay: (3,)
865 Okay: a[3,] = 1
866 Okay: a[1:4]
867 Okay: a[:4]
868 Okay: a[1:]
869 Okay: a[1:4:2]
890 870
891 E225: i=i+1 871 E225: i=i+1
892 E225: submitted +=1 872 E225: submitted +=1
893 E225: x = x /2 - 1 873 E225: x = x /2 - 1
894 E225: z = x **y 874 E225: z = x **y
895 E225: z = 1and 1 875 E225: z = 1and 1
896 E226: c = (a+b) * (a-b) 876 E226: c = (a+b) * (a-b)
897 E226: hypot2 = x*x + y*y 877 E226: hypot2 = x*x + y*y
898 E227: c = a|b 878 E227: c = a|b
899 E228: msg = fmt%(errno, errmsg) 879 E228: msg = fmt%(errno, errmsg)
900 """ 880 E231: ['a','b']
901 parens = 0 881 E231: foo(bar,baz)
882 E231: [{'a':'b'}]
883 """
902 need_space = False 884 need_space = False
903 prev_type = tokenize.OP 885 prev_type = tokenize.OP
904 prev_text = prev_end = None 886 prev_text = prev_end = None
905 operator_types = (tokenize.OP, tokenize.NAME) 887 operator_types = (tokenize.OP, tokenize.NAME)
888 brace_stack = []
906 for token_type, text, start, end, line in tokens: 889 for token_type, text, start, end, line in tokens:
890 if token_type == tokenize.OP and text in {'[', '(', '{'}:
891 brace_stack.append(text)
892 elif token_type == FSTRING_START: # pragma: >=3.12 cover
893 brace_stack.append('f')
894 elif token_type == tokenize.NAME and text == 'lambda':
895 brace_stack.append('l')
896 elif brace_stack:
897 if token_type == tokenize.OP and text in {']', ')', '}'}:
898 brace_stack.pop()
899 elif token_type == FSTRING_END: # pragma: >=3.12 cover
900 brace_stack.pop()
901 elif (
902 brace_stack[-1] == 'l' and
903 token_type == tokenize.OP and
904 text == ':'
905 ):
906 brace_stack.pop()
907
907 if token_type in SKIP_COMMENTS: 908 if token_type in SKIP_COMMENTS:
908 continue 909 continue
909 if text in ('(', 'lambda'): 910
910 parens += 1 911 if token_type == tokenize.OP and text in {',', ';', ':'}:
911 elif text == ')': 912 next_char = line[end[1]:end[1] + 1]
912 parens -= 1 913 if next_char not in WHITESPACE and next_char not in '\r\n':
914 # slice
915 if text == ':' and brace_stack[-1:] == ['[']:
916 pass
917 # 3.12+ fstring format specifier
918 elif text == ':' and brace_stack[-2:] == ['f', '{']: # pragma: >=3.12 cover # noqa: E501
919 pass
920 # tuple (and list for some reason?)
921 elif text == ',' and next_char in ')]':
922 pass
923 else:
924 yield start, f'E231 missing whitespace after {text!r}', text
925
913 if need_space: 926 if need_space:
914 if start != prev_end: 927 if start != prev_end:
915 # Found a (probably) needed space 928 # Found a (probably) needed space
916 if need_space is not True and not need_space[1]: 929 if need_space is not True and not need_space[1]:
917 yield (need_space[0], 930 yield (need_space[0],
918 "E225 missing whitespace around operator") 931 "E225 missing whitespace around operator")
919 need_space = False 932 need_space = False
920 elif text == '>' and prev_text in ('<', '-'):
921 # Tolerate the "<>" operator, even if running Python 3
922 # Deal with Python 3's annotated return value "->"
923 pass
924 elif ( 933 elif (
925 # def f(a, /, b): 934 # def f(a, /, b):
926 # ^ 935 # ^
927 # def f(a, b, /): 936 # def f(a, b, /):
928 # ^ 937 # ^
948 code, optype = 'E227', 'bitwise or shift' 957 code, optype = 'E227', 'bitwise or shift'
949 yield (need_space[0], "%s missing whitespace " 958 yield (need_space[0], "%s missing whitespace "
950 "around %s operator" % (code, optype)) 959 "around %s operator" % (code, optype))
951 need_space = False 960 need_space = False
952 elif token_type in operator_types and prev_end is not None: 961 elif token_type in operator_types and prev_end is not None:
953 if text == '=' and parens: 962 if (
954 # Allow keyword args or defaults: foo(bar=None). 963 text == '=' and (
964 # allow lambda default args: lambda x=None: None
965 brace_stack[-1:] == ['l'] or
966 # allow keyword args or defaults: foo(bar=None).
967 brace_stack[-1:] == ['('] or
968 # allow python 3.8 fstring repr specifier
969 brace_stack[-2:] == ['f', '{']
970 )
971 ):
955 pass 972 pass
956 elif text in WS_NEEDED_OPERATORS: 973 elif text in WS_NEEDED_OPERATORS:
957 need_space = True 974 need_space = True
958 elif text in UNARY_OPERATORS: 975 elif text in UNARY_OPERATORS:
959 # Check if the operator is used as a binary operator 976 # Check if the operator is used as a binary operator
1143 1160
1144 Okay: import os 1161 Okay: import os
1145 Okay: # this is a comment\nimport os 1162 Okay: # this is a comment\nimport os
1146 Okay: '''this is a module docstring'''\nimport os 1163 Okay: '''this is a module docstring'''\nimport os
1147 Okay: r'''this is a module docstring'''\nimport os 1164 Okay: r'''this is a module docstring'''\nimport os
1148 Okay:
1149 try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y
1150 Okay:
1151 try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y
1152 E402: a=1\nimport os 1165 E402: a=1\nimport os
1153 E402: 'One string'\n"Two string"\nimport os 1166 E402: 'One string'\n"Two string"\nimport os
1154 E402: a=1\nfrom sys import x 1167 E402: a=1\nfrom sys import x
1155 1168
1156 Okay: if x:\n import os 1169 Okay: if x:\n import os
1227 found = line.find(':') 1240 found = line.find(':')
1228 prev_found = 0 1241 prev_found = 0
1229 counts = {char: 0 for char in '{}[]()'} 1242 counts = {char: 0 for char in '{}[]()'}
1230 while -1 < found < last_char: 1243 while -1 < found < last_char:
1231 update_counts(line[prev_found:found], counts) 1244 update_counts(line[prev_found:found], counts)
1232 if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) 1245 if (
1233 counts['['] <= counts[']'] and # [1:2] (slice) 1246 counts['{'] <= counts['}'] and # {'a': 1} (dict)
1234 counts['('] <= counts[')']) and # (annotation) 1247 counts['['] <= counts[']'] and # [1:2] (slice)
1235 not (sys.version_info >= (3, 8) and 1248 counts['('] <= counts[')'] and # (annotation)
1236 line[found + 1] == '=')): # assignment expression 1249 line[found + 1] != '=' # assignment expression
1250 ):
1237 lambda_kw = LAMBDA_REGEX.search(line, 0, found) 1251 lambda_kw = LAMBDA_REGEX.search(line, 0, found)
1238 if lambda_kw: 1252 if lambda_kw:
1239 before = line[:lambda_kw.start()].rstrip() 1253 before = line[:lambda_kw.start()].rstrip()
1240 if before[-1:] == '=' and before[:-1].strip().isidentifier(): 1254 if before[-1:] == '=' and before[:-1].strip().isidentifier():
1241 yield 0, ("E731 do not assign a lambda expression, use a " 1255 yield 0, ("E731 do not assign a lambda expression, use a "
1467 r"""Object type comparisons should always use isinstance(). 1481 r"""Object type comparisons should always use isinstance().
1468 1482
1469 Do not compare types directly. 1483 Do not compare types directly.
1470 1484
1471 Okay: if isinstance(obj, int): 1485 Okay: if isinstance(obj, int):
1472 E721: if type(obj) is type(1): 1486 Okay: if type(obj) is int:
1473 1487 E721: if type(obj) == type(1):
1474 When checking if an object is a string, keep in mind that it might
1475 be a unicode string too! In Python 2.3, str and unicode have a
1476 common base class, basestring, so you can do:
1477
1478 Okay: if isinstance(obj, basestring):
1479 Okay: if type(a1) is type(b1):
1480 """ 1488 """
1481 match = COMPARE_TYPE_REGEX.search(logical_line) 1489 match = COMPARE_TYPE_REGEX.search(logical_line)
1482 if match and not noqa: 1490 if match and not noqa:
1483 inst = match.group(1) 1491 inst = match.group(1)
1484 if inst and inst.isidentifier() and inst not in SINGLETONS: 1492 if inst and inst.isidentifier() and inst not in SINGLETONS:
1485 return # Allow comparison for types which are not obvious 1493 return # Allow comparison for types which are not obvious
1486 yield match.start(), "E721 do not compare types, use 'isinstance()'" 1494 yield (
1495 match.start(),
1496 "E721 do not compare types, for exact checks use `is` / `is not`, "
1497 "for instance checks use `isinstance()`",
1498 )
1487 1499
1488 1500
1489 @register_check 1501 @register_check
1490 def bare_except(logical_line, noqa): 1502 def bare_except(logical_line, noqa):
1491 r"""When catching exceptions, mention specific exceptions when 1503 r"""When catching exceptions, mention specific exceptions when
1602 prev_text = text 1614 prev_text = text
1603 prev_start = start 1615 prev_start = start
1604 1616
1605 1617
1606 @register_check 1618 @register_check
1607 def python_3000_has_key(logical_line, noqa):
1608 r"""The {}.has_key() method is removed in Python 3: use the 'in'
1609 operator.
1610
1611 Okay: if "alph" in d:\n print d["alph"]
1612 W601: assert d.has_key('alph')
1613 """
1614 pos = logical_line.find('.has_key(')
1615 if pos > -1 and not noqa:
1616 yield pos, "W601 .has_key() is deprecated, use 'in'"
1617
1618
1619 @register_check
1620 def python_3000_raise_comma(logical_line):
1621 r"""When raising an exception, use "raise ValueError('message')".
1622
1623 The older form is removed in Python 3.
1624
1625 Okay: raise DummyError("Message")
1626 W602: raise DummyError, "Message"
1627 """
1628 match = RAISE_COMMA_REGEX.match(logical_line)
1629 if match and not RERAISE_COMMA_REGEX.match(logical_line):
1630 yield match.end() - 1, "W602 deprecated form of raising exception"
1631
1632
1633 @register_check
1634 def python_3000_not_equal(logical_line):
1635 r"""New code should always use != instead of <>.
1636
1637 The older syntax is removed in Python 3.
1638
1639 Okay: if a != 'no':
1640 W603: if a <> 'no':
1641 """
1642 pos = logical_line.find('<>')
1643 if pos > -1:
1644 yield pos, "W603 '<>' is deprecated, use '!='"
1645
1646
1647 @register_check
1648 def python_3000_backticks(logical_line):
1649 r"""Use repr() instead of backticks in Python 3.
1650
1651 Okay: val = repr(1 + 2)
1652 W604: val = `1 + 2`
1653 """
1654 pos = logical_line.find('`')
1655 if pos > -1:
1656 yield pos, "W604 backticks are deprecated, use 'repr()'"
1657
1658
1659 @register_check
1660 def python_3000_invalid_escape_sequence(logical_line, tokens, noqa): 1619 def python_3000_invalid_escape_sequence(logical_line, tokens, noqa):
1661 r"""Invalid escape sequences are deprecated in Python 3.6. 1620 r"""Invalid escape sequences are deprecated in Python 3.6.
1662 1621
1663 Okay: regex = r'\.png$' 1622 Okay: regex = r'\.png$'
1664 W605: regex = '\.png$' 1623 W605: regex = '\.png$'
1686 'N', 1645 'N',
1687 'u', 1646 'u',
1688 'U', 1647 'U',
1689 ] 1648 ]
1690 1649
1691 for token_type, text, start, end, line in tokens: 1650 prefixes = []
1692 if token_type == tokenize.STRING: 1651 for token_type, text, start, _, _ in tokens:
1693 start_line, start_col = start 1652 if token_type in {tokenize.STRING, FSTRING_START}:
1694 quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1]
1695 # Extract string modifiers (e.g. u or r) 1653 # Extract string modifiers (e.g. u or r)
1696 quote_pos = text.index(quote) 1654 prefixes.append(text[:text.index(text[-1])].lower())
1697 prefix = text[:quote_pos].lower() 1655
1698 start = quote_pos + len(quote) 1656 if token_type in {tokenize.STRING, FSTRING_MIDDLE}:
1699 string = text[start:-len(quote)] 1657 if 'r' not in prefixes[-1]:
1700 1658 start_line, start_col = start
1701 if 'r' not in prefix: 1659 pos = text.find('\\')
1702 pos = string.find('\\')
1703 while pos >= 0: 1660 while pos >= 0:
1704 pos += 1 1661 pos += 1
1705 if string[pos] not in valid: 1662 if text[pos] not in valid:
1706 line = start_line + string.count('\n', 0, pos) 1663 line = start_line + text.count('\n', 0, pos)
1707 if line == start_line: 1664 if line == start_line:
1708 col = start_col + len(prefix) + len(quote) + pos 1665 col = start_col + pos
1709 else: 1666 else:
1710 col = pos - string.rfind('\n', 0, pos) - 1 1667 col = pos - text.rfind('\n', 0, pos) - 1
1711 yield ( 1668 yield (
1712 (line, col - 1), 1669 (line, col - 1),
1713 "W605 invalid escape sequence '\\%s'", 1670 f"W605 invalid escape sequence '\\{text[pos]}'",
1714 string[pos], 1671 text[pos],
1715 ) 1672 )
1716 pos = string.find('\\', pos + 1) 1673 pos = text.find('\\', pos + 1)
1717 1674
1718 1675 if token_type in {tokenize.STRING, FSTRING_END}:
1719 @register_check 1676 prefixes.pop()
1720 def python_3000_async_await_keywords(logical_line, tokens):
1721 """'async' and 'await' are reserved keywords starting at Python 3.7.
1722
1723 W606: async = 42
1724 W606: await = 42
1725 Okay: async def read(db):\n data = await db.fetch('SELECT ...')
1726 """
1727 # The Python tokenize library before Python 3.5 recognizes
1728 # async/await as a NAME token. Therefore, use a state machine to
1729 # look for the possible async/await constructs as defined by the
1730 # Python grammar:
1731 # https://docs.python.org/3/reference/grammar.html
1732
1733 state = None
1734 for token_type, text, start, end, line in tokens:
1735 error = False
1736
1737 if token_type == tokenize.NL:
1738 continue
1739
1740 if state is None:
1741 if token_type == tokenize.NAME:
1742 if text == 'async':
1743 state = ('async_stmt', start)
1744 elif text == 'await':
1745 state = ('await', start)
1746 elif (token_type == tokenize.NAME and
1747 text in ('def', 'for')):
1748 state = ('define', start)
1749
1750 elif state[0] == 'async_stmt':
1751 if token_type == tokenize.NAME and text in ('def', 'with', 'for'):
1752 # One of funcdef, with_stmt, or for_stmt. Return to
1753 # looking for async/await names.
1754 state = None
1755 else:
1756 error = True
1757 elif state[0] == 'await':
1758 if token_type == tokenize.NAME:
1759 # An await expression. Return to looking for async/await
1760 # names.
1761 state = None
1762 elif token_type == tokenize.OP and text == '(':
1763 state = None
1764 else:
1765 error = True
1766 elif state[0] == 'define':
1767 if token_type == tokenize.NAME and text in ('async', 'await'):
1768 error = True
1769 else:
1770 state = None
1771
1772 if error:
1773 yield (
1774 state[1],
1775 "W606 'async' and 'await' are reserved keywords starting with "
1776 "Python 3.7",
1777 )
1778 state = None
1779
1780 # Last token
1781 if state is not None:
1782 yield (
1783 state[1],
1784 "W606 'async' and 'await' are reserved keywords starting with "
1785 "Python 3.7",
1786 )
1787 1677
1788 1678
1789 ######################################################################## 1679 ########################################################################
1790 @register_check 1680 @register_check
1791 def maximum_doc_length(logical_line, max_doc_length, noqa, tokens): 1681 def maximum_doc_length(logical_line, max_doc_length, noqa, tokens):
1859 1749
1860 def expand_indent(line): 1750 def expand_indent(line):
1861 r"""Return the amount of indentation. 1751 r"""Return the amount of indentation.
1862 1752
1863 Tabs are expanded to the next multiple of 8. 1753 Tabs are expanded to the next multiple of 8.
1864
1865 >>> expand_indent(' ')
1866 4
1867 >>> expand_indent('\t')
1868 8
1869 >>> expand_indent(' \t')
1870 8
1871 >>> expand_indent(' \t')
1872 16
1873 """ 1754 """
1874 line = line.rstrip('\n\r') 1755 line = line.rstrip('\n\r')
1875 if '\t' not in line: 1756 if '\t' not in line:
1876 return len(line) - len(line.lstrip()) 1757 return len(line) - len(line.lstrip())
1877 result = 0 1758 result = 0
1884 break 1765 break
1885 return result 1766 return result
1886 1767
1887 1768
1888 def mute_string(text): 1769 def mute_string(text):
1889 """Replace contents with 'xxx' to prevent syntax matching. 1770 """Replace contents with 'xxx' to prevent syntax matching."""
1890
1891 >>> mute_string('"abc"')
1892 '"xxx"'
1893 >>> mute_string("'''abc'''")
1894 "'''xxx'''"
1895 >>> mute_string("r'abc'")
1896 "r'xxx'"
1897 """
1898 # String modifiers (e.g. u or r) 1771 # String modifiers (e.g. u or r)
1899 start = text.index(text[-1]) + 1 1772 start = text.index(text[-1]) + 1
1900 end = len(text) - 1 1773 end = len(text) - 1
1901 # Triple quotes 1774 # Triple quotes
1902 if text[-3:] in ('"""', "'''"): 1775 if text[-3:] in ('"""', "'''"):
1993 self._logical_checks = options.logical_checks 1866 self._logical_checks = options.logical_checks
1994 self._ast_checks = options.ast_checks 1867 self._ast_checks = options.ast_checks
1995 self.max_line_length = options.max_line_length 1868 self.max_line_length = options.max_line_length
1996 self.max_doc_length = options.max_doc_length 1869 self.max_doc_length = options.max_doc_length
1997 self.indent_size = options.indent_size 1870 self.indent_size = options.indent_size
1871 self.fstring_start = 0
1998 self.multiline = False # in a multiline string? 1872 self.multiline = False # in a multiline string?
1999 self.hang_closing = options.hang_closing 1873 self.hang_closing = options.hang_closing
2000 self.indent_size = options.indent_size 1874 self.indent_size = options.indent_size
2001 self.verbose = options.verbose 1875 self.verbose = options.verbose
2002 self.filename = filename 1876 self.filename = filename
2095 if token_type == tokenize.COMMENT: 1969 if token_type == tokenize.COMMENT:
2096 comments.append(text) 1970 comments.append(text)
2097 continue 1971 continue
2098 if token_type == tokenize.STRING: 1972 if token_type == tokenize.STRING:
2099 text = mute_string(text) 1973 text = mute_string(text)
1974 elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover
1975 text = 'x' * len(text)
2100 if prev_row: 1976 if prev_row:
2101 (start_row, start_col) = start 1977 (start_row, start_col) = start
2102 if prev_row != start_row: # different row 1978 if prev_row != start_row: # different row
2103 prev_text = self.lines[prev_row - 1][prev_col - 1] 1979 prev_text = self.lines[prev_row - 1][prev_col - 1]
2104 if prev_text == ',' or (prev_text not in '{[(' and 1980 if prev_text == ',' or (prev_text not in '{[(' and
2185 2061
2186 def maybe_check_physical(self, token, prev_physical): 2062 def maybe_check_physical(self, token, prev_physical):
2187 """If appropriate for token, check current physical line(s).""" 2063 """If appropriate for token, check current physical line(s)."""
2188 # Called after every token, but act only on end of line. 2064 # Called after every token, but act only on end of line.
2189 2065
2066 if token.type == FSTRING_START: # pragma: >=3.12 cover
2067 self.fstring_start = token.start[0]
2190 # a newline token ends a single physical line. 2068 # a newline token ends a single physical line.
2191 if _is_eol_token(token): 2069 elif _is_eol_token(token):
2192 # if the file does not end with a newline, the NEWLINE 2070 # if the file does not end with a newline, the NEWLINE
2193 # token is inserted by the parser, but it does not contain 2071 # token is inserted by the parser, but it does not contain
2194 # the previous physical line in `token[4]` 2072 # the previous physical line in `token[4]`
2195 if token[4] == '': 2073 if token.line == '':
2196 self.check_physical(prev_physical) 2074 self.check_physical(prev_physical)
2197 else: 2075 else:
2198 self.check_physical(token[4]) 2076 self.check_physical(token.line)
2199 elif token[0] == tokenize.STRING and '\n' in token[1]: 2077 elif (
2078 token.type == tokenize.STRING and '\n' in token.string or
2079 token.type == FSTRING_END
2080 ):
2200 # Less obviously, a string that contains newlines is a 2081 # Less obviously, a string that contains newlines is a
2201 # multiline string, either triple-quoted or with internal 2082 # multiline string, either triple-quoted or with internal
2202 # newlines backslash-escaped. Check every physical line in 2083 # newlines backslash-escaped. Check every physical line in
2203 # the string *except* for the last one: its newline is 2084 # the string *except* for the last one: its newline is
2204 # outside of the multiline string, so we consider it a 2085 # outside of the multiline string, so we consider it a
2210 # contains the magical "# noqa" comment, we disable all 2091 # contains the magical "# noqa" comment, we disable all
2211 # physical checks for the entire multiline string 2092 # physical checks for the entire multiline string
2212 # - have to wind self.line_number back because initially it 2093 # - have to wind self.line_number back because initially it
2213 # points to the last line of the string, and we want 2094 # points to the last line of the string, and we want
2214 # check_physical() to give accurate feedback 2095 # check_physical() to give accurate feedback
2215 if noqa(token[4]): 2096 if noqa(token.line):
2216 return 2097 return
2098 if token.type == FSTRING_END: # pragma: >=3.12 cover
2099 start = self.fstring_start
2100 else:
2101 start = token.start[0]
2102 end = token.end[0]
2103
2217 self.multiline = True 2104 self.multiline = True
2218 self.line_number = token[2][0] 2105 self.line_number = start
2219 _, src, (_, offset), _, _ = token 2106 for line_number in range(start, end):
2220 src = self.lines[self.line_number - 1][:offset] + src 2107 self.check_physical(self.lines[line_number - 1] + '\n')
2221 for line in src.split('\n')[:-1]:
2222 self.check_physical(line + '\n')
2223 self.line_number += 1 2108 self.line_number += 1
2224 self.multiline = False 2109 self.multiline = False
2225 2110
2226 def check_all(self, expected=None, line_offset=0): 2111 def check_all(self, expected=None, line_offset=0):
2227 """Run all checks on the input file.""" 2112 """Run all checks on the input file."""
2485 2370
2486 if not options.reporter: 2371 if not options.reporter:
2487 options.reporter = BaseReport if options.quiet else StandardReport 2372 options.reporter = BaseReport if options.quiet else StandardReport
2488 2373
2489 options.select = tuple(options.select or ()) 2374 options.select = tuple(options.select or ())
2490 # if not (options.select or options.ignore or 2375 # if not (options.select or options.ignore) and DEFAULT_IGNORE:
2491 # options.testsuite or options.doctest) and DEFAULT_IGNORE:
2492 # # The default choice: ignore controversial checks 2376 # # The default choice: ignore controversial checks
2493 # options.ignore = tuple(DEFAULT_IGNORE.split(',')) 2377 # options.ignore = tuple(DEFAULT_IGNORE.split(','))
2494 # else: 2378 # else:
2495 # # Ignore all checks which are not explicitly selected or all if no 2379 # # Ignore all checks which are not explicitly selected
2496 # options.ignore = ('',) if options.select else tuple(options.ignore) 2380 # options.ignore = ('',) if options.select else tuple(options.ignore)
2497 2381
2498 # check is ignored or explicitly selected
2499 options.ignore = ('',) if options.select else tuple(options.ignore) 2382 options.ignore = ('',) if options.select else tuple(options.ignore)
2500 options.benchmark_keys = BENCHMARK_KEYS[:] 2383 options.benchmark_keys = BENCHMARK_KEYS[:]
2501 options.ignore_code = self.ignore_code 2384 options.ignore_code = self.ignore_code
2502 options.physical_checks = self.get_checks('physical_line') 2385 options.physical_checks = self.get_checks('physical_line')
2503 options.logical_checks = self.get_checks('logical_line') 2386 options.logical_checks = self.get_checks('logical_line')
2659 help="set the error format [default|pylint|<custom>]") 2542 help="set the error format [default|pylint|<custom>]")
2660 parser.add_option('--diff', action='store_true', 2543 parser.add_option('--diff', action='store_true',
2661 help="report changes only within line number ranges in " 2544 help="report changes only within line number ranges in "
2662 "the unified diff received on STDIN") 2545 "the unified diff received on STDIN")
2663 group = parser.add_option_group("Testing Options") 2546 group = parser.add_option_group("Testing Options")
2664 if os.path.exists(TESTSUITE_PATH):
2665 group.add_option('--testsuite', metavar='dir',
2666 help="run regression tests from dir")
2667 group.add_option('--doctest', action='store_true',
2668 help="run doctest on myself")
2669 group.add_option('--benchmark', action='store_true', 2547 group.add_option('--benchmark', action='store_true',
2670 help="measure processing speed") 2548 help="measure processing speed")
2671 return parser 2549 return parser
2672 2550
2673 2551
2740 value = normalize_paths(value, local_dir) 2618 value = normalize_paths(value, local_dir)
2741 setattr(new_options, normalized_opt, value) 2619 setattr(new_options, normalized_opt, value)
2742 2620
2743 # Third, overwrite with the command-line options 2621 # Third, overwrite with the command-line options
2744 (options, __) = parser.parse_args(arglist, values=new_options) 2622 (options, __) = parser.parse_args(arglist, values=new_options)
2745 options.doctest = options.testsuite = False
2746 return options 2623 return options
2747 2624
2748 2625
2749 def process_options(arglist=None, parse_argv=False, config_file=None, 2626 def process_options(arglist=None, parse_argv=False, config_file=None,
2750 parser=None, verbose=None): 2627 parser=None, verbose=None):
2773 2650
2774 # If explicitly specified verbosity, override any `-v` CLI flag 2651 # If explicitly specified verbosity, override any `-v` CLI flag
2775 if verbose is not None: 2652 if verbose is not None:
2776 options.verbose = verbose 2653 options.verbose = verbose
2777 2654
2778 if options.ensure_value('testsuite', False): 2655 if parse_argv and not args:
2779 args.append(options.testsuite) 2656 if options.diff or any(os.path.exists(name)
2780 elif not options.ensure_value('doctest', False): 2657 for name in PROJECT_CONFIG):
2781 if parse_argv and not args: 2658 args = ['.']
2782 if options.diff or any(os.path.exists(name) 2659 else:
2783 for name in PROJECT_CONFIG): 2660 parser.error('input not specified')
2784 args = ['.'] 2661 options = read_config(options, args, arglist, parser)
2785 else: 2662 options.reporter = parse_argv and options.quiet == 1 and FileReport
2786 parser.error('input not specified')
2787 options = read_config(options, args, arglist, parser)
2788 options.reporter = parse_argv and options.quiet == 1 and FileReport
2789 2663
2790 options.filename = _parse_multi_options(options.filename) 2664 options.filename = _parse_multi_options(options.filename)
2791 options.exclude = normalize_paths(options.exclude) 2665 options.exclude = normalize_paths(options.exclude)
2792 options.select = _parse_multi_options(options.select) 2666 options.select = _parse_multi_options(options.select)
2793 options.ignore = _parse_multi_options(options.ignore) 2667 options.ignore = _parse_multi_options(options.ignore)
2828 pass # not supported on Windows 2702 pass # not supported on Windows
2829 2703
2830 style_guide = StyleGuide(parse_argv=True) 2704 style_guide = StyleGuide(parse_argv=True)
2831 options = style_guide.options 2705 options = style_guide.options
2832 2706
2833 if options.doctest or options.testsuite: 2707 report = style_guide.check_files()
2834 from testsuite.support import run_tests
2835 report = run_tests(style_guide)
2836 else:
2837 report = style_guide.check_files()
2838 2708
2839 if options.statistics: 2709 if options.statistics:
2840 report.print_statistics() 2710 report.print_statistics()
2841 2711
2842 if options.benchmark: 2712 if options.benchmark:
2843 report.print_benchmark() 2713 report.print_benchmark()
2844
2845 if options.testsuite and not options.quiet:
2846 report.print_results()
2847 2714
2848 if report.total_errors: 2715 if report.total_errors:
2849 if options.count: 2716 if options.count:
2850 sys.stderr.write(str(report.total_errors) + '\n') 2717 sys.stderr.write(str(report.total_errors) + '\n')
2851 sys.exit(1) 2718 sys.exit(1)

eric ide

mercurial