timeit

Програмиране с Python

Курс във Факултета по Математика и Информатика към СУ

Решение на Статичен анализ на python код от Десислава Цветкова

Обратно към всички решения

Към профила на Десислава Цветкова

Резултати

  • 8 точки от тестове
  • 0 бонус точки
  • 8 точки общо
  • 9 успешни тест(а)
  • 2 неуспешни тест(а)

Код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import ast
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels, lines_count_elements = CrawlerNesting.get_level_nesting(
        tree_code_ast)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, lines_count_elements, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return dict(errors)


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, lines_count_elements, errors):
    if not forbid_semicolons:
        return

    for line, count in lines_count_elements.items():
        if count > 1:
            errors[line].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if not max_nesting:
        return

    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    all = [k for k, v in lines_levels.items() if lines_levels[k] ==
           lines_levels[func.lineno] and k > func.lineno]
    if all:
        return min(all) - func.lineno
    return max(lines_levels.keys()) - func.lineno


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue)

    @classmethod
    def _get_levels(cls, queue):
        lines_levels = {}
        lines_count_elements = defaultdict(int)
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level
                lines_count_elements[element.lineno] += 1

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels, lines_count_elements

Лог от изпълнението

E.....F....
======================================================================
ERROR: test_dict_nesting (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
KeyError: 2

======================================================================
FAIL: test_multiple_issues_all_over_the_place (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: Items in the first set but not the second:
'method with too many lines (14 > 5)'

----------------------------------------------------------------------
Ran 11 tests in 0.104s

FAILED (failures=1, errors=1)

История (5 версии и 0 коментара)

Десислава обнови решението на 16.05.2016 23:06 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import ast
import re
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'
REGEX_SINGLE = r"'.*?;.*'"
REGEX_DOUBLE = r'".*?;.*"'
REGEX_TRIPLE = r"'''.*?;.*'''"


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels = CrawlerNesting.get_level_nesting(tree_code_ast, max_nesting)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, code, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return errors


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, code, errors):
    if not forbid_semicolons:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        line = re.sub(REGEX_SINGLE, "", line)
        line = re.sub(REGEX_DOUBLE, "", line)
        line = re.sub(REGEX_TRIPLE, "", line)
        if line.find(';') != -1:
            errors[index + 1].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if max_nesting:
        errors_levels = check_nesting(max_nesting, lines_levels)
        for key, error in errors_levels.items():
            errors[key].append(error)


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    level_line_func = lines_levels[func.lineno]
    all = [k for k, v in lines_levels.items() if lines_levels[k] ==
           level_line_func and k > func.lineno]
    if all:
        return min(all) - func.lineno - 1
    return 0


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


def check_nesting(max_nesting, lines_levels):
    errors = defaultdict(list)
    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))
    return errors


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast, max_nesting):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue, max_nesting)

    @classmethod
    def _get_levels(cls, queue, max_nesting):
        lines_levels = {}
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels

Десислава обнови решението на 16.05.2016 23:12 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import ast
import re
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'
REGEX_SINGLE = r"'.*?;.*'"
REGEX_DOUBLE = r'".*?;.*"'
REGEX_TRIPLE = r"'''.*?;.*'''"


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels = CrawlerNesting.get_level_nesting(tree_code_ast, max_nesting)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, code, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return errors


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, code, errors):
    if not forbid_semicolons:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        line = re.sub(REGEX_SINGLE, "", line)
        line = re.sub(REGEX_DOUBLE, "", line)
        line = re.sub(REGEX_TRIPLE, "", line)
        if line.find(';') != -1:
            errors[index + 1].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if max_nesting:
        errors_levels = check_nesting(max_nesting, lines_levels)
        for key, error in errors_levels.items():
            errors[key].append(error)


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    level_line_func = lines_levels[func.lineno]
    all = [k for k, v in lines_levels.items() if lines_levels[k] <=
           level_line_func and k > func.lineno]
    if all:
        return min(all) - func.lineno - 1
    return max(lines_levels.keys()) - func.lineno - 1


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


def check_nesting(max_nesting, lines_levels):
    errors = defaultdict(list)
    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))
    return errors


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast, max_nesting):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue, max_nesting)

    @classmethod
    def _get_levels(cls, queue, max_nesting):
        lines_levels = {}
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels

Десислава обнови решението на 16.05.2016 23:31 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import ast
import re
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'
REGEX_SINGLE = r'''\'.*?;.*?\''''
REGEX_DOUBLE = r'''\".*?;.*?\"'''
REGEX_TRIPLE = r'''\'\'\'.*?;.*?\'\'\''''


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels = CrawlerNesting.get_level_nesting(tree_code_ast, max_nesting)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, code, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return dict(errors)


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, code, errors):
    if not forbid_semicolons:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        #line = re.sub(REGEX_SINGLE, "", line)
        #line = re.sub(REGEX_DOUBLE, "", line)
        #line = re.sub(REGEX_TRIPLE, "", line)
        if line.find(';') != -1:
            errors[index + 1].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if not max_nesting:
        return

    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    level_line_func = lines_levels[func.lineno]
    all = [k for k, v in lines_levels.items() if lines_levels[k] <=
           level_line_func and k > func.lineno]
    if all:
        return min(all) - func.lineno - 1
    return max(lines_levels.keys()) - func.lineno - 1


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast, max_nesting):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue, max_nesting)

    @classmethod
    def _get_levels(cls, queue, max_nesting):
        lines_levels = {}
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels

