Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
# TODO: change max-complexity into 10 after refactoring
flake8 . --count --max-complexity=11 --max-line-length=120 --max-doc-length=120 --ignore=I201,I202,I101,I100,R504,A003,E800,SC200,SC100,E402 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
flake8 . --count --max-complexity=11 --max-line-length=120 --max-doc-length=120 --ignore=I201,I202,I101,I100,R504,A003,E800,SC200,SC100,E402,W503,WPS --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
- name: Set up Eslint
run: |
npm install eslint --save-dev
Expand All @@ -40,4 +40,4 @@ jobs:
run: java -version
- name: Test with pytest
run: |
pytest
pytest
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ flake8-return==1.1.1
flake8-spellcheck==0.14.0
mccabe==0.6.1
pep8-naming==0.11.1
wps-light==0.15.2

# extra libraries and frameworks
django==3.0.8
Expand Down
28 changes: 28 additions & 0 deletions src/python/review/inspectors/flake8/.flake8
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,31 @@ ignore=W291, # trailing whitespaces
E301, E302, E303, E304, E305, # problem with stepik templates
E402, # module level import not at top of file
I100, # Import statements are in the wrong order
# WPS: Naming
WPS110, # Forbid blacklisted variable names.
WPS111, # Forbid short variable or module names.
WPS112, # Forbid private name pattern.
WPS114, # Forbid names with underscored numbers pattern.
WPS125, # Forbid variable or module names which shadow builtin names. TODO: Collision with flake8-builtins
# WPS: Consistency
WPS303, # Forbid underscores in numbers.
WPS305, # Forbid f strings.
WPS306, # Forbid writing classes without base classes.
WPS318, # Forbid extra indentation. TODO: Collision with standard flake8 indentation check
WPS323, # Forbid % formatting on strings.
WPS324, # Enforce consistent return statements. TODO: Collision with flake8-return
WPS335, # Forbid wrong for loop iter targets.
WPS358, # Forbid using float zeros: 0.0.
WPS362, # Forbid assignment to a subscript slice.
# WPS: Best practices
WPS404, # Forbid complex defaults. TODO: Collision with "B006"
WPS420, # Forbid some python keywords.
WPS421, # Forbid calling some built-in functions.(e.g., print)
WPS429, # Forbid multiple assignments on the same line.
WPS430, # Forbid nested functions.
WPS431, # Forbid nested classes.
WPS435, # Forbid multiplying lists.
# WPS: Refactoring
WPS527, # Require tuples as arguments for frozenset.
# WPS: OOP
WPS602, # Forbid @staticmethod decorator.
12 changes: 9 additions & 3 deletions src/python/review/inspectors/flake8/flake8.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]:
f'--format={FORMAT}',
f'--config={PATH_FLAKE8_CONFIG}',
'--max-complexity', '0',
path
path,
]
output = run_in_subprocess(command)
return cls.parse(output)
Expand Down Expand Up @@ -63,11 +63,17 @@ def parse(cls, output: str) -> List[BaseIssue]:

@staticmethod
def choose_issue_type(code: str) -> IssueType:
# Handling individual codes
if code in CODE_TO_ISSUE_TYPE:
return CODE_TO_ISSUE_TYPE[code]

code_prefix = re.match(r'^([a-z]+)\d+$', code, re.IGNORECASE).group(1)
issue_type = CODE_PREFIX_TO_ISSUE_TYPE.get(code_prefix)
regex_match = re.match(r'^([A-Z]+)(\d)\d*$', code, re.IGNORECASE)
code_prefix = regex_match.group(1)
first_code_number = regex_match.group(2)

# Handling other issues
issue_type = (CODE_PREFIX_TO_ISSUE_TYPE.get(code_prefix + first_code_number)
or CODE_PREFIX_TO_ISSUE_TYPE.get(code_prefix))
if not issue_type:
logger.warning(f'flake8: {code} - unknown error code')
return IssueType.BEST_PRACTICES
Expand Down
73 changes: 73 additions & 0 deletions src/python/review/inspectors/flake8/issue_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,72 @@

# builtin naming
'A003': IssueType.BEST_PRACTICES,

# WPS: Naming
"WPS117": IssueType.CODE_STYLE, # Forbid naming variables self, cls, or mcs.
"WPS125": IssueType.ERROR_PRONE, # Forbid variable or module names which shadow builtin names.

