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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
from collections import defaultdict
import ast
import re


class NestingValidator(ast.NodeVisitor):
    def __init__(self, max_nesting, problems, depth=0):
        self.max_nesting = max_nesting
        self.problems = problems
        self.depth = depth

    def _validate_depth(self, node):
        if self.depth > self.max_nesting:
            self.problems[node.lineno].add(
                'nesting too deep ({} > {})'.format(
                    self.depth, self.max_nesting))

    def _count_depth(self, node):
        self._validate_depth(node)
        child_depth = self.depth + 1
        for child in ast.iter_child_nodes(node):
            nesting_validator = NestingValidator(
                self.max_nesting, self.problems, child_depth)
            nesting_validator.visit(child)

    def generic_visit(self, node):
        if isinstance(node, (ast.stmt, ast.excepthandler)):
            self._validate_depth(node)
        ast.NodeVisitor.generic_visit(self, node)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class IndentationValidator(ast.NodeVisitor):
    def __init__(self, code_lines, indentation_size, problems, depth=0):
        self.code_lines = code_lines
        self.indentation_size = indentation_size
        self.problems = problems
        self.depth = depth

    def _validate_indentation(self, node):
        line = self.code_lines[node.lineno]
        indent_size_needed = self.depth * self.indentation_size
        indent_size_actual = len(line) - len(line.lstrip())
        if indent_size_needed != indent_size_actual:
            self.problems[node.lineno].add(
                'indentation is {} instead of {}'.format(
                    indent_size_actual, indent_size_needed))

    def generic_visit(self, node):
        if isinstance(node, (ast.stmt,
                             ast.excepthandler)):
            self._validate_indentation(node)
        ast.NodeVisitor.generic_visit(self, node)

    def _count_depth(self, node):
        self._validate_indentation(node)
        child_depth = self.depth+1
        for child in ast.iter_child_nodes(node):
            indentation_validator = IndentationValidator(
                self.code_lines, self.indentation_size, self.problems,
                child_depth)
            indentation_validator.visit(child)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class ClassMethodValidator(ast.NodeVisitor):
    def __init__(self, max_methods, problems):
        self.max_methods = max_methods
        self.problems = problems

    def visit_ClassDef(self, cls):
        num_methods = 0
        for node in ast.iter_child_nodes(cls):
            if isinstance(node, ast.FunctionDef):
                num_methods += 1

        if num_methods > self.max_methods:
            self.problems[cls.lineno].add(
                'too many methods in class({} > {})'.format(
                    num_methods, self.max_methods))

        self.generic_visit(cls)


class FunctionArityValidator(ast.NodeVisitor):
    def __init__(self, max_arity, problems):
        self.max_arity = max_arity
        self.problems = problems

    def _validate_arity(self, fn):
        num_args = len(fn.args.args) + len(fn.args.kwonlyargs)
        if num_args > self.max_arity:
            self.problems[fn.lineno].add(
                'too many arguments({} > {})'.format(num_args, self.max_arity))
        self.generic_visit(fn)

    visit_FunctionDef = _validate_arity
    visit_Lambda = _validate_arity


# TODO
class FunctionLinesValidator(ast.NodeVisitor):
    def __init__(self, max_lines, problems):
        self.max_lines = max_lines
        self.problems = problems

    def visit_FunctionDef(self, fn):
        seen_lines = set()
        else_nodes = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try)
        else_lines = 0
        for child in ast.walk(fn):
            if isinstance(child, (ast.expr, ast.stmt,
                                  ast.excepthandler, ast.arg)):
                seen_lines.add(child.lineno)

            if isinstance(child, else_nodes) and len(child.orelse) > 0:
                else_lines += 1

        logical_lines = len(seen_lines) + else_lines
        if logical_lines > self.max_lines:
            self.problems[fn.lineno].add(
                'method with too many lines ({} > {})'.format(
                    logical_lines, self.max_lines))

        self.generic_visit(fn)


def critic_line_length(code_lines, max_length, problems):
    for line_num, line in code_lines.items():
        if len(line) > max_length:
            problems[line_num].add('line too long ({} > {})'.format(
                len(line), max_length))