Десислава обнови решението на 18.05.2016 16:29 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import ast
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels, lines_count_elements = CrawlerNesting.get_level_nesting(
        tree_code_ast)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, lines_count_elements, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return dict(errors)


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, lines_count_elements, errors):
    if not forbid_semicolons:
        return

    for line, count in lines_count_elements.items():
        if count > 1:
            errors[line].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if not max_nesting:
        return

    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    level_line_func = lines_levels[func.lineno]
    all = [k for k, v in lines_levels.items() if lines_levels[k] <=
           level_line_func and k > func.lineno]
    if all:
        return min(all) - func.lineno - 1
    return max(lines_levels.keys()) - func.lineno - 1


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue)

    @classmethod
    def _get_levels(cls, queue):
        lines_levels = {}
        lines_count_elements = defaultdict(int)
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level
                lines_count_elements[element.lineno] += 1

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels, lines_count_elements


Десислава обнови решението на 18.05.2016 16:59 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import ast
from collections import defaultdict

LINE_LONG = 'line too long ({} > {})'
NESTING_DEEP = 'nesting too deep ({} > {})'
MULTIPLE = 'multiple expressions on the same line'
INDENTATION = 'indentation is {} instead of {}'
CLASS = 'too many methods in class({} > {})'
ARITY = 'too many arguments({} > {})'
TRAILING = 'trailing whitespace'
LINES = 'method with too many lines ({} > {})'


def critic(code, max_nesting=None, line_length=79, forbid_semicolons=True,
           indentation_size=4, methods_per_class=None, max_arity=None,
           forbid_trailing_whitespace=True, max_lines_per_function=None):
    errors = defaultdict(list)
    tree_code_ast = ast.parse(code)
    lines_levels, lines_count_elements = CrawlerNesting.get_level_nesting(
        tree_code_ast)
    visitor = ClassFuncLister()
    visitor.visit(tree_code_ast)

    errors_lines_lengths(line_length, code, errors)
    errors_semicolons(forbid_semicolons, lines_count_elements, errors)
    errors_nesting(max_nesting, lines_levels, errors)
    errors_indentation(indentation_size, lines_levels, code, errors)
    errors_methods_classes(methods_per_class, visitor.classes, errors)
    errors_arity(max_arity, visitor, errors)
    errors_trailing(forbid_trailing_whitespace, code, errors)
    errors_lines_per_function(max_lines_per_function,
                              visitor.funcs, lines_levels, errors)
    return dict(errors)


def errors_lines_lengths(line_length, code, errors):
    if not line_length:
        return

    code = code.split('\n')
    for index, line in enumerate(code):
        if len(line) > line_length:
            errors[index + 1].append(LINE_LONG.format(len(line), line_length))


def errors_semicolons(forbid_semicolons, lines_count_elements, errors):
    if not forbid_semicolons:
        return

    for line, count in lines_count_elements.items():
        if count > 1:
            errors[line].append(MULTIPLE)


def errors_nesting(max_nesting, lines_levels, errors):
    if not max_nesting:
        return

    for key, level in lines_levels.items():
        if level > max_nesting:
            errors[key].append(NESTING_DEEP.format(level, max_nesting))


def errors_indentation(indentation_size, lines_levels, code, errors):
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.strip() == '':
            continue
        indentation = len(line) - len(line.lstrip())
        correct_indentation = lines_levels[index + 1] * indentation_size
        if correct_indentation != indentation:
            errors[index + 1].append(INDENTATION.format(indentation,
                                                        correct_indentation))


def errors_methods_classes(methods_per_class, classes, errors):
    if not methods_per_class and methods_per_class != 0:
        return
    classes_count = list(map(class_line_count_function, classes))
    for line, count_methods in classes_count:
        if count_methods > methods_per_class:
            errors[line].append(CLASS.format(count_methods, methods_per_class))


def errors_arity(max_arity, visitor, errors):
    if not max_arity and max_arity != 0:
        return

    for func in visitor.funcs:
        count = len(func.args.args)
        if count > max_arity:
            errors[func.lineno].append(ARITY.format(count, max_arity))


def errors_trailing(forbid_trailing_whitespace, code, errors):
    if not forbid_trailing_whitespace:
        return
    code = code.split('\n')
    for index, line in enumerate(code):
        if line.rstrip() != line:
            errors[index + 1].append(TRAILING)


def errors_lines_per_function(max_lines_per_function, funcs, lines_levels,
                              errors):
    if not max_lines_per_function:
        return

    for func in funcs:
        lines = function_lines(func, lines_levels)
        if lines > max_lines_per_function:
            errors[func.lineno].append(LINES.format(lines,
                                                    max_lines_per_function))


def function_lines(func, lines_levels):
    all = [k for k, v in lines_levels.items() if lines_levels[k] ==
           lines_levels[func.lineno] and k > func.lineno]
    if all:
        return min(all) - func.lineno
    return max(lines_levels.keys()) - func.lineno


def class_line_count_function(cls):
    fs = len(list(filter(lambda x: isinstance(x, ast.FunctionDef), cls.body)))
    return cls.lineno, fs


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self, *args, **kwargs):
        super(ast.NodeVisitor, *args, **kwargs).__init__()
        self.funcs = []
        self.classes = []

    def visit_FunctionDef(self, node):
        self.funcs.append(node)
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes.append(node)
        self.generic_visit(node)


class CrawlerNesting:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef, ast.ClassDef]
    DIFF = -1

    @classmethod
    def get_level_nesting(cls, code_ast):
        queue = [(code_ast, cls.DIFF)]
        return cls._get_levels(queue)

    @classmethod
    def _get_levels(cls, queue):
        lines_levels = {}
        lines_count_elements = defaultdict(int)
        while queue:
            element, level = queue.pop()
            if not isinstance(element, ast.Module):
                lines_levels[element.lineno] = level
                lines_count_elements[element.lineno] += 1

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return lines_levels, lines_count_elements