Skip to content

Commit 26921fe

Browse files
committed
[cocom] Add repository level analysis via lizard
Add repository level analysis by adding a new category of analyzer(RepositoryAnalyzer) which executes lizard at repository level for cocom backend. Add and alter tests for corresponding changes. Signed-off-by: inishchith <[email protected]>
1 parent 5a526a6 commit 26921fe

File tree

4 files changed

+323
-72
lines changed

4 files changed

+323
-72
lines changed

graal/backends/core/analyzers/lizard.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
#
1919
# Authors:
2020
# Valerio Cosentino <[email protected]>
21+
# Nishchith Shetty <[email protected]>
2122
#
2223

23-
import warnings
24-
2524
import lizard
26-
25+
from graal.backends.core.analyzers.cloc import Cloc
2726
from .analyzer import Analyzer
2827

2928

@@ -45,10 +44,10 @@ class Lizard(Analyzer):
4544
Golang
4645
Lua
4746
"""
48-
version = '0.2.1'
47+
version = '0.3.0'
4948

50-
def analyze(self, **kwargs):
51-
"""Add code complexity information using Lizard.
49+
def __analyze_file(self, file_path, details):
50+
"""Add code complexity information for a file using Lizard.
5251
5352
Current information includes cyclomatic complexity (ccn),
5453
avg lines of code and tokens, number of functions and tokens.
@@ -61,12 +60,7 @@ def analyze(self, **kwargs):
6160
:returns result: dict of the results of the analysis
6261
"""
6362
result = {}
64-
file_path = kwargs['file_path']
65-
details = kwargs['details']
66-
67-
with warnings.catch_warnings():
68-
warnings.simplefilter('ignore', DeprecationWarning)
69-
analysis = lizard.analyze_file(file_path)
63+
analysis = lizard.analyze_file(file_path)
7064

7165
result['ccn'] = analysis.CCN
7266
result['avg_ccn'] = analysis.average_cyclomatic_complexity
@@ -94,3 +88,65 @@ def analyze(self, **kwargs):
9488

9589
result['funs'] = funs_data
9690
return result
91+
92+
def __analyze_repository(self, repository_path, files_affected, details):
93+
"""Add code complexity information for a given repository
94+
using Lizard and CLOC.
95+
96+
Current information includes cyclomatic complexity (ccn),
97+
lines of code, number of functions, tokens, blanks and comments.
98+
99+
:param repository_path: repository path
100+
:param details: if True, it returns fine-grained results
101+
102+
:returns result: list of the results of the analysis
103+
"""
104+
analysis_result = []
105+
106+
repository_analysis = lizard.analyze(
107+
paths=[repository_path],
108+
threads=1,
109+
exts=lizard.get_extensions([]),
110+
)
111+
cloc = Cloc()
112+
113+
for analysis in repository_analysis:
114+
cloc_analysis = cloc.analyze(file_path=analysis.filename)
115+
file_path = analysis.filename.replace(repository_path + "/", '')
116+
in_commit = True if file_path in files_affected else False
117+
118+
result = {
119+
'loc': analysis.nloc,
120+
'ccn': analysis.CCN,
121+
'tokens': analysis.token_count,
122+
'num_funs': len(analysis.function_list),
123+
'file_path': file_path,
124+
'in_commit': in_commit,
125+
'blanks': cloc_analysis['blanks'],
126+
'comments': cloc_analysis['comments']
127+
}
128+
analysis_result.append(result)
129+
130+
# TODO: implement details option
131+
132+
return analysis_result
133+
134+
def analyze(self, **kwargs):
135+
"""Add code complexity information using Lizard.
136+
137+
:param file_path: file path
138+
:param repository_path: repository path
139+
:param details: if True, it returns detailed information about an analysis
140+
141+
:returns result: the results of the analysis
142+
"""
143+
144+
details = kwargs['details']
145+
146+
if kwargs.get('repository_level', False):
147+
files_affected = kwargs['files_affected']
148+
result = self.__analyze_repository(kwargs["repository_path"], files_affected, details)
149+
else:
150+
result = self.__analyze_file(kwargs['file_path'], details)
151+
152+
return result

graal/backends/core/cocom.py

Lines changed: 114 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,19 @@
2323
import logging
2424

