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 ---")
37 var model
= toolcontext
.modelbuilder
.model
38 var filter
= new ModelFilter(private_visibility
)
39 self.set_all_average_metrics
(model
)
40 var mclass_codesmell
= new BadConceptonController(model
, filter
)
41 var collect
= new Counter[MClassDef]
42 var mclassdefs
= new Array[MClassDef]
44 for mclass
in mainmodule
.flatten_mclass_hierarchy
do
45 mclass_codesmell
.collect
(mclass
.mclassdefs
,self)
47 if toolcontext
.opt_get_all
.value
then
48 mclass_codesmell
.print_all
50 mclass_codesmell
.print_top
(10)
54 fun set_all_average_metrics
(model
: Model) do
55 var model_builder
= toolcontext
.modelbuilder
56 self.average_number_of_lines
= model
.get_avg_linenumber
(model_builder
)
57 self.average_number_of_parameter
= model
.get_avg_parameter
58 self.average_number_of_method
= model
.get_avg_method
59 self.average_number_of_attribute
= model
.get_avg_attribut
63 class BadConceptonController
67 var filter
: ModelFilter
70 var bad_conception_elements
= new Array[BadConceptionFinder]
72 # Print all collected code smell sort in decroissant order
74 for bad_conception
in self.sort
do
75 bad_conception
.print_collected_data
79 # Print the n top element
80 fun print_top
(number
: Int) do
81 for bad_conception
in self.get_numbers_of_elements
(number
) do
82 bad_conception
.print_collected_data
86 # Collect method take Array of mclassdef to find the code smells for every class
87 fun collect
(mclassdefs
: Array[MClassDef],phase
: CodeSmellsMetricsPhase) do
88 for mclassdef
in mclassdefs
do
89 var bad_conception_class
= new BadConceptionFinder(mclassdef
, phase
, model
, filter
)
90 bad_conception_class
.collect
91 bad_conception_elements
.add
(bad_conception_class
)
95 # Sort the bad_conception_elements array
96 fun sort
: Array[BadConceptionFinder]
98 var res
= bad_conception_elements
99 var sorter
= new BadConceptionComparator
104 # Return an array with n elements
105 fun get_numbers_of_elements
(number
: Int) : Array[BadConceptionFinder]do
106 var return_values
= new Array[BadConceptionFinder]
109 if list
.length
<= number
*2 then min
= list
.length
111 var t
= list
[list
.length-i-1
]
118 class BadConceptionFinder
119 var mclassdef
: MClassDef
120 var array_badconception
= new Array[BadConception]
121 var phase
: CodeSmellsMetricsPhase
123 var filter
: ModelFilter
126 # Collect code smell with selected toolcontext option
128 var bad_conception_elements
= new Array[BadConception]
129 # Check toolcontext option
130 if phase
.toolcontext
.opt_feature_envy
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new FeatureEnvy(phase
, model
, filter
))
131 if phase
.toolcontext
.opt_long_method
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LongMethod(phase
, model
, filter
))
132 if phase
.toolcontext
.opt_long_params
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LongParameterList(phase
, model
, filter
))
133 if phase
.toolcontext
.opt_no_abstract_implementation
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new NoAbstractImplementation(phase
, model
, filter
))
134 if phase
.toolcontext
.opt_large_class
.value
or phase
.toolcontext
.opt_all
.value
then bad_conception_elements
.add
(new LargeClass(phase
, model
, filter
))
135 # Collected all code smell if their state is true
136 for bad_conception_element
in bad_conception_elements
do
137 if bad_conception_element
.collect
(self.mclassdef
,phase
.toolcontext
.modelbuilder
) then array_badconception
.add
(bad_conception_element
)
139 # Compute global score
143 fun print_collected_data
do
144 if array_badconception
.length
!= 0 then
145 print
"--------------------"
146 print phase
.toolcontext
.format_h1
("Full name: {mclassdef.full_name} Location: {mclassdef.location}")
147 for bad_conception
in array_badconception
do
148 bad_conception
.print_result
153 fun collect_global_score
do
154 if array_badconception
.not_empty
then
155 for bad_conception
in array_badconception
do
156 self.score
+= bad_conception
.score
162 abstract class BadConception
163 var phase
: CodeSmellsMetricsPhase
167 var filter
: ModelFilter
172 fun name
: String is abstract
175 fun desc
: String is abstract
178 fun collect
(mclassdef
: MClassDef, model_builder
: ModelBuilder): Bool is abstract
180 # Show results in console
181 fun print_result
is abstract
183 # Compute code smell score to sort
191 var number_attribut
= 0
193 var number_method
= 0
195 redef fun name
do return "LARGC"
197 redef fun desc
do return "Large class"
199 redef fun collect
(mclassdef
, model_builder
): Bool do
200 self.number_attribut
= mclassdef
.collect_intro_and_redef_mattributes
(filter
).length
201 # Get the number of methods (Accessor include) (subtract the get and set of attibutes with (numberAtribut*2))
202 self.number_method
= mclassdef
.collect_intro_and_redef_methods
(filter
).length
204 return self.number_method
.to_f
> phase
.average_number_of_method
and self.number_attribut
.to_f
> phase
.average_number_of_attribute
207 redef fun print_result
do
208 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)")
211 redef fun score_rate
do
212 score
= (number_method
.to_f
+ number_attribut
.to_f
) / (phase
.average_number_of_method
+ phase
.average_number_of_attribute
)
216 class LongParameterList
218 var bad_methods
= new Array[MMethodDef]
220 redef fun name
do return "LONGPL"
222 redef fun desc
do return "Long parameter list"
224 redef fun collect
(mclassdef
, model_builder
): Bool do
225 for meth
in mclassdef
.collect_intro_and_redef_mpropdefs
(filter
) do
226 var threshold_value
= 4
227 # Get the threshold value from the toolcontext command
228 if phase
.toolcontext
.opt_long_params_threshold
.value
!= 0 then threshold_value
= phase
.toolcontext
.opt_long_params_threshold
.value
229 # Check if the property is a method definition
230 if not meth
isa MMethodDef then continue
231 # Check if method has a signature
232 if meth
.msignature
== null then continue
233 if meth
.msignature
.mparameters
.length
<= threshold_value
then continue
234 self.bad_methods
.add
(meth
)
237 return self.bad_methods
.not_empty
240 redef fun print_result
do
241 print phase
.toolcontext
.format_h2
("{desc}:")
242 if self.bad_methods
.not_empty
then
243 print
" Affected method(s):"
244 for method
in self.bad_methods
do
245 print
" -{method.name} has {method.msignature.mparameters.length} parameters"
250 redef fun score_rate
do
251 if self.bad_methods
.not_empty
then
252 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
259 var bad_methods
= new Array[MMethodDef]
261 redef fun name
do return "FEM"
263 redef fun desc
do return "Feature envy"
265 redef fun collect
(mclassdef
, model_builder
): Bool do
266 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
, filter
)
267 for mmethoddef
in mmethoddefs
do
268 var max_class_call
= mmethoddef
.class_call
.max
269 # Check if the class with the maximum call is >= auto-call and the maximum call class is != of this class
270 if mmethoddef
.class_call
[max_class_call
] <= mmethoddef
.total_self_call
or max_class_call
.mclass
.full_name
== mclassdef
.mclass
.full_name
then continue
271 self.bad_methods
.add
(mmethoddef
)
274 return self.bad_methods
.not_empty
277 redef fun print_result
do
278 print phase
.toolcontext
.format_h2
("{desc}:")
279 if self.bad_methods
.not_empty
then
280 print
" Affected method(s):"
281 for method
in self.bad_methods
do
282 var max_class_call
= method
.class_call
.max
283 if max_class_call
!= null then
284 # Check if the type of max call class is generique
285 if max_class_call
.mclass
.mclass_type
isa MGenericType and not phase
.toolcontext
.opt_move_generics
.value
then
286 print
" -{method.name}({method.msignature.mparameters.join(", ")}) {method.total_self_call}/{method.class_call[max_class_call]}"
288 print
" -{method.name}({method.msignature.mparameters.join(", ")}) {method.total_self_call}/{method.class_call[max_class_call]} move to {max_class_call}"
295 redef fun score_rate
do
296 if self.bad_methods
.not_empty
then
297 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
304 var bad_methods
= new Array[MMethodDef]
306 redef fun name
do return "LONGMETH"
308 redef fun desc
do return "Long method"
310 redef fun collect
(mclassdef
, model_builder
): Bool do
311 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
, filter
)
312 var threshold_value
= phase
.average_number_of_lines
.to_i
313 # Get the threshold value from the toolcontext command
314 if phase
.toolcontext
.opt_long_method_threshold
.value
!= 0 then threshold_value
= phase
.toolcontext
.opt_long_method_threshold
.value
316 for mmethoddef
in mmethoddefs
do
317 if mmethoddef
.line_number
<= threshold_value
then continue
318 self.bad_methods
.add
(mmethoddef
)
321 return self.bad_methods
.not_empty
324 redef fun print_result
do
325 print phase
.toolcontext
.format_h2
("{desc}: Average {phase.average_number_of_lines.to_i} lines")
326 if self.bad_methods
.not_empty
then
327 print
" Affected method(s):"
328 for method
in self.bad_methods
do
329 print
" -{method.name} has {method.line_number} lines"
334 redef fun score_rate
do
335 if self.bad_methods
.not_empty
then
336 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
341 class NoAbstractImplementation
343 var bad_methods
= new Array[MMethodDef]
345 redef fun name
do return "LONGMETH"
347 redef fun desc
do return "No Implemented abstract property"
349 redef fun collect
(mclassdef
, model_builder
): Bool do
350 if not mclassdef
.mclass
.is_abstract
and not mclassdef
.mclass
.is_interface
then
351 if mclassdef
.collect_abstract_methods
(filter
).not_empty
then
352 bad_methods
.add_all
(mclassdef
.collect_not_define_properties
(filter
))
356 return bad_methods
.not_empty
359 redef fun print_result
do
360 print phase
.toolcontext
.format_h2
("{desc}:")
361 if self.bad_methods
.not_empty
then
362 print
" Affected method(s):"
363 for method
in self.bad_methods
do
364 print
" -{method.name}"
369 redef fun score_rate
do
370 if self.bad_methods
.not_empty
then
371 self.score
= self.bad_methods
.length
.to_f
/ phase
.average_number_of_method
377 fun get_avg_parameter
: Float do
378 var counter
= new Counter[MMethodDef]
379 var filter
= new ModelFilter
380 for mclassdef
in collect_mclassdefs
(filter
) do
381 for method
in mclassdef
.collect_intro_and_redef_mpropdefs
(filter
) do
382 # check if the property is a method definition
383 if not method
isa MMethodDef then continue
384 #Check if method has a signature
385 if method
.msignature
== null then continue
386 if method
.msignature
.mparameters
.length
== 0 then continue
387 counter
[method
] = method
.msignature
.mparameters
.length
390 return counter
.avg
+ counter
.std_dev
393 fun get_avg_attribut
: Float do
394 var counter
= new Counter[MClassDef]
395 var filter
= new ModelFilter
396 for mclassdef
in collect_mclassdefs
(filter
) do
397 var number_attributs
= mclassdef
.collect_intro_and_redef_mattributes
(filter
).length
398 if number_attributs
!= 0 then counter
[mclassdef
] = number_attributs
400 return counter
.avg
+ counter
.std_dev
403 fun get_avg_method
: Float do
404 var counter
= new Counter[MClassDef]
405 var filter
= new ModelFilter
406 for mclassdef
in collect_mclassdefs
(filter
) do
407 var number_methodes
= mclassdef
.collect_intro_and_redef_methods
(filter
).length
408 if number_methodes
!= 0 then counter
[mclassdef
] = number_methodes
410 return counter
.avg
+ counter
.std_dev
413 fun get_avg_linenumber
(model_builder
: ModelBuilder): Float do
414 var methods_analyse_metrics
= new Counter[MClassDef]
415 var filter
= new ModelFilter
416 for mclassdef
in collect_mclassdefs
(filter
) do
419 for mmethoddef
in call_analyze_methods
(mclassdef
,model_builder
, filter
) do
420 result
+= mmethoddef
.line_number
421 if mmethoddef
.line_number
== 0 then continue
424 if not mclassdef
.collect_local_mproperties
(filter
).length
!= 0 then continue
425 if count
== 0 then continue
426 methods_analyse_metrics
[mclassdef
] = (result
/count
).to_i
428 return methods_analyse_metrics
.avg
+ methods_analyse_metrics
.std_dev
432 class BadConceptionComparator
434 redef type COMPARED: BadConceptionFinder
435 redef fun compare
(a
,b
) do
436 var test
= a
.array_badconception
.length
<=> b
.array_badconception
.length
438 return a
.score
<=> b
.score
440 return a
.array_badconception
.length
<=> b
.array_badconception
.length