1# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
2# http://www.logilab.fr/ -- mailto:contact@logilab.fr
3#
4# This program is free software; you can redistribute it and/or modify it under
5# the terms of the GNU General Public License as published by the Free Software
6# Foundation; either version 2 of the License, or (at your option) any later
7# version.
8#
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16""" Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
17 http://www.logilab.fr/ -- mailto:contact@logilab.fr
18
19Raw metrics checker
20"""
21
22import tokenize
23
24# pylint now requires pylint >= 2.2, so this is no longer necessary
25#if not hasattr(tokenize, 'NL'):
26#    raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
27
28from logilab.common.ureports import Table
29
30from pylint.interfaces import ITokenChecker
31from pylint.utils import EmptyReport
32from pylint.checkers import BaseTokenChecker
33from pylint.reporters import diff_string
34
35def report_raw_stats(sect, stats, old_stats):
36    """calculate percentage of code / doc / comment / empty
37    """
38    total_lines = stats['total_lines']
39    if not total_lines:
40        raise EmptyReport()
41    sect.description = '%s lines have been analyzed' % total_lines
42    lines = ('type', 'number', '%', 'previous', 'difference')
43    for node_type in ('code', 'docstring', 'comment', 'empty'):
44        key = node_type + '_lines'
45        total = stats[key]
46        percent = float(total * 100) / total_lines
47        old = old_stats.get(key, None)
48        if old is not None:
49            diff_str = diff_string(old, total)
50        else:
51            old, diff_str = 'NC', 'NC'
52        lines += (node_type, str(total), '%.2f' % percent,
53                  str(old), diff_str)
54    sect.append(Table(children=lines, cols=5, rheaders=1))
55
56
57class RawMetricsChecker(BaseTokenChecker):
58    """does not check anything but gives some raw metrics :
59    * total number of lines
60    * total number of code lines
61    * total number of docstring lines
62    * total number of comments lines
63    * total number of empty lines
64    """
65
66    __implements__ = (ITokenChecker,)
67
68    # configuration section name
69    name = 'metrics'
70    # configuration options
71    options = ()
72    # messages
73    msgs = {}
74    # reports
75    reports = (('RP0701', 'Raw metrics', report_raw_stats),)
76
77    def __init__(self, linter):
78        BaseTokenChecker.__init__(self, linter)
79        self.stats = None
80
81    def open(self):
82        """init statistics"""
83        self.stats = self.linter.add_stats(total_lines=0, code_lines=0,
84                                           empty_lines=0, docstring_lines=0,
85                                           comment_lines=0)
86
87    def process_tokens(self, tokens):
88        """update stats"""
89        i = 0
90        tokens = list(tokens)
91        while i < len(tokens):
92            i, lines_number, line_type = get_type(tokens, i)
93            self.stats['total_lines'] += lines_number
94            self.stats[line_type] += lines_number
95
96
97JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER)
98
99def get_type(tokens, start_index):
100    """return the line type : docstring, comment, code, empty"""
101    i = start_index
102    tok_type = tokens[i][0]
103    start = tokens[i][2]
104    pos = start
105    line_type = None
106    while i < len(tokens) and tokens[i][2][0] == start[0]:
107        tok_type = tokens[i][0]
108        pos = tokens[i][3]
109        if line_type is None:
110            if tok_type == tokenize.STRING:
111                line_type = 'docstring_lines'
112            elif tok_type == tokenize.COMMENT:
113                line_type = 'comment_lines'
114            elif tok_type in JUNK:
115                pass
116            else:
117                line_type = 'code_lines'
118        i += 1
119    if line_type is None:
120        line_type = 'empty_lines'
121    elif i < len(tokens) and tok_type == tokenize.NEWLINE:
122        i += 1
123    return i, pos[0] - start[0] + 1, line_type
124
125
126def register(linter):
127    """ required method to auto register this checker """
128    linter.register_checker(RawMetricsChecker(linter))
129
130