1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 # Detect the code smells and antipatterns in the code.
16 module codesmells_metrics
19 import nitsmell_toolcontext
20 import method_analyze_metrics
21 import mclassdef_collect
23 redef class ToolContext
24 var codesmells_metrics_phase
= new CodeSmellsMetricsPhase(self, null)
27 class CodeSmellsMetricsPhase
29 var average_number_of_lines
= 0.0
30 var average_number_of_parameter
= 0.0
31 var average_number_of_method
= 0.0
32 var average_number_of_attribute
= 0.0
34 redef fun process_mainmodule
(mainmodule
, given_mmodules
) do
35 print toolcontext
.format_h1
("--- Code Smells Metrics ---")
36 self.set_all_average_metrics
37 var mclass_codesmell
= new BadConceptonController
38 var collect
= new Counter[MClassDef]
39 var mclassdefs
= new Array[MClassDef]
41 for mclass
in mainmodule
.flatten_mclass_hierarchy
do
42 mclass_codesmell
.collect
(mclass
.mclassdefs
,self)
44 if toolcontext
.opt_get_all
.value
then
45 mclass_codesmell
.print_all
47 mclass_codesmell
.print_top
(10)
51 fun set_all_average_metrics
do
52 var model_builder
= toolcontext
.modelbuilder
53 var model_view
= model_builder
.model
.private_view
54 self.average_number_of_lines
= model_view
.get_avg_linenumber
(model_builder
)
55 self.average_number_of_parameter
= model_view
.get_avg_parameter
56 self.average_number_of_method
= model_view
.get_avg_method
57 self.average_number_of_attribute
= model_view
.get_avg_attribut
61 class BadConceptonController
63 var bad_conception_elements
= new Array[BadConceptionFinder]
65 # Print all collected code smell sort in decroissant order
67 for bad_conception
in self.sort
do
68 bad_conception
.print_collected_data
72 # Print the n top element
73 fun print_top
(number
: Int) do
74 for bad_conception
in self.get_numbers_of_elements
(number
) do
75 bad_conception
.print_collected_data
79 # Collect method take Array of mclassdef to find the code smells for every class
80 fun collect
(mclassdefs
: Array[MClassDef],phase
: CodeSmellsMetricsPhase) do
81 for mclassdef
in mclassdefs
do
82 var bad_conception_class
= new BadConceptionFinder(mclassdef
,phase
)
83 bad_conception_class
.collect
84 bad_conception_elements
.add
(bad_conception_class
)
88 # Sort the bad_conception_elements array
89 fun sort
: Array[BadConceptionFinder]
91 var res
= bad_conception_elements
92 var sorter
= new BadConceptionComparator
97 # Return an array with n elements
98 fun get_numbers_of_elements
(number
: Int) : Array[BadConceptionFinder]do
99 var return_values
= new Array[BadConceptionFinder]
102 if list
.length
<= number
*2 then min
= list
.length
104 var t
= list
[list
.length-i-1
]
111 class BadConceptionFinder
112 var mclassdef
: MClassDef
113 var array_badconception
= new Array[BadConception]
114 var phase
: CodeSmellsMetricsPhase
117 # Collect code smell with selected toolcontext option
119 var bad_conception_elements
= new Array[BadConception]
120 # Check toolcontext option
121 if phase
.toolcontext
.opt_feature_envy
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new FeatureEnvy(phase
))
122 if phase
.toolcontext
.opt_long_method
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LongMethod(phase
))
123 if phase
.toolcontext
.opt_long_params
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LongParameterList(phase
))
124 if phase
.toolcontext
.opt_no_abstract_implementation
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new NoAbstractImplementation(phase
))
125 if phase
.toolcontext
.opt_large_class
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LargeClass(phase
))
126 # Collected all code smell if their state is true
127 for bad_conception_element
in bad_conception_elements
do
128 if bad_conception_element
.collect
(self.mclassdef
,phase
.toolcontext
.modelbuilder
) then array_badconception
.add
(bad_conception_element
)
130 # Compute global score
134 fun print_collected_data
do
135 if array_badconception
.length
!= 0 then
136 print
"--------------------"
137 print phase
.toolcontext
.format_h1
("Full name: {mclassdef.full_name} Location: {mclassdef.location}")
138 for bad_conception
in array_badconception
do
139 bad_conception
.print_result
144 fun collect_global_score
do
145 if array_badconception
.not_empty
then
146 for bad_conception
in array_badconception
do
147 self.score
+= bad_conception
.score
153 abstract class BadConception
154 var phase
: CodeSmellsMetricsPhase
159 fun name
: String is abstract
162 fun desc
: String is abstract
165 fun collect
(mclassdef
: MClassDef, model_builder
: ModelBuilder): Bool is abstract
167 # Show results in console
168 fun print_result
is abstract
170 # Compute code smell score to sort
178 var number_attribut
= 0
180 var number_method
= 0
182 redef fun name
do return "LARGC"
184 redef fun desc
do return "Large class"
186 redef fun collect
(mclassdef
, model_builder
): Bool do
187 self.number_attribut
= mclassdef
.collect_intro_and_redef_mattributes
(model_builder
.model
.private_view
).length
188 # Get the number of methods (Accessor include) (subtract the get and set of attibutes with (numberAtribut*2))
189 self.number_method
= mclassdef
.collect_intro_and_redef_methods
(model_builder
.model
.private_view
).length
191 return self.number_method
.to_f
> phase
.average_number_of_method
and self.number_attribut
.to_f
> phase
.average_number_of_attribute
194 redef fun print_result
do
195 print phase
.toolcontext
.format_h2
("{desc}: {number_attribut} attributes and {number_method} methods ({phase.average_number_of_attribute}A {phase.average_number_of_method}M Average)")
198 redef fun score_rate
do
199 score
= (number_method
.to_f
+ number_attribut
.to_f
) / (phase
.average_number_of_method
+ phase
.average_number_of_attribute
)
203 class LongParameterList
205 var bad_methods
= new Array[MMethodDef]
207 redef fun name
do return "LONGPL"
209 redef fun desc
do return "Long parameter list"
211 redef fun collect
(mclassdef
, model_builder
): Bool do
212 for meth
in mclassdef
.collect_intro_and_redef_mpropdefs
(model_builder
.model
.private_view
) do
213 var threshold_value
= 4
214 # Get the threshold value from the toolcontext command
215 if phase
.toolcontext
.opt_long_params_threshold
.value
!= 0 then threshold_value
= phase
.toolcontext
.opt_long_params_threshold
.value
216 # Check if the property is a method definition
217 if not meth
isa MMethodDef then continue
218 # Check if method has a signature
219 if meth
.msignature
== null then continue
220 if meth
.msignature
.mparameters
.length
<= threshold_value
then continue
221 self.bad_methods
.add
(meth
)
224 return self.bad_methods
.not_empty
227 redef fun print_result
do
228 print phase
.toolcontext
.format_h2
("{desc}:")
229 if self.bad_methods
.not_empty
then
230 print
" Affected method(s):"
231 for method
in self.bad_methods
do
232 print
" -{method.name} has {method.msignature.mparameters.length} parameters"
237 redef fun score_rate
do
238 if self.bad_methods
.not_empty
then
239 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
246 var bad_methods
= new Array[MMethodDef]
248 redef fun name
do return "FEM"
250 redef fun desc
do return "Feature envy"
252 redef fun collect
(mclassdef
, model_builder
): Bool do
253 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
)
254 for mmethoddef
in mmethoddefs
do
255 var max_class_call
= mmethoddef
.class_call
.max
256 # Check if the class with the maximum call is >= auto-call and the maximum call class is != of this class
257 if mmethoddef
.class_call
[max_class_call
] <= mmethoddef
.total_self_call
or max_class_call
.mclass
.full_name
== mclassdef
.mclass
.full_name
then continue
258 self.bad_methods
.add
(mmethoddef
)
261 return self.bad_methods
.not_empty
264 redef fun print_result
do
265 print phase
.toolcontext
.format_h2
("{desc}:")
266 if self.bad_methods
.not_empty
then
267 print
" Affected method(s):"
268 for method
in self.bad_methods
do
269 var max_class_call
= method
.class_call
.max
270 if max_class_call
!= null then
271 # Check if the type of max call class is generique
272 if max_class_call
.mclass
.mclass_type
isa MGenericType and not phase
.toolcontext
.opt_move_generics
.value
then
273 print
" -{method.name}({method.msignature.mparameters.join(", ")}) {method.total_self_call}/{method.class_call[max_class_call]}"
275 print
" -{method.name}({method.msignature.mparameters.join(", ")}) {method.total_self_call}/{method.class_call[max_class_call]} move to {max_class_call}"
282 redef fun score_rate
do
283 if self.bad_methods
.not_empty
then
284 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
291 var bad_methods
= new Array[MMethodDef]
293 redef fun name
do return "LONGMETH"
295 redef fun desc
do return "Long method"
297 redef fun collect
(mclassdef
, model_builder
): Bool do
298 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
)
299 var threshold_value
= phase
.average_number_of_lines
.to_i
300 # Get the threshold value from the toolcontext command
301 if phase
.toolcontext
.opt_long_method_threshold
.value
!= 0 then threshold_value
= phase
.toolcontext
.opt_long_method_threshold
.value
303 for mmethoddef
in mmethoddefs
do
304 if mmethoddef
.line_number
<= threshold_value
then continue
305 self.bad_methods
.add
(mmethoddef
)
308 return self.bad_methods
.not_empty
311 redef fun print_result
do
312 print phase
.toolcontext
.format_h2
("{desc}: Average {phase.average_number_of_lines.to_i} lines")
313 if self.bad_methods
.not_empty
then
314 print
" Affected method(s):"
315 for method
in self.bad_methods
do
316 print
" -{method.name} has {method.line_number} lines"
321 redef fun score_rate
do
322 if self.bad_methods
.not_empty
then
323 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
328 class NoAbstractImplementation
330 var bad_methods
= new Array[MMethodDef]
332 redef fun name
do return "LONGMETH"
334 redef fun desc
do return "No Implemented abstract property"
336 redef fun collect
(mclassdef
, model_builder
): Bool do
337 if not mclassdef
.mclass
.is_abstract
and not mclassdef
.mclass
.is_interface
then
338 if mclassdef
.collect_abstract_methods
(model_builder
.model
.private_view
).not_empty
then
339 bad_methods
.add_all
(mclassdef
.collect_not_define_properties
(model_builder
.model
.private_view
))
343 return bad_methods
.not_empty
346 redef fun print_result
do
347 print phase
.toolcontext
.format_h2
("{desc}:")
348 if self.bad_methods
.not_empty
then
349 print
" Affected method(s):"
350 for method
in self.bad_methods
do
351 print
" -{method.name}"
356 redef fun score_rate
do
357 if self.bad_methods
.not_empty
then
358 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
363 redef class ModelView
364 fun get_avg_parameter
: Float do
365 var counter
= new Counter[MMethodDef]
366 for mclassdef
in mclassdefs
do
367 for method
in mclassdef
.collect_intro_and_redef_mpropdefs
(self) do
368 # check if the property is a method definition
369 if not method
isa MMethodDef then continue
370 #Check if method has a signature
371 if method
.msignature
== null then continue
372 if method
.msignature
.mparameters
.length
== 0 then continue
373 counter
[method
] = method
.msignature
.mparameters
.length
376 return counter
.avg
+ counter
.std_dev
379 fun get_avg_attribut
: Float do
380 var counter
= new Counter[MClassDef]
381 for mclassdef
in mclassdefs
do
382 var number_attributs
= mclassdef
.collect_intro_and_redef_mattributes
(self).length
383 if number_attributs
!= 0 then counter
[mclassdef
] = number_attributs
385 return counter
.avg
+ counter
.std_dev
388 fun get_avg_method
: Float do
389 var counter
= new Counter[MClassDef]
390 for mclassdef
in mclassdefs
do
391 var number_methodes
= mclassdef
.collect_intro_and_redef_methods
(self).length
392 if number_methodes
!= 0 then counter
[mclassdef
] = number_methodes
394 return counter
.avg
+ counter
.std_dev
397 fun get_avg_linenumber
(model_builder
: ModelBuilder): Float do
398 var methods_analyse_metrics
= new Counter[MClassDef]
399 for mclassdef
in mclassdefs
do
402 for mmethoddef
in call_analyze_methods
(mclassdef
,model_builder
) do
403 result
+= mmethoddef
.line_number
404 if mmethoddef
.line_number
== 0 then continue
407 if not mclassdef
.collect_local_mproperties
(self).length
!= 0 then continue
408 if count
== 0 then continue
409 methods_analyse_metrics
[mclassdef
] = (result
/count
).to_i
411 return methods_analyse_metrics
.avg
+ methods_analyse_metrics
.std_dev
415 class BadConceptionComparator
417 redef type COMPARED: BadConceptionFinder
418 redef fun compare
(a
,b
) do
419 var test
= a
.array_badconception
.length
<=> b
.array_badconception
.length
421 return a
.score
<=> b
.score
423 return a
.array_badconception
.length
<=> b
.array_badconception
.length