# TODO
def critic_semicolons(ast_tree, code_lines, problems):
    lines_seen = set()
    mulptiple_expression_lines = set()

    for node in ast.walk(ast_tree):
        if isinstance(node, ast.stmt):
            if node.lineno in lines_seen:
                mulptiple_expression_lines.add(node.lineno)
            else:
                lines_seen.add(node.lineno)

    for line in mulptiple_expression_lines:
        if code_lines[line].find(';') != -1:
            problems[line].add('multiple expressions on the same line')


# TODO
def critic_nesting(ast_tree, max_nesting, problems):
    scopes = (ast.If, ast.While, ast.For, ast.With, ast.Try,
              ast.ExceptHandler, ast.ClassDef, ast.FunctionDef)
    NestingValidator(max_nesting, problems).visit(ast_tree)


# TODO
def critic_indentation(ast_tree, code_lines, indentation_size, problems):
    IndentationValidator(
        code_lines, indentation_size, problems).visit(ast_tree)


def critic_methods_per_class(ast_tree, max_methods, problems):
    ClassMethodValidator(max_methods, problems).visit(ast_tree)


def critic_max_arity(ast_tree, max_arity, problems):
    FunctionArityValidator(max_arity, problems).visit(ast_tree)


def critic_trailing_whitespace(code_lines, problems):
    trailing_whitespace_pattern = r'\s$'
    for line_num, line in code_lines.items():
        if re.search(trailing_whitespace_pattern, line):
            problems[line_num].add('trailing whitespace')


def critic_max_lines_per_function(ast_tree, max_lines, problems):
    FunctionLinesValidator(max_lines, problems).visit(ast_tree)


def critic(code, **rules):
    default_rules = {
        'line_length': 79,
        'forbid_semicolons': True,
        'max_nesting': None,
        'indentation_size': 4,
        'methods_per_class': None,
        'max_arity': None,
        'forbid_trailing_whitespace': True,
        'max_lines_per_function': None
    }

    for rule in default_rules.keys():
        if rule not in rules.keys():
            rules[rule] = default_rules[rule]

    problems = defaultdict(set)
    ast_tree = ast.parse(code)
    code_lines = dict(enumerate(code.splitlines(), 1))

    critic_line_length(code_lines, rules['line_length'], problems)

    if rules['forbid_semicolons']:
        critic_semicolons(ast_tree, code_lines, problems)

    if rules['max_nesting'] is not None:
        critic_nesting(ast_tree, rules['max_nesting'], problems)

    critic_indentation(
        ast_tree, code_lines, rules['indentation_size'], problems)

    if rules['methods_per_class'] is not None:
        critic_methods_per_class(ast_tree, rules['methods_per_class'],
                                 problems)

    if rules['max_arity'] is not None:
        critic_max_arity(ast_tree, rules['max_arity'], problems)

    if rules['forbid_trailing_whitespace']:
        critic_trailing_whitespace(code_lines, problems)

    if rules['max_lines_per_function'] is not None:
        critic_max_lines_per_function(
            ast_tree, rules['max_lines_per_function'], problems)

    return problems

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