# WPS: Consistency
"WPS300": IssueType.CODE_STYLE, # Forbid imports relative to the current folder.
"WPS301": IssueType.CODE_STYLE, # Forbid imports like import os.path.
"WPS304": IssueType.CODE_STYLE, # Forbid partial floats like .05 or 23..
"WPS310": IssueType.BEST_PRACTICES, # Forbid uppercase X, O, B, and E in numbers.
"WPS313": IssueType.CODE_STYLE, # Enforce separation of parenthesis from keywords with spaces.
"WPS317": IssueType.CODE_STYLE, # Forbid incorrect indentation for parameters.
"WPS318": IssueType.CODE_STYLE, # Forbid extra indentation.
"WPS319": IssueType.CODE_STYLE, # Forbid brackets in the wrong position.
"WPS320": IssueType.CODE_STYLE, # Forbid multi-line function type annotations.
"WPS321": IssueType.CODE_STYLE, # Forbid uppercase string modifiers.
"WPS324": IssueType.ERROR_PRONE, # If any return has a value, all return nodes should have a value.
"WPS325": IssueType.ERROR_PRONE, # If any yield has a value, all yield nodes should have a value.
"WPS326": IssueType.ERROR_PRONE, # Forbid implicit string concatenation.
"WPS329": IssueType.ERROR_PRONE, # Forbid meaningless except cases.
"WPS330": IssueType.ERROR_PRONE, # Forbid unnecessary operators in your code.
"WPS338": IssueType.BEST_PRACTICES, # Forbid incorrect order of methods inside a class.
"WPS339": IssueType.CODE_STYLE, # Forbid meaningless zeros.
"WPS340": IssueType.CODE_STYLE, # Forbid extra + signs in the exponent.
"WPS341": IssueType.CODE_STYLE, # Forbid lowercase letters as hex numbers.
"WPS343": IssueType.CODE_STYLE, # Forbid uppercase complex number suffix.
"WPS344": IssueType.ERROR_PRONE, # Forbid explicit division (or modulo) by zero.
"WPS347": IssueType.ERROR_PRONE, # Forbid imports that may cause confusion outside of the module.
"WPS348": IssueType.CODE_STYLE, # Forbid starting lines with a dot.
"WPS350": IssueType.CODE_STYLE, # Enforce using augmented assign pattern.
"WPS355": IssueType.CODE_STYLE, # Forbid useless blank lines before and after brackets.
"WPS361": IssueType.CODE_STYLE, # Forbids inconsistent newlines in comprehensions.

# WPS: Best practices
"WPS405": IssueType.ERROR_PRONE, # Forbid anything other than ast.Name to define loop variables.
"WPS406": IssueType.ERROR_PRONE, # Forbid anything other than ast.Name to define contexts.
"WPS408": IssueType.ERROR_PRONE, # Forbid using the same logical conditions in one expression.
"WPS414": IssueType.ERROR_PRONE, # Forbid tuple unpacking with side-effects.
"WPS415": IssueType.ERROR_PRONE, # Forbid the same exception class in multiple except blocks.
"WPS416": IssueType.ERROR_PRONE, # Forbid yield keyword inside comprehensions.
"WPS417": IssueType.ERROR_PRONE, # Forbid duplicate items in hashes.
"WPS418": IssueType.ERROR_PRONE, # Forbid exceptions inherited from BaseException.
"WPS419": IssueType.ERROR_PRONE, # Forbid multiple returning paths with try / except case.
"WPS424": IssueType.ERROR_PRONE, # Forbid BaseException exception.
"WPS426": IssueType.ERROR_PRONE, # Forbid lambda inside loops.
"WPS432": IssueType.CODE_STYLE, # Forbid magic numbers.
"WPS433": IssueType.CODE_STYLE, # Forbid imports nested in functions.
"WPS439": IssueType.ERROR_PRONE, # Forbid Unicode escape sequences in binary strings.
"WPS440": IssueType.ERROR_PRONE, # Forbid overlapping local and block variables.
"WPS441": IssueType.ERROR_PRONE, # Forbid control variables after the block body.
"WPS442": IssueType.ERROR_PRONE, # Forbid shadowing variables from outer scopes.
"WPS443": IssueType.ERROR_PRONE, # Forbid explicit unhashable types of asset items and dict keys.
"WPS445": IssueType.ERROR_PRONE, # Forbid incorrectly named keywords in starred dicts.
"WPS448": IssueType.ERROR_PRONE, # Forbid incorrect order of except.
"WPS449": IssueType.ERROR_PRONE, # Forbid float keys.
"WPS456": IssueType.ERROR_PRONE, # Forbids using float("NaN") construct to generate NaN.
"WPS457": IssueType.ERROR_PRONE, # Forbids use of infinite while True: loops.
"WPS458": IssueType.ERROR_PRONE, # Forbids to import from already imported modules.