2525
from graal.graal import (Graal,
26+
GraalError,
2627
GraalRepository,
2728
GraalCommand,
2829
DEFAULT_WORKTREE_PATH)
2930
from graal.backends.core.analyzers.cloc import Cloc
3031
from graal.backends.core.analyzers.lizard import Lizard
3132
from perceval.utils import DEFAULT_DATETIME, DEFAULT_LAST_DATETIME
3233

33-
CATEGORY_COCOM = 'code_complexity'
34+
LIZARD_FILE = 'lizard_file'
35+
LIZARD_REPOSITORY = 'lizard_repository'
36+
37+
CATEGORY_COCOM_LIZARD_FILE = 'code_complexity_' + LIZARD_FILE
38+
CATEGORY_COCOM_LIZARD_REPOSITORY = 'code_complexity_' + LIZARD_REPOSITORY
3439

3540
logger = logging.getLogger(__name__)
3641

@@ -70,37 +75,55 @@ class CoCom(Graal):
7075
:raises RepositoryError: raised when there was an error cloning or
7176
updating the repository.
7277
"""
73-
version = '0.2.4'
78+
version = '0.2.5'
7479

75-
CATEGORIES = [CATEGORY_COCOM]
80+
CATEGORIES = [CATEGORY_COCOM_LIZARD_FILE,
81+
CATEGORY_COCOM_LIZARD_REPOSITORY]
7682

7783
def __init__(self, uri, git_path, worktreepath=DEFAULT_WORKTREE_PATH,
7884
entrypoint=None, in_paths=None, out_paths=None, details=False,
7985
tag=None, archive=None):
8086
super().__init__(uri, git_path, worktreepath,
8187
entrypoint=entrypoint, in_paths=in_paths, out_paths=out_paths, details=details,
8288
tag=tag, archive=archive)
83-
self.file_analyzer = FileAnalyzer(details)
8489

85-
def fetch(self, category=CATEGORY_COCOM, paths=None,
90+
self.analyzer = None
91+
self.analyzer_kind = None
92+
93+
def fetch(self, category=CATEGORY_COCOM_LIZARD_FILE, paths=None,
8694
from_date=DEFAULT_DATETIME, to_date=DEFAULT_LAST_DATETIME,
8795
branches=None, latest_items=False):
8896
"""Fetch commits and add code complexity information."""
8997

9098
items = super().fetch(category,
9199
from_date=from_date, to_date=to_date,
92100
branches=branches, latest_items=latest_items)
101+
if category == CATEGORY_COCOM_LIZARD_FILE:
102+
self.analyzer_kind = LIZARD_FILE
103+
self.analyzer = FileAnalyzer(self.details)
104+
elif category == CATEGORY_COCOM_LIZARD_REPOSITORY:
105+
self.analyzer_kind = LIZARD_REPOSITORY
106+
self.analyzer = RepositoryAnalyzer(self.details)
107+
else:
108+
raise GraalError(cause="Unknown category %s" % category)
93109

94110
return items
95111

96112
@staticmethod
97113
def metadata_category(item):
98114
"""Extracts the category from a Code item.
99115
100-
This backend only generates one type of item which is
101-
'code_complexity'.
116+
This backend generates the following types of item:
117+
- 'code_complexity_lizard_file'.
118+
- 'code_complexity_lizard_repository'
102119
"""
103-
return CATEGORY_COCOM
120+
121+
if item['analyzer'] == LIZARD_FILE:
122+
return CATEGORY_COCOM_LIZARD_FILE
123+
elif item['analyzer'] == LIZARD_REPOSITORY:
124+
return CATEGORY_COCOM_LIZARD_REPOSITORY
125+
else:
126+
raise GraalError(cause="Unknown analyzer %s" % item['analyzer'])
104127

105128
def _filter_commit(self, commit):
106129
"""Filter a commit according to its data (e.g., author, sha, etc.)
@@ -121,51 +144,54 @@ def _filter_commit(self, commit):
121144

122145
def _analyze(self, commit):
123146
"""Analyse a commit and the corresponding
124-
checkout version of the repository
147+
checkout version of the repository.
125148
126149
:param commit: a Perceval commit item
127150
"""
128151
analysis = []
129152