.....FF....
======================================================================
FAIL: test_max_lines_per_function (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 (7 > 3)'
Items in the second set but not the first:
'method with too many lines (6 > 3)'

======================================================================
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 (13 > 5)'

----------------------------------------------------------------------
Ran 11 tests in 0.117s

FAILED (failures=2)

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

Николай обнови решението на 17.05.2016 23: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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
from collections import defaultdict
import ast
import re


class IndentationValidator(ast.NodeVisitor):
    def __init__(self, code_lines, indentation_size, problems, depth=0):
        self.code_lines = code_lines
        self.indentation_size = indentation_size
        self.problems = problems
        self.depth = depth

    def generic_visit(self, node):
        if isinstance(node, (ast.expr, ast.stmt,
                             ast.excepthandler)):
            line = self.code_lines[node.lineno]
            indent_size_needed = self.depth * self.indentation_size
            indent_size_actual = len(line) - len(line.lstrip())
            if indent_size_needed != indent_size_actual:
                self.problems[node.lineno].add(
                    'indentation is {} instead of {}'.format(
                        indent_size_actual, indent_size_needed))
        ast.NodeVisitor.generic_visit(self, node)

    def _count_depth(self, node):
        child_depth = self.depth+1
        for child in ast.iter_child_nodes(node):
            indentation_validator = IndentationValidator(
                self.code_lines, self.indentation_size, self.problems,
                child_depth)
            indentation_validator.visit(child)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class ClassMethodValidator(ast.NodeVisitor):
    def __init__(self, max_methods, problems):
        self.max_methods = max_methods
        self.problems = problems

    def visit_ClassDef(self, cls):
        num_methods = 0
        for node in ast.iter_child_nodes(cls):
            if isinstance(node, ast.FunctionDef):
                num_methods += 1

        if num_methods > self.max_methods:
            self.problems[cls.lineno].add(
                'too many methods in class({} > {})'.format(
                    num_methods, self.max_methods))

        self.generic_visit(cls)


class FunctionArityValidator(ast.NodeVisitor):
    def __init__(self, max_arity, problems):
        self.max_arity = max_arity
        self.problems = problems

    def _validate_arity(self, fn):
        num_args = len(fn.args.args) + len(fn.args.kwonlyargs)
        if num_args > self.max_arity:
            self.problems[fn.lineno].add(
                'too many arguments({} > {})'.format(num_args, self.max_arity))
        self.generic_visit(fn)

    visit_FunctionDef = _validate_arity
    visit_Lambda = _validate_arity


# TODO
class FunctionLinesValidator(ast.NodeVisitor):
    def __init__(self, max_lines, problems):
        self.max_lines = max_lines
        self.problems = problems

    def visit_FunctionDef(self, fn):
        seen_lines = set()
        else_nodes = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try)
        else_lines = 0
        for child in ast.walk(fn):
            if isinstance(child, (ast.expr, ast.stmt,
                                  ast.excepthandler, ast.arg)):
                seen_lines.add(child.lineno)

            if isinstance(child, else_nodes) and len(child.orelse) > 0:
                else_lines += 1

        logical_lines = len(seen_lines) + else_lines
        if logical_lines > self.max_lines:
            self.problems[fn.lineno].add(
                'method with too many lines ({} > {})'.format(
                    logical_lines, self.max_lines))

        self.generic_visit(fn)


def critic_line_length(code_lines, max_length, problems):
    for line_num, line in code_lines.items():
        if len(line) > max_length:
            problems[line_num].add('line too long ({} > {})'.format(
                len(line), max_length))


# TODO
def critic_semicolons(ast_tree, code_lines, problems):
    lines_seen = set()
    mulptiple_expression_lines = set()

    for node in ast.walk(ast_tree):
        if isinstance(node, ast.stmt):
            if node.lineno in lines_seen:
                mulptiple_expression_lines.add(node.lineno)
            else:
                lines_seen.add(node.lineno)

    for line in mulptiple_expression_lines:
        if code_lines[line].find(';') != -1:
            problems[line].add('multiple expressions on the same line')


# TODO
def critic_nesting(ast_tree, max_nesting, problems):
    scopes = (ast.If, ast.While, ast.For, ast.With, ast.Try,
              ast.ExceptHandler, ast.ClassDef, ast.FunctionDef)
    fringe = [(ast_tree, 0, 1)]
    found_problems = {}

    while len(fringe) != 0:
        node, depth, lineno = fringe.pop()

        if lineno in found_problems.keys():
            continue

        if depth > max_nesting:
            if isinstance(node, ast.stmt):
                lineno = node.lineno
            else:
                lineno += 1
            found_problems[lineno].add(
                'nesting too deep ({} > {})'.format(depth, max_nesting))
            continue

        if isinstance(node, scopes):
            depth += 1
            lineno = node.lineno

        for child in ast.iter_child_nodes(node):
            fringe.append((child, depth, lineno))


# TODO
def critic_indentation(ast_tree, code_lines, indentation_size, problems):
    IndentationValidator(code_lines, indentation_size,
                         problems).visit(ast_tree)


def critic_methods_per_class(ast_tree, max_methods, problems):
    ClassMethodValidator(max_methods, problems).visit(ast_tree)


