timeit

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

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

Решение на Статичен анализ на python код от Емил Илиев

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

Към профила на Емил Илиев

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 7 успешни тест(а)
  • 4 неуспешни тест(а)

Код

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
import ast
import re
from collections import defaultdict


class AbstractTreeAnalysis():

    DEFAULT_REULES = {
        "line_length": 79,
        "forbid_semicolons": True,
        "max_nesting": 1,
        "indentation_size": 4,
        "methods_per_class": None,
        "max_arity": 2,
        "forbid_trailing_whitespace": True,
        "max_lines_per_function": None,
    }

    def __init__(self):
        self.result = defaultdict(list)

    def check_line_length(self, code_lines):
        for line in range(len(code_lines)):
            if len(code_lines[line]) > self.DEFAULT_REULES["line_length"]:
                self.result[line + 1].append(
                    self.line_length_error(code_lines[line]))

    def methods_per_class(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["methods_per_class"]):
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    method_counter = 0
                    for func in node.body:
                        if isinstance(func, ast.FunctionDef):
                            method_counter += 1
                    if (method_counter >
                            self.DEFAULT_REULES["methods_per_class"]):
                        self.result[node.lineno].append(
                            self.methods_error(method_counter))

    def arguments_per_method(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["max_arity"]):
            for node in ast.walk(tree):
                arguments_count = 0
                if isinstance(node, ast.FunctionDef):
                    arguments_count += len(node.args.args)
                    arguments_count += len(node.args.defaults)
                    if node.args.kwarg:
                        arguments_count += 1
                    if node.args.vararg:
                        arguments_count += 1
                if self.DEFAULT_REULES["max_arity"] < arguments_count:
                    self.result[node.lineno].append(
                        self.arguments_error(arguments_count))

    def check_for_trailing_whitespace(self, code_lines):
        if (self.allowed_rule(
                self.DEFAULT_REULES["forbid_trailing_whitespace"])):
            for index, item in enumerate(code_lines):
                if re.search("\s+$", item):
                    self.result[index + 1].append('trailing whitespace')

    def check_for_semicolons(self, code_lines):
        if self.allowed_rule(self.DEFAULT_REULES["forbid_semicolons"]):
            for index, item in enumerate(code_lines):
                found = item.find(";")
                last_char = len(item) - 1
                while -1 < found:
                    if found != last_char:
                        self.result[index + 1].append(self.multiline_error())
                        break
                    found = item.find(";", found + 1)

    def max_nesting(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["max_nesting"]):
            for node in ast.walk(tree):
                if isinstance(node, ast.FunctionDef):
                    report = self.check_for_nesting(node)
                    if report[0]:
                        self.result[report[1]].append(
                            self.max_nesting_error(report[2]))

    def generate_result(self, code, **rules):
        self.DEFAULT_REULES.update(rules)
        tree = ast.parse(code)
        code_lines = code.splitlines()
        self.check_line_length(code_lines)
        self.methods_per_class(tree)
        self.arguments_per_method(tree)
        self.check_for_trailing_whitespace(code_lines)
        self.check_for_semicolons(code_lines)
        self.max_nesting(tree)

        return self.result

    # help functions
    def check_for_nesting(self, node, nesting_count=0):
        control_nodes = (ast.If, ast.While, ast.For, ast.With,
                         ast.Try, ast.ExceptHandler)
        line_number = 0
        for body_node in node.body:
            if nesting_count > self.DEFAULT_REULES["max_nesting"]:
                line_number = body_node.lineno
            if isinstance(body_node, control_nodes):
                return self.check_for_nesting(body_node, nesting_count + 1)
        return (nesting_count > self.DEFAULT_REULES["max_nesting"],
                line_number, nesting_count)

    def line_length_error(self, line):
        return "line too long ({} > {})".format(
            len(line), self.DEFAULT_REULES["line_length"])

    def methods_error(self, methods_number):
        return "too many methods in class({} > {})".format(
            methods_number, self.DEFAULT_REULES["methods_per_class"])

    def arguments_error(self, arguments_number):
        return "too many arguments({} > {})".format(
            arguments_number, self.DEFAULT_REULES["max_arity"])

    def multiline_error(self):
        return "multiple expressions on the same line"

    def max_nesting_error(self, current_nesting):
        return "nesting too deep ({} > {})".format(
            current_nesting, self.DEFAULT_REULES["max_nesting"])

    def allowed_rule(self, rule):
        if rule is None or rule == 0:
            return False
        return True


def critic(code, **rules):
    abstract_tree = AbstractTreeAnalysis()
    return abstract_tree.generate_result(code, **rules)

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