# WPS: Refactoring
"WPS524": IssueType.ERROR_PRONE, # Forbid misrefactored self assignment.

# WPS: OOP
"WPS601": IssueType.ERROR_PRONE, # Forbid shadowing class level attributes with instance level attributes.
"WPS613": IssueType.ERROR_PRONE, # Forbid super() with incorrect method or property access.
"WPS614": IssueType.ERROR_PRONE, # Forbids descriptors in regular functions.
}

CODE_PREFIX_TO_ISSUE_TYPE: Dict[str, IssueType] = {
Expand All @@ -30,4 +96,11 @@
'F': IssueType.BEST_PRACTICES, # standard flake8
'C': IssueType.BEST_PRACTICES, # flake8-comprehensions
'SC': IssueType.BEST_PRACTICES, # flake8-spellcheck

'WPS1': IssueType.CODE_STYLE, # WPS type: Naming
'WPS2': IssueType.COMPLEXITY, # WPS type: Complexity
'WPS3': IssueType.BEST_PRACTICES, # WPS type: Consistency
'WPS4': IssueType.BEST_PRACTICES, # WPS type: Best practices
'WPS5': IssueType.BEST_PRACTICES, # WPS type: Refactoring
'WPS6': IssueType.BEST_PRACTICES, # WPS type: OOP
}
21 changes: 12 additions & 9 deletions test/python/inspectors/test_flake8_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
FILE_NAMES_AND_N_ISSUES = [
('case0_spaces.py', 5),
('case1_simple_valid_program.py', 0),
('case2_boolean_expressions.py', 1),
('case3_redefining_builtin.py', 1),
('case2_boolean_expressions.py', 8),
('case3_redefining_builtin.py', 2),
('case4_naming.py', 10),
('case5_returns.py', 1),
('case6_unused_variables.py', 3),
('case8_good_class.py', 0),
('case7_empty_lines.py', 0),
('case10_unused_variable_in_loop.py', 1),
('case13_complex_logic.py', 3),
('case13_complex_logic_2.py', 1),
('case13_complex_logic.py', 12),
('case13_complex_logic_2.py', 3),
('case11_redundant_parentheses.py', 0),
('case14_returns_errors.py', 4),
('case16_comments.py', 0),
Expand Down Expand Up @@ -47,17 +47,20 @@ def test_file_with_issues(file_name: str, n_issues: int):
('case0_spaces.py', IssuesTestInfo(n_code_style=5)),
('case1_simple_valid_program.py', IssuesTestInfo()),
('case2_boolean_expressions.py', IssuesTestInfo(n_code_style=1,
n_cc=8)),
('case3_redefining_builtin.py', IssuesTestInfo(n_error_prone=1)),
n_best_practices=4,
n_error_prone=1,
n_cc=8,
n_other_complexity=2)),
('case3_redefining_builtin.py', IssuesTestInfo(n_error_prone=2)),
('case4_naming.py', IssuesTestInfo(n_code_style=7, n_best_practices=3, n_cc=5)),
('case6_unused_variables.py', IssuesTestInfo(n_best_practices=3,
n_cc=1)),
('case8_good_class.py', IssuesTestInfo(n_cc=1)),
('case7_empty_lines.py', IssuesTestInfo(n_cc=4)),
('case7_empty_lines.py', IssuesTestInfo(n_cc=5)),
('case10_unused_variable_in_loop.py', IssuesTestInfo(n_best_practices=1,
n_cc=1)),
('case13_complex_logic.py', IssuesTestInfo(n_cc=6)),
('case13_complex_logic_2.py', IssuesTestInfo(n_cc=2)),
('case13_complex_logic.py', IssuesTestInfo(n_cc=5, n_other_complexity=10)),
('case13_complex_logic_2.py', IssuesTestInfo(n_cc=2, n_other_complexity=2)),
('case14_returns_errors.py', IssuesTestInfo(n_best_practices=1,
n_error_prone=3,
n_cc=4)),
Expand Down
2 changes: 1 addition & 1 deletion test/python/inspectors/test_pylint_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
('case0_spaces.py', 3),
('case1_simple_valid_program.py', 0),
('case2_boolean_expressions.py', 3),
('case3_redefining_builtin.py', 1),
('case3_redefining_builtin.py', 2),
('case4_naming.py', 3),
('case5_returns.py', 1),
('case6_unused_variables.py', 4),
Expand Down
82 changes: 33 additions & 49 deletions test/resources/inspectors/python/case13_complex_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ def max_of_three(a, b, c):
return a


FEW_UNITS_NUMBER = 9
PACK_UNITS_NUMBER = 49
HORDE_UNITS_NUMBER = 499
SWARM_UNITS_NUMBER = 999