def critic_max_arity(ast_tree, max_arity, problems):
    FunctionArityValidator(max_arity, problems).visit(ast_tree)


def critic_trailing_whitespace(code_lines, problems):
    trailing_whitespace_pattern = r'\s$'
    for line_num, line in code_lines.items():
        if re.search(trailing_whitespace_pattern, line):
            problems[line_num].add('trailing whitespace')


def critic_max_lines_per_function(ast_tree, max_lines, problems):
    FunctionLinesValidator(max_lines, problems).visit(ast_tree)


def critic(code, **rules):
    default_rules = {
        'line_length': 79,
        'forbid_semicolons': True,
        'max_nesting': None,
        'indentation_size': 4,
        'methods_per_class': None,
        'max_arity': None,
        'forbid_trailing_whitespace': True,
        'max_lines_per_function': None
    }

    for rule in default_rules.keys():
        if rule not in rules.keys():
            rules[rule] = default_rules[rule]

    problems = defaultdict(set)
    ast_tree = ast.parse(code)
    code_lines = dict(enumerate(code.splitlines(), 1))

    critic_line_length(code_lines, rules['line_length'], problems)

    if rules['forbid_semicolons']:
        critic_semicolons(ast_tree, code_lines, problems)

    # if rules['max_nesting'] is not None:
    #     critic_nesting(ast_tree, rules['max_nesting'], problems)

    # critic_indentation(ast_tree, code_lines, rules[
    #     'indentation_size'], problems)

    if rules['methods_per_class'] is not None:
        critic_methods_per_class(ast_tree, rules['methods_per_class'],
                                 problems)

    if rules['max_arity'] is not None:
        critic_max_arity(ast_tree, rules['max_arity'], problems)

    if rules['forbid_trailing_whitespace']:
        critic_trailing_whitespace(code_lines, problems)

    if rules['max_lines_per_function'] is not None:
        critic_max_lines_per_function(
            ast_tree, rules['max_lines_per_function'], problems)

    return problems

Николай обнови решението на 17.05.2016 23:42 (преди над 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
from collections import defaultdict
import ast
import re


class NestingValidator(ast.NodeVisitor):
    def __init__(self, max_nesting, problems, depth=0):
        self.max_nesting = max_nesting
        self.problems = problems
        self.depth = depth

    def _validate_depth(self, node):
        if self.depth > self.max_nesting:
            self.problems[node.lineno].add(
                'nesting too deep ({} > {})'.format(
                    self.depth, self.max_nesting))

    def _count_depth(self, node):
        self._validate_depth(node)
        child_depth = self.depth + 1
        for child in ast.iter_child_nodes(node):
            nesting_validator = NestingValidator(
                self.max_nesting, self.problems, child_depth)
            nesting_validator.visit(child)

    def generic_visit(self, node):
        if isinstance(node, (ast.stmt, ast.excepthandler)):
            self._validate_depth(node)
        ast.NodeVisitor.generic_visit(self, node)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class IndentationValidator(ast.NodeVisitor):
    def __init__(self, code_lines, indentation_size, problems, depth=0):
        self.code_lines = code_lines
        self.indentation_size = indentation_size
        self.problems = problems
        self.depth = depth

    def generic_visit(self, node):
        if isinstance(node, (ast.expr, ast.stmt,
                             ast.excepthandler)):
            line = self.code_lines[node.lineno]
            indent_size_needed = self.depth * self.indentation_size
            indent_size_actual = len(line) - len(line.lstrip())
            if indent_size_needed != indent_size_actual:
                self.problems[node.lineno].add(
                    'indentation is {} instead of {}'.format(
                        indent_size_actual, indent_size_needed))
        ast.NodeVisitor.generic_visit(self, node)

    def _count_depth(self, node):
        child_depth = self.depth+1
        for child in ast.iter_child_nodes(node):
            indentation_validator = IndentationValidator(
                self.code_lines, self.indentation_size, self.problems,
                child_depth)
            indentation_validator.visit(child)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class ClassMethodValidator(ast.NodeVisitor):
    def __init__(self, max_methods, problems):
        self.max_methods = max_methods
        self.problems = problems

    def visit_ClassDef(self, cls):
        num_methods = 0
        for node in ast.iter_child_nodes(cls):
            if isinstance(node, ast.FunctionDef):
                num_methods += 1

        if num_methods > self.max_methods:
            self.problems[cls.lineno].add(
                'too many methods in class({} > {})'.format(
                    num_methods, self.max_methods))

        self.generic_visit(cls)


class FunctionArityValidator(ast.NodeVisitor):
    def __init__(self, max_arity, problems):
        self.max_arity = max_arity
        self.problems = problems

    def _validate_arity(self, fn):
        num_args = len(fn.args.args) + len(fn.args.kwonlyargs)
        if num_args > self.max_arity:
            self.problems[fn.lineno].add(
                'too many arguments({} > {})'.format(num_args, self.max_arity))
        self.generic_visit(fn)

    visit_FunctionDef = _validate_arity
    visit_Lambda = _validate_arity


# TODO
class FunctionLinesValidator(ast.NodeVisitor):
    def __init__(self, max_lines, problems):
        self.max_lines = max_lines
        self.problems = problems

    def visit_FunctionDef(self, fn):
        seen_lines = set()
        else_nodes = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try)
        else_lines = 0
        for child in ast.walk(fn):
            if isinstance(child, (ast.expr, ast.stmt,
                                  ast.excepthandler, ast.arg)):
                seen_lines.add(child.lineno)

            if isinstance(child, else_nodes) and len(child.orelse) > 0:
                else_lines += 1

        logical_lines = len(seen_lines) + else_lines
        if logical_lines > self.max_lines:
            self.problems[fn.lineno].add(
                'method with too many lines ({} > {})'.format(
                    logical_lines, self.max_lines))

        self.generic_visit(fn)