130-
for committed_file in commit['files']:
131-
132-
file_path = committed_file['file']
133-
if self.in_paths:
134-
found = [p for p in self.in_paths if file_path.endswith(p)]
135-
if not found:
136-
continue
137-
138-
local_path = self.worktreepath + '/' + file_path
139-
if not GraalRepository.exists(local_path):
140-
file_info = {
141-
'blanks': None,
142-
'comments': None,
143-
'loc': None,
144-
'ccn': None,
145-
'avg_ccn': None,
146-
'avg_loc': None,
147-
'avg_tokens': None,
148-
'num_funs': None,
149-
'tokens': None,
150-
'file_path': file_path,
151-
}
152-
if self.details:
153-
file_info['funs'] = []
154-
155-
if committed_file.get("newfile", None):
156-
file_path = committed_file["newfile"]
157-
local_path = self.worktreepath + '/' + file_path
158-
analysis.append(file_info)
159-
elif committed_file.get("action", None) == "D":
160-
analysis.append(file_info)
161-
continue
162-
else:
163-
continue
164-
165-
file_info = self.file_analyzer.analyze(local_path)
166-
file_info.update({'file_path': file_path})
167-
analysis.append(file_info)
168-
153+
if self.analyzer_kind == LIZARD_FILE:
154+
for committed_file in commit['files']:
155+
156+
file_path = committed_file['file']
157+
if self.in_paths:
158+
found = [p for p in self.in_paths if file_path.endswith(p)]
159+
if not found:
160+
continue
161+
162+
local_path = self.worktreepath + '/' + file_path
163+
if not GraalRepository.exists(local_path):
164+
file_info = {
165+
'blanks': None,
166+
'comments': None,
167+
'loc': None,
168+
'ccn': None,
169+
'avg_ccn': None,
170+
'avg_loc': None,
171+
'avg_tokens': None,
172+
'num_funs': None,
173+
'tokens': None,
174+
'file_path': file_path,
175+
}
176+
if self.details:
177+
file_info['funs'] = []
178+
179+
if committed_file.get("newfile", None):
180+
file_path = committed_file["newfile"]
181+
local_path = self.worktreepath + '/' + file_path
182+
analysis.append(file_info)
183+
elif committed_file.get("action", None) == "D":
184+
analysis.append(file_info)
185+
continue
186+
else:
187+
continue
188+
189+
file_info = self.analyzer.analyze(local_path)
190+
file_info.update({'file_path': file_path})
191+
analysis.append(file_info)
192+
else:
193+
files_affected = [file_info['file'] for file_info in commit['files']]
194+
analysis = self.analyzer.analyze(self.worktreepath, files_affected)
169195
return analysis
170196

171197
def _post(self, commit):
@@ -176,6 +202,8 @@ def _post(self, commit):
176202
commit.pop('files', None)
177203
commit.pop('parents', None)
178204
commit.pop('refs', None)
205+
commit['analyzer'] = self.analyzer_kind
206+
179207
return commit
180208

181209

@@ -192,7 +220,7 @@ def __init__(self, details=False):
192220
self.lizard = Lizard()
193221

194222
def analyze(self, file_path):
195-
"""Analyze the content of a file using CLOC and Lizard
223+
"""Analyze the content of a file using CLOC and Lizard.
196224
197225
:param file_path: file path
198226
@@ -227,6 +255,43 @@ def analyze(self, file_path):
227255
return lizard_analysis
228256

229257

258+
class RepositoryAnalyzer:
259+
"""Class to analyse the content of a repository"""
260+
261+
def __init__(self, details=False):
262+
self.details = details
263+
self.lizard = Lizard()
264+
265+
def analyze(self, repository_path, files_affected):
266+
"""Analyze the content of a repository using CLOC and Lizard.
267+
268+
:param repository_path: repository path
269+
270+
:returns a list containing the results of the analysis
271+
[ {
272+
'loc': ..,
273+
'ccn': ..,
274+
'tokens': ..,
275+
'num_funs': ..,
276+
'file_path': ..,
277+
'in_commit': ..,
278+
'blanks': ..,
279+
'comments': ..,
280+
},
281+
...
282+
]
283+
"""
284+
kwargs = {
285+
'repository_path': repository_path,
286+
'repository_level': True,
287+
'files_affected': files_affected,
288+
'details': self.details
289+
}
290+
lizard_analysis = self.lizard.analyze(**kwargs)
291+
292+
return lizard_analysis
293+
294+
230295
class CoComCommand(GraalCommand):
231296
"""Class to run CoCom backend from the command line."""
232297

0 commit comments

Comments
 (0)