..F..FF.F..
======================================================================
FAIL: test_indentation (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: 0 != 1

======================================================================
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: 0 != 1

======================================================================
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: 3 != 5

======================================================================
FAIL: test_too_deep_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
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 11 tests in 0.103s

FAILED (failures=4)

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

Емил обнови решението на 18.05.2016 10:44 (преди над 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
import ast
import re
from collections import defaultdict


class AbstractTreeAnalysis():

    DEFAULT_REULES = {
        "line_length": 79,
        "forbid_semicolons": True,
        "max_nesting": 1,
        "indentation_size": 4,
        "methods_per_class": None,
        "max_arity": 2,
        "forbid_trailing_whitespace": True,
        "max_lines_per_function": None,
    }

    def __init__(self):
        self.result = defaultdict(list)

    def check_line_length(self, code_lines):
        for line in range(len(code_lines)):
            if len(code_lines[line]) > self.DEFAULT_REULES["line_length"]:
                self.result[line + 1].append(
                    self.line_length_error(code_lines[line]))

    def methods_per_class(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["methods_per_class"]):
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    method_counter = 0
                    for func in node.body:
                        if isinstance(func, ast.FunctionDef):
                            method_counter += 1
                    if (method_counter >
                            self.DEFAULT_REULES["methods_per_class"]):
                        self.result[node.lineno].append(
                            self.methods_error(method_counter))

    def arguments_per_method(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["max_arity"]):
            for node in ast.walk(tree):
                arguments_count = 0
                if isinstance(node, ast.FunctionDef):
                    arguments_count += len(node.args.args)
                    arguments_count += len(node.args.defaults)
                    if node.args.kwarg:
                        arguments_count += 1
                    if node.args.vararg:
                        arguments_count += 1
                if self.DEFAULT_REULES["max_arity"] < arguments_count:
                    self.result[node.lineno].append(
                        self.arguments_error(arguments_count))

    def check_for_trailing_whitespace(self, code_lines):
        if (self.allowed_rule(
                self.DEFAULT_REULES["forbid_trailing_whitespace"])):
            for index, item in enumerate(code_lines):
                if re.search("\s+$", item):
                    self.result[index + 1].append('trailing whitespace')

    def check_for_semicolons(self, code_lines):
        if self.allowed_rule(self.DEFAULT_REULES["forbid_semicolons"]):
            for index, item in enumerate(code_lines):
                found = item.find(";")
                last_char = len(item) - 1
                while -1 < found:
                    if found != last_char:
                        self.result[index + 1].append(self.multiline_error())
                        break
                    found = item.find(";", found + 1)

    def max_nesting(self, tree):
        if self.allowed_rule(self.DEFAULT_REULES["max_nesting"]):
            for node in ast.walk(tree):
                if isinstance(node, ast.FunctionDef):
                    report = self.check_for_nesting(node)
                    if report[0]:
                        self.result[report[1]].append(
                            self.max_nesting_error(report[2]))

    def generate_result(self, code, **rules):
        self.DEFAULT_REULES.update(rules)
        tree = ast.parse(code)
        code_lines = code.splitlines()
        self.check_line_length(code_lines)
        self.methods_per_class(tree)
        self.arguments_per_method(tree)
        self.check_for_trailing_whitespace(code_lines)
        self.check_for_semicolons(code_lines)
        self.max_nesting(tree)

        return self.result

    # help functions
    def check_for_nesting(self, node, nesting_count=0):
        control_nodes = (ast.If, ast.While, ast.For, ast.With,
                         ast.Try, ast.ExceptHandler)
        line_number = 0
        for body_node in node.body:
            if nesting_count > self.DEFAULT_REULES["max_nesting"]:
                line_number = body_node.lineno
            if isinstance(body_node, control_nodes):
                return self.check_for_nesting(body_node, nesting_count + 1)
        return (nesting_count > self.DEFAULT_REULES["max_nesting"],
                line_number, nesting_count)

    def line_length_error(self, line):
        return "line too long ({} > {})".format(
            len(line), self.DEFAULT_REULES["line_length"])

    def methods_error(self, methods_number):
        return "too many methods in class({} > {})".format(
            methods_number, self.DEFAULT_REULES["methods_per_class"])

    def arguments_error(self, arguments_number):
        return "too many arguments({} > {})".format(
            arguments_number, self.DEFAULT_REULES["max_arity"])

    def multiline_error(self):
        return "multiple expressions on the same line"

    def max_nesting_error(self, current_nesting):
        return "nesting too deep ({} > {})".format(
            current_nesting, self.DEFAULT_REULES["max_nesting"])

    def allowed_rule(self, rule):
        if rule is None or rule == 0:
            return False
        return True


def critic(code, **rules):
    abstract_tree = AbstractTreeAnalysis()
    return abstract_tree.generate_result(code, **rules)