def critic_line_length(code_lines, max_length, problems):
    for line_num, line in code_lines.items():
        if len(line) > max_length:
            problems[line_num].add('line too long ({} > {})'.format(
                len(line), max_length))


# TODO
def critic_semicolons(ast_tree, code_lines, problems):
    lines_seen = set()
    mulptiple_expression_lines = set()

    for node in ast.walk(ast_tree):
        if isinstance(node, ast.stmt):
            if node.lineno in lines_seen:
                mulptiple_expression_lines.add(node.lineno)
            else:
                lines_seen.add(node.lineno)

    for line in mulptiple_expression_lines:
        if code_lines[line].find(';') != -1:
            problems[line].add('multiple expressions on the same line')


# TODO
def critic_nesting(ast_tree, max_nesting, problems):
    scopes = (ast.If, ast.While, ast.For, ast.With, ast.Try,
              ast.ExceptHandler, ast.ClassDef, ast.FunctionDef)
    NestingValidator(max_nesting, problems).visit(ast_tree)


# TODO
def critic_indentation(ast_tree, code_lines, indentation_size, problems):
    IndentationValidator(code_lines, indentation_size,
                         problems).visit(ast_tree)


def critic_methods_per_class(ast_tree, max_methods, problems):
    ClassMethodValidator(max_methods, problems).visit(ast_tree)


def critic_max_arity(ast_tree, max_arity, problems):
    FunctionArityValidator(max_arity, problems).visit(ast_tree)


def critic_trailing_whitespace(code_lines, problems):
    trailing_whitespace_pattern = r'\s$'
    for line_num, line in code_lines.items():
        if re.search(trailing_whitespace_pattern, line):
            problems[line_num].add('trailing whitespace')


def critic_max_lines_per_function(ast_tree, max_lines, problems):
    FunctionLinesValidator(max_lines, problems).visit(ast_tree)