def army_of_units(count):
if count < 1:
print("no army")
elif count <= 9:
elif count <= FEW_UNITS_NUMBER:
print('few')
elif count <= 49:
elif count <= PACK_UNITS_NUMBER:
print('pack')
elif count <= 499:
elif count <= HORDE_UNITS_NUMBER:
print("horde")
elif count <= 999:
elif count <= SWARM_UNITS_NUMBER:
print('swarm')
else:
print('legion')
Expand Down Expand Up @@ -65,60 +71,38 @@ def determine_strange_quark(spin, charge):
print("Higgs boson Boson")


SHEEP_PRICE = 6769
COW_PRICE = 3848
PIG_PRICE = 1296
GOAT_PRICE = 678
DOG_PRICE = 137
CHICKEN_PRICE = 23


def buy_animal(money):
if money >= 6769:
print(str(money // 6769) + " sheep")
elif money >= 3848:
if money >= SHEEP_PRICE:
number_of_sheep = money // SHEEP_PRICE
print(f"{number_of_sheep} sheep")
elif money >= COW_PRICE:
print("1 cow")
elif money >= 1296:
if money // 1296 == 1:
elif money >= PIG_PRICE:
if money // PIG_PRICE == 1:
print("1 pig")
else:
print("2 pigs")
elif money >= 678:
elif money >= GOAT_PRICE:
print("1 goat")
elif money >= 137:
if money // 137 == 1:
elif money >= DOG_PRICE:
if money // DOG_PRICE == 1:
print("1 dog")
else:
print(str(money // 137) + " dogs")
elif money >= 23:
if money // 23 == 1:
number_of_dogs = money // DOG_PRICE
print(f"{number_of_dogs} dogs")
elif money >= CHICKEN_PRICE:
if money // CHICKEN_PRICE == 1:
print("1 chicken")
else:
print(str(money // 23) + " chickens")
number_of_chickens = money // CHICKEN_PRICE
print(f"{number_of_chickens} chickens")
else:
print("None")


def fun_with_complex_logic(a, b, c):
d = 0
if a > 10:
d = 30
elif a < 100:
d = 50
elif a == 300 and b == 40:
for i in range(9):
a += i
elif a == 200:
if b > 300 and c < 50:
d = 400
else:
d = 800
elif a == 2400:
if b > 500 and c < 50:
d = 400
else:
d = 800
elif a == 1000:
if b == 900:
if c == 1000:
d = 10000
else:
d = 900
elif c == 300:
d = 1000
elif a + b == 400:
d = 400
print(d)
return d
16 changes: 10 additions & 6 deletions test/resources/inspectors/python/case13_complex_logic_2.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
elements = list(input('Enter cells: '))
y = 0
o = 0

CROSS_SYMBOL = 'X'
NOUGHT_SYMBOL = 'O'

for x in elements:
if x == 'X':
if x == CROSS_SYMBOL:
y = y + 1
elif x == 'O':
elif x == NOUGHT_SYMBOL:
o = o + 1

odds = abs(y - o)
Expand All @@ -22,8 +26,8 @@

full_field = [up_row, up_col, mid_row, mid_col, down_row, down_col, diagonal_1, diagonal_2]

x_win = ['X', 'X', 'X']
o_win = ['O', 'O', 'O']
x_win = [CROSS_SYMBOL, CROSS_SYMBOL, CROSS_SYMBOL]
o_win = [NOUGHT_SYMBOL, NOUGHT_SYMBOL, NOUGHT_SYMBOL]

field = f"""
---------
Expand All @@ -35,10 +39,10 @@
if odds < 2:
if x_win in full_field and o_win not in full_field:
print(field)
print('X wins')
print(f'{CROSS_SYMBOL} wins')
elif o_win in full_field and x_win not in full_field:
print(field)
print('O wins')
print(f'{NOUGHT_SYMBOL} wins')
elif o_win in full_field and x_win in full_field:
print(field)
print('Impossible')
Expand Down
5 changes: 4 additions & 1 deletion test/resources/inspectors/python/case3_redefining_builtin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
a = int(input())
b = int(input())
list = list(filter(lambda x: x % 3 == 0, range(a, b + 1)))

range = range(a, b + 1)

list = list(filter(lambda x: x % 3 == 0, range))
print(sum(list) / len(list))
2 changes: 1 addition & 1 deletion test/resources/inspectors/python/case4_naming.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def myFun(self):
print('hello 1')

def my_fun(self, QQ):
print('hello 2' + QQ)
print('hello 2 {}'.format(QQ))

@classmethod
def test_fun(first):
Expand Down
Loading