def critic(code, **rules):
    default_rules = {
        'line_length': 79,
        'forbid_semicolons': True,
        'max_nesting': None,
        'indentation_size': 4,
        'methods_per_class': None,
        'max_arity': None,
        'forbid_trailing_whitespace': True,
        'max_lines_per_function': None
    }

    for rule in default_rules.keys():
        if rule not in rules.keys():
            rules[rule] = default_rules[rule]

    problems = defaultdict(set)
    ast_tree = ast.parse(code)
    code_lines = dict(enumerate(code.splitlines(), 1))

    critic_line_length(code_lines, rules['line_length'], problems)

    if rules['forbid_semicolons']:
        critic_semicolons(ast_tree, code_lines, problems)

    if rules['max_nesting'] is not None:
        critic_nesting(ast_tree, rules['max_nesting'], problems)

    # critic_indentation(ast_tree, code_lines, rules[
    #     'indentation_size'], problems)

    if rules['methods_per_class'] is not None:
        critic_methods_per_class(ast_tree, rules['methods_per_class'],
                                 problems)

    if rules['max_arity'] is not None:
        critic_max_arity(ast_tree, rules['max_arity'], problems)

    if rules['forbid_trailing_whitespace']:
        critic_trailing_whitespace(code_lines, problems)

    if rules['max_lines_per_function'] is not None:
        critic_max_lines_per_function(
            ast_tree, rules['max_lines_per_function'], problems)

    return problems

Николай обнови решението на 17.05.2016 23:52 (преди над 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
from collections import defaultdict
import ast
import re


class NestingValidator(ast.NodeVisitor):
    def __init__(self, max_nesting, problems, depth=0):
        self.max_nesting = max_nesting
        self.problems = problems
        self.depth = depth

    def _validate_depth(self, node):
        if self.depth > self.max_nesting:
            self.problems[node.lineno].add(
                'nesting too deep ({} > {})'.format(
                    self.depth, self.max_nesting))

    def _count_depth(self, node):
        self._validate_depth(node)
        child_depth = self.depth + 1
        for child in ast.iter_child_nodes(node):
            nesting_validator = NestingValidator(
                self.max_nesting, self.problems, child_depth)
            nesting_validator.visit(child)

    def generic_visit(self, node):
        if isinstance(node, (ast.stmt, ast.excepthandler)):
            self._validate_depth(node)
        ast.NodeVisitor.generic_visit(self, node)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class IndentationValidator(ast.NodeVisitor):
    def __init__(self, code_lines, indentation_size, problems, depth=0):
        self.code_lines = code_lines
        self.indentation_size = indentation_size
        self.problems = problems
        self.depth = depth

    def _validate_indentation(self, node):
        line = self.code_lines[node.lineno]
        indent_size_needed = self.depth * self.indentation_size
        indent_size_actual = len(line) - len(line.lstrip())
        if indent_size_needed != indent_size_actual:
            self.problems[node.lineno].add(
                'indentation is {} instead of {}'.format(
                    indent_size_actual, indent_size_needed))

    def generic_visit(self, node):
        if isinstance(node, (ast.stmt,
                             ast.excepthandler)):
            self._validate_indentation(node)
        ast.NodeVisitor.generic_visit(self, node)

    def _count_depth(self, node):
        self._validate_indentation(node)
        child_depth = self.depth+1
        for child in ast.iter_child_nodes(node):
            indentation_validator = IndentationValidator(
                self.code_lines, self.indentation_size, self.problems,
                child_depth)
            indentation_validator.visit(child)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth
    visit_FunctionDef = _count_depth
    visit_ClassDef = _count_depth


class ClassMethodValidator(ast.NodeVisitor):
    def __init__(self, max_methods, problems):
        self.max_methods = max_methods
        self.problems = problems

    def visit_ClassDef(self, cls):
        num_methods = 0
        for node in ast.iter_child_nodes(cls):
            if isinstance(node, ast.FunctionDef):
                num_methods += 1

        if num_methods > self.max_methods:
            self.problems[cls.lineno].add(
                'too many methods in class({} > {})'.format(
                    num_methods, self.max_methods))

        self.generic_visit(cls)


class FunctionArityValidator(ast.NodeVisitor):
    def __init__(self, max_arity, problems):
        self.max_arity = max_arity
        self.problems = problems

    def _validate_arity(self, fn):
        num_args = len(fn.args.args) + len(fn.args.kwonlyargs)
        if num_args > self.max_arity:
            self.problems[fn.lineno].add(
                'too many arguments({} > {})'.format(num_args, self.max_arity))
        self.generic_visit(fn)

    visit_FunctionDef = _validate_arity
    visit_Lambda = _validate_arity


# TODO
class FunctionLinesValidator(ast.NodeVisitor):
    def __init__(self, max_lines, problems):
        self.max_lines = max_lines
        self.problems = problems

    def visit_FunctionDef(self, fn):
        seen_lines = set()
        else_nodes = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try)
        else_lines = 0
        for child in ast.walk(fn):
            if isinstance(child, (ast.expr, ast.stmt,
                                  ast.excepthandler, ast.arg)):
                seen_lines.add(child.lineno)

            if isinstance(child, else_nodes) and len(child.orelse) > 0:
                else_lines += 1

        logical_lines = len(seen_lines) + else_lines
        if logical_lines > self.max_lines:
            self.problems[fn.lineno].add(
                'method with too many lines ({} > {})'.format(
                    logical_lines, self.max_lines))

        self.generic_visit(fn)


def critic_line_length(code_lines, max_length, problems):
    for line_num, line in code_lines.items():
        if len(line) > max_length:
            problems[line_num].add('line too long ({} > {})'.format(
                len(line), max_length))


# TODO
def critic_semicolons(ast_tree, code_lines, problems):
    lines_seen = set()
    mulptiple_expression_lines = set()

    for node in ast.walk(ast_tree):
        if isinstance(node, ast.stmt):
            if node.lineno in lines_seen:
                mulptiple_expression_lines.add(node.lineno)
            else:
                lines_seen.add(node.lineno)

    for line in mulptiple_expression_lines:
        if code_lines[line].find(';') != -1:
            problems[line].add('multiple expressions on the same line')


# TODO
def critic_nesting(ast_tree, max_nesting, problems):
    scopes = (ast.If, ast.While, ast.For, ast.With, ast.Try,
              ast.ExceptHandler, ast.ClassDef, ast.FunctionDef)
    NestingValidator(max_nesting, problems).visit(ast_tree)


# TODO
def critic_indentation(ast_tree, code_lines, indentation_size, problems):
    IndentationValidator(
        code_lines, indentation_size, problems).visit(ast_tree)


def critic_methods_per_class(ast_tree, max_methods, problems):
    ClassMethodValidator(max_methods, problems).visit(ast_tree)


def critic_max_arity(ast_tree, max_arity, problems):
    FunctionArityValidator(max_arity, problems).visit(ast_tree)


def critic_trailing_whitespace(code_lines, problems):
    trailing_whitespace_pattern = r'\s$'
    for line_num, line in code_lines.items():
        if re.search(trailing_whitespace_pattern, line):
            problems[line_num].add('trailing whitespace')


def critic_max_lines_per_function(ast_tree, max_lines, problems):
    FunctionLinesValidator(max_lines, problems).visit(ast_tree)


def critic(code, **rules):
    default_rules = {
        'line_length': 79,
        'forbid_semicolons': True,
        'max_nesting': None,
        'indentation_size': 4,
        'methods_per_class': None,
        'max_arity': None,
        'forbid_trailing_whitespace': True,
        'max_lines_per_function': None
    }

    for rule in default_rules.keys():
        if rule not in rules.keys():
            rules[rule] = default_rules[rule]

    problems = defaultdict(set)
    ast_tree = ast.parse(code)
    code_lines = dict(enumerate(code.splitlines(), 1))

    critic_line_length(code_lines, rules['line_length'], problems)

    if rules['forbid_semicolons']:
        critic_semicolons(ast_tree, code_lines, problems)

    if rules['max_nesting'] is not None:
        critic_nesting(ast_tree, rules['max_nesting'], problems)

    critic_indentation(
        ast_tree, code_lines, rules['indentation_size'], problems)

    if rules['methods_per_class'] is not None:
        critic_methods_per_class(ast_tree, rules['methods_per_class'],
                                 problems)

    if rules['max_arity'] is not None:
        critic_max_arity(ast_tree, rules['max_arity'], problems)

    if rules['forbid_trailing_whitespace']:
        critic_trailing_whitespace(code_lines, problems)

    if rules['max_lines_per_function'] is not None:
        critic_max_lines_per_function(
            ast_tree, rules['max_lines_per_function'], problems)

    return problems