Adding code smell detection :
authorFlorian Deljarry <deljarry.florian@gmail.com>
Fri, 26 May 2017 18:39:47 +0000 (20:39 +0200)
committerFlorian Deljarry <deljarry.florian@gmail.com>
Tue, 6 Jun 2017 20:14:57 +0000 (22:14 +0200)
Long class
Long method parameter list
Long size method
Feature envy
Adding a visitor to analyse the contents of the methods

Signed-off-by: Florian Deljarry <deljarry.florian@gmail.com>

15 files changed:
src/metrics/codesmells_metrics.nit [new file with mode: 0644]
src/metrics/mclassdef_collect.nit [new file with mode: 0644]
src/metrics/method_analyze_metrics.nit [new file with mode: 0644]
src/nitsmells.nit [new file with mode: 0644]
tests/TestNitsmells/FeatureEnvy/featureenvy.nit [new file with mode: 0644]
tests/TestNitsmells/LargeClass/largeclass.nit [new file with mode: 0644]
tests/TestNitsmells/LongMethod/longmethod.nit [new file with mode: 0644]
tests/TestNitsmells/LongParameterList/longparameterlist.nit [new file with mode: 0644]
tests/TestNitsmells/platform/platform.nit [new file with mode: 0644]
tests/nitsmells.args [new file with mode: 0644]
tests/sav/nitsmells.res [new file with mode: 0644]
tests/sav/nitsmells_args1.res [new file with mode: 0644]
tests/sav/nitsmells_args2.res [new file with mode: 0644]
tests/sav/nitsmells_args3.res [new file with mode: 0644]
tests/sav/nitsmells_args4.res [new file with mode: 0644]

diff --git a/src/metrics/codesmells_metrics.nit b/src/metrics/codesmells_metrics.nit
new file mode 100644 (file)
index 0000000..1bd502d
--- /dev/null
@@ -0,0 +1,317 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Detect the code smells and antipatterns in the code.
+
+module codesmells_metrics
+
+import frontend
+import metrics_base
+import mclasses_metrics
+import semantize
+import method_analyze_metrics
+import mclassdef_collect
+
+redef class ToolContext
+       var codesmells_metrics_phase = new CodeSmellsMetricsPhase(self, null)
+end
+
+class CodeSmellsMetricsPhase
+       super Phase
+       var average_number_of_lines = 0.0
+       var average_number_of_parameter = 0.0
+       var average_number_of_method = 0.0
+       var average_number_of_attribute = 0.0
+
+       redef fun process_mainmodule(mainmodule, given_mmodules) do
+               print toolcontext.format_h1("--- Code Smells Metrics ---")
+               self.set_all_average_metrics
+               var mclass_codesmell = new BadConceptonController
+               var collect = new Counter[MClassDef]
+               var mclassdefs = new Array[MClassDef]
+
+               for mclass in mainmodule.flatten_mclass_hierarchy do
+                       mclass_codesmell.collect(mclass.mclassdefs,self)
+               end
+               mclass_codesmell.print_top(10)
+       end
+
+       fun set_all_average_metrics do
+               var model_builder = toolcontext.modelbuilder
+               var model_view = model_builder.model.private_view
+               self.average_number_of_lines = model_view.get_avg_linenumber(model_builder)
+               self.average_number_of_parameter = model_view.get_avg_parameter
+               self.average_number_of_method = model_view.get_avg_method
+               self.average_number_of_attribute = model_view.get_avg_attribut
+       end
+end
+
+class BadConceptonController
+       # Code smell list
+       var bad_conception_elements = new Array[BadConceptionFinder]
+
+       # Print all element conception
+       fun print_all do
+               for bad_conception in bad_conception_elements do
+                       bad_conception.print_all
+               end
+       end
+
+       # Print number of top element conception
+       fun print_top(number: Int) do
+               var test = self.get_numbers_of_elements(number)
+               for bad_conception in test do
+                       bad_conception.print_all
+               end
+       end
+
+       # Collection
+       fun collect(mclassdefs: Array[MClassDef],phase: CodeSmellsMetricsPhase) do
+               for mclassdef in mclassdefs do
+                       var bad_conception_class = new BadConceptionFinder(mclassdef,phase)
+                       bad_conception_class.collect
+                       bad_conception_elements.add(bad_conception_class)
+               end
+       end
+
+       fun sort: Array[BadConceptionFinder]
+       do
+               var res = bad_conception_elements
+               var sorter = new BadConceptionComparator
+               sorter.sort(res)
+               return res
+       end
+
+       fun get_numbers_of_elements(number : Int) : Array[BadConceptionFinder]do
+               var return_values = new Array[BadConceptionFinder]
+               var list = self.sort
+               var min = number
+               if list.length <= number*2 then min = list.length
+               for i in [0..min[ do
+                       var t = list[list.length-i-1]
+                       return_values.add(t)
+               end
+               return return_values
+       end
+end
+
+class BadConceptionFinder
+       var mclassdef: MClassDef
+       var array_badconception = new Array[BadConception]
+       var phase: CodeSmellsMetricsPhase
+
+       fun collect do
+               var bad_conception_elements = new Array[BadConception]
+               bad_conception_elements.add(new LargeClass(phase))
+               bad_conception_elements.add(new LongParameterList(phase))
+               bad_conception_elements.add(new FeatureEnvy(phase))
+               bad_conception_elements.add(new LongMethod(phase))
+               for bad_conception_element in bad_conception_elements do
+                       if bad_conception_element.collect(self.mclassdef,phase.toolcontext.modelbuilder) == true then array_badconception.add(bad_conception_element)
+               end
+       end
+
+       fun print_all do
+               if array_badconception.length != 0 then
+                       print "-----------"
+                       print "{mclassdef.full_name}"
+                       for bad_conception in array_badconception do
+                               bad_conception.print_result
+                       end
+               end
+       end
+end
+
+class BadConception
+       var phase: CodeSmellsMetricsPhase
+
+       # Name
+       fun name: String is abstract
+
+       # Description
+       fun desc: String is abstract
+
+       # Collection method
+       fun collect(mclassdef: MClassDef, model_builder: ModelBuilder): Bool is abstract
+
+       # Show results in console
+       fun print_result is abstract
+end
+
+class LargeClass
+       super BadConception
+       var number_attribut = 0
+
+       var number_method = 0
+
+       redef fun name do return "LARGC"
+
+       redef fun desc do return "Large class"
+
+       redef fun collect(mclassdef, model_builder): Bool do
+               number_attribut = mclassdef.collect_intro_and_redef_mattributes(model_builder.model.private_view).length
+               # get the number of methods (subtract the get and set of attibutes with (numberAtribut*2))
+               number_method = mclassdef.collect_intro_and_redef_methods(model_builder.model.private_view).length
+               return number_method.to_f > phase.average_number_of_method and number_attribut.to_f > phase.average_number_of_attribute
+       end
+
+       redef fun print_result do
+               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)")
+       end
+end
+
+class LongParameterList
+       super BadConception
+       var bad_methods = new Array[MMethodDef]
+
+       redef fun name do return "LONGPL"
+
+       redef fun desc do return "Long parameter list"
+
+       redef fun collect(mclassdef, model_builder): Bool do
+               for meth in mclassdef.collect_intro_and_redef_mpropdefs(model_builder.model.private_view) do
+                       # check if the property is a method definition
+                       if not meth isa MMethodDef then continue
+                       # Check if method has a signature
+                       if meth.msignature == null then continue
+                       if meth.msignature.mparameters.length <= 4 then continue
+                       bad_methods.add(meth)
+               end
+               return bad_methods.not_empty
+       end
+
+
+       redef fun print_result do
+               print "{desc}:"
+               if bad_methods.length >= 1 then
+                       print " Affected method(s):"
+                       for method in bad_methods do
+                               print "         -{method.name} has {method.msignature.mparameters.length} parameters"
+                       end
+               end
+       end
+end
+
+class FeatureEnvy
+       super BadConception
+       var bad_methods = new Array[MMethodDef]
+
+       redef fun name do return "FEM"
+
+       redef fun desc do return "Feature envy"
+
+       redef fun collect(mclassdef, model_builder): Bool do
+               var mmethoddefs = call_analyze_methods(mclassdef,model_builder)
+               for mmethoddef in mmethoddefs do
+                       if mmethoddef.total_extern_call <= mmethoddef.total_self_call then continue
+                       bad_methods.add(mmethoddef)
+               end
+               return bad_methods.not_empty
+       end
+
+       redef fun print_result do
+               print "{desc}:"
+               if bad_methods.length >= 1 then
+                       print " Affected method(s):"
+                       for method in bad_methods do
+                               print "         -{method.name} {method.total_self_call}/{method.total_call}"
+                       end
+               end
+       end
+end
+
+class LongMethod
+       super BadConception
+       var bad_methods = new Array[MMethodDef]
+
+       redef fun name do return "LONGMETH"
+
+       redef fun desc do return "Long method"
+
+       redef fun collect(mclassdef, model_builder): Bool do
+               var mmethoddefs = call_analyze_methods(mclassdef,model_builder)
+               for mmethoddef in mmethoddefs do
+                       if mmethoddef.line_number <= phase.average_number_of_lines.to_i then continue
+                       bad_methods.add(mmethoddef)
+               end
+               return bad_methods.not_empty
+       end
+
+       redef fun print_result do
+               print "{desc}:  Average {phase.average_number_of_lines.to_i} lines"
+               if bad_methods.length >= 1 then
+                       print " Affected method(s):"
+                       for method in bad_methods do
+                               print "         -{method.name} has {method.line_number} lines"
+                       end
+               end
+       end
+end
+
+redef class ModelView
+       fun get_avg_parameter: Float do
+               var counter = new Counter[MMethodDef]
+               for mclassdef in mclassdefs do
+                       for method in mclassdef.collect_intro_and_redef_mpropdefs(self) do
+                       # check if the property is a method definition
+                               if not method isa MMethodDef then continue
+                               #Check if method has a signature
+                               if method.msignature == null then continue
+                               if method.msignature.mparameters.length == 0 then continue
+                               counter[method] = method.msignature.mparameters.length
+                       end
+               end
+               return counter.avg + counter.std_dev
+       end
+
+       fun get_avg_attribut: Float do
+               var counter = new Counter[MClassDef]
+               for mclassdef in mclassdefs do
+                       var number_attributs = mclassdef.collect_intro_and_redef_mattributes(self).length
+                       if number_attributs != 0 then counter[mclassdef] = number_attributs
+               end
+               return counter.avg + counter.std_dev
+       end
+
+       fun get_avg_method: Float do
+               var counter = new Counter[MClassDef]
+               for mclassdef in mclassdefs do
+                       var number_methodes = mclassdef.collect_intro_and_redef_methods(self).length
+                       if number_methodes != 0 then counter[mclassdef] = number_methodes
+               end
+               return counter.avg + counter.std_dev
+       end
+
+       fun get_avg_linenumber(model_builder: ModelBuilder): Float do
+               var methods_analyse_metrics = new Counter[MClassDef]
+               for mclassdef in mclassdefs do
+                       var result = 0
+                       var count = 0
+                       for mmethoddef in call_analyze_methods(mclassdef,model_builder) do
+                               result += mmethoddef.line_number
+                               if mmethoddef.line_number == 0 then continue
+                               count += 1
+                       end
+                       if not mclassdef.collect_local_mproperties(self).length != 0 then continue
+                       if count == 0 then continue
+                       methods_analyse_metrics[mclassdef] = (result/count).to_i
+               end
+               return methods_analyse_metrics.avg + methods_analyse_metrics.std_dev
+       end
+end
+
+class BadConceptionComparator
+       super Comparator
+       redef type COMPARED: BadConceptionFinder
+       redef fun compare(a,b) do return a.array_badconception.length <=> b.array_badconception.length
+end
diff --git a/src/metrics/mclassdef_collect.nit b/src/metrics/mclassdef_collect.nit
new file mode 100644 (file)
index 0000000..5b6c937
--- /dev/null
@@ -0,0 +1,167 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# This module redef Mclassdef to add new collect methods.
+
+module mclassdef_collect
+
+# We usualy need specific phases
+# NOTE: `frontend` is sufficent in most case (it is often too much)
+import frontend
+import model_views
+import model_collect
+
+redef class MClassDef
+       # Collect all mproperties introduced in 'self' with `visibility >= min_visibility`.
+       fun collect_intro_mproperties(view: ModelView): Set[MProperty] do
+               var set = new HashSet[MProperty]
+                       for mprop in self.intro_mproperties do
+                               if not view.accept_mentity(mprop) then continue
+                               set.add(mprop)
+                       end
+               return set
+       end
+
+       # Collect mmethods introduced in 'self' with `visibility >= min_visibility`.
+       fun collect_intro_mmethods(view: ModelView): Set[MMethod] do
+               var res = new HashSet[MMethod]
+               for mproperty in collect_intro_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MMethod then res.add(mproperty)
+               end
+               return res
+       end
+
+       # Collect mmethods redefined in 'self' with `visibility >= min_visibility`.
+       fun collect_redef_mmethods(view: ModelView): Set[MMethod] do
+               var res = new HashSet[MMethod]
+               for mproperty in collect_redef_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MMethod then res.add(mproperty)
+               end
+               return res
+       end
+
+       # Collect mattributes redefined in 'self' with `visibility >= min_visibility`.
+       fun collect_redef_mattributes(view: ModelView): Set[MAttribute] do
+               var res = new HashSet[MAttribute]
+               for mproperty in collect_redef_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MAttribute then res.add(mproperty)
+               end
+               return res
+       end
+
+       # Collect mattributes introduced in 'self' with `visibility >= min_visibility`.
+       fun collect_intro_mattributes(view: ModelView): Set[MAttribute] do
+               var res = new HashSet[MAttribute]
+               for mproperty in collect_intro_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MAttribute then res.add(mproperty)
+               end
+               return res
+       end
+
+       # Collect all mproperties redefined in 'self' with `visibility >= min_visibility`.
+       fun collect_redef_mproperties(view: ModelView): Set[MProperty] do
+               var set = new HashSet[MProperty]
+               for mpropdef in self.mpropdefs do
+                       if not view.accept_mentity(mpropdef) then continue
+                       if mpropdef.mproperty.intro_mclassdef.mclass == self then continue
+                               set.add(mpropdef.mproperty)
+                       end
+               return set
+       end
+
+       # Collect mmethods inherited by 'self' if accepted by `view`.
+       fun collect_inherited_mmethods(view: ModelView): Set[MMethod] do
+               var res = new HashSet[MMethod]
+               for mproperty in collect_inherited_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MMethod then res.add(mproperty)
+               end
+               return res
+       end
+
+       # Collect mproperties introduced and redefined in 'self' with `visibility >= min_visibility`.
+       fun collect_local_mproperties(view: ModelView): Set[MProperty] do
+               var set = new HashSet[MProperty]
+               set.add_all collect_intro_mproperties(view)
+               set.add_all collect_redef_mproperties(view)
+               return set
+       end
+
+       # Collect all mproperties inehrited by 'self' with `visibility >= min_visibility`.
+       fun collect_inherited_mproperties(view: ModelView): Set[MProperty] do
+               var set = new HashSet[MProperty]
+               for parent in collect_parents(view) do
+                       set.add_all(parent.collect_intro_mproperties(view))
+                       set.add_all(parent.collect_inherited_mproperties(view))
+               end
+               return set
+       end
+
+       # Collect mattributes inherited by 'self' with `visibility >= min_visibility`.
+       fun collect_inherited_mattributes(view: ModelView): Set[MAttribute] do
+               var res = new HashSet[MAttribute]
+               for mproperty in collect_inherited_mproperties(view) do
+                       if not view.accept_mentity(mproperty) then continue
+                       if mproperty isa MAttribute then res.add(mproperty)
+               end
+               return res
+       end
+
+       fun collect_all_methods(view: ModelView): Set[MMethod] do
+               var set = new HashSet[MMethod]
+               set.add_all collect_intro_mmethods(view)
+               set.add_all collect_redef_mmethods(view)
+               set.add_all collect_inherited_mmethods(view)
+               return set
+       end
+
+       fun collect_all_mattributes(view: ModelView): Set[MAttribute] do
+               var set = new HashSet[MAttribute]
+               set.add_all collect_redef_mattributes(view)
+               set.add_all collect_intro_mattributes(view)
+               set.add_all collect_inherited_mattributes(view)
+               return set
+       end
+
+       fun collect_intro_and_redef_methods(view: ModelView): Set[MMethod] do
+               var set = new HashSet[MMethod]
+               set.add_all collect_intro_mmethods(view)
+               set.add_all collect_redef_mmethods(view)
+               return set
+       end
+
+       fun collect_intro_and_redef_mattributes(view: ModelView): Set[MAttribute] do
+               var set = new HashSet[MAttribute]
+               set.add_all collect_redef_mattributes(view)
+               set.add_all collect_intro_mattributes(view)
+               return set
+       end
+
+       fun collect_intro_and_redef_mproperties(view: ModelView): Set[MProperty] do
+               var set = new HashSet[MProperty]
+               set.add_all collect_redef_mproperties(view)
+               set.add_all collect_intro_mproperties(view)
+               return set
+       end
+
+       fun collect_intro_and_redef_mpropdefs(view: ModelView): Set[MPropDef] do
+               var set = new HashSet[MPropDef]
+               set.add_all collect_intro_mpropdefs(view)
+               set.add_all collect_redef_mpropdefs(view)
+               return set
+       end
+end
diff --git a/src/metrics/method_analyze_metrics.nit b/src/metrics/method_analyze_metrics.nit
new file mode 100644 (file)
index 0000000..6aee2a9
--- /dev/null
@@ -0,0 +1,83 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# This module call visitor for get number of line, total attributs call and total of self attributes call
+
+module method_analyze_metrics
+
+# We usualy need specific phases
+# NOTE: `frontend` is sufficent in most case (it is often too much)
+import metrics_base
+import mclasses_metrics
+import semantize
+import mclassdef_collect
+
+
+fun call_analyze_methods(mclassdef: MClassDef, model_builder: ModelBuilder): Array[MMethodDef] do
+       var mmethoddefs = new Array[MMethodDef]
+       for m_prop in mclassdef.collect_intro_and_redef_mpropdefs(model_builder.model.private_view) do
+               var n_prop = model_builder.mpropdef2node(m_prop)
+               #Check if the property is a method definition
+               if n_prop isa AMethPropdef and m_prop isa MMethodDef then
+                       if n_prop.n_methid isa AIdMethid then
+                               #Call visitor to analyse the method
+                               var visitor = new MethodAnalyzeMetrics(n_prop)
+                               visitor.enter_visit(n_prop)
+                               mmethoddefs.add(set_analyse_result_methoddef(m_prop,visitor))
+                       end
+               end
+       end
+       return mmethoddefs
+end
+
+fun set_analyse_result_methoddef(mmethoddef: MMethodDef, visitor: MethodAnalyzeMetrics): MMethodDef do
+       mmethoddef.total_call = visitor.total_call
+       mmethoddef.line_number = visitor.line_number.length
+       mmethoddef.total_self_call = visitor.total_self_call
+       mmethoddef.total_extern_call = visitor.total_call - visitor.total_self_call
+       return mmethoddef
+end
+
+public class MethodAnalyzeMetrics
+       super Visitor
+       var ameth_prop_def: AMethPropdef
+       var total_call = 0
+       var line_number = new Counter[nullable Int]
+       var total_self_call = 0
+
+       redef fun visit(n) do
+               n.visit_all(self)
+               if n isa AExpr then
+                       if not n isa ABlockExpr then
+                               if n.first_location != null then
+                                       line_number.inc(n.first_location.line_start)
+                               end
+                       end
+               end
+
+               if n isa ASendExpr then
+                       var callsite = n.callsite
+                       if callsite != null then
+                               self.total_call += 1
+                               if callsite.recv_is_self == true then self.total_self_call += 1
+                       end
+               end
+       end
+end
+
+redef class MMethodDef
+       var total_call = 0
+       var line_number = 0
+       var total_self_call = 0
+       var total_extern_call = 0
+end
diff --git a/src/nitsmells.nit b/src/nitsmells.nit
new file mode 100644 (file)
index 0000000..da8454a
--- /dev/null
@@ -0,0 +1,36 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import metrics_base
+import mclasses_metrics
+import semantize
+
+import codesmells_metrics
+
+# Create a tool context to handle options and paths
+var toolcontext = new ToolContext
+toolcontext.tooldescription = "Usage: nitsmells [OPTION]... <file.nit>...\n Computes code smells on Nit programs."
+# We do not add other options, so process them now!
+toolcontext.process_options(args)
+# Get arguments
+var arguments = toolcontext.option_context.rest
+# We need a model to collect stufs
+var model = new Model
+# An a model builder to parse files
+var modelbuilder = new ModelBuilder(model, toolcontext)
+# Here we load an process all modules passed on the command line
+var mmodules = modelbuilder.parse_full(arguments)
+modelbuilder.run_phases
+print "*** CODE SMELLS METRICS ***"
+toolcontext.run_global_phases(mmodules)
diff --git a/tests/TestNitsmells/FeatureEnvy/featureenvy.nit b/tests/TestNitsmells/FeatureEnvy/featureenvy.nit
new file mode 100644 (file)
index 0000000..6a11274
--- /dev/null
@@ -0,0 +1,42 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A test program with a fake model to check model tools.
+module featureenvy
+
+import platform
+
+class Starter
+       var attribute = 0
+       var attribute1 = 0
+       var attribute2 = 0
+       fun start do
+               self.attribute1 = 10
+               self.attribute = 2
+       end
+
+       fun ended do end
+end
+
+class FeatureEnvy
+       var testVariable = 0
+
+       fun feature_envy_method do
+               var starter = new Starter
+               testVariable = starter.attribute
+               if starter.attribute == self.testVariable then
+                       starter.attribute = 10
+               end
+       end
+end
diff --git a/tests/TestNitsmells/LargeClass/largeclass.nit b/tests/TestNitsmells/LargeClass/largeclass.nit
new file mode 100644 (file)
index 0000000..1c18141
--- /dev/null
@@ -0,0 +1,61 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A test program with a fake model to check model tools.
+module largeclass
+
+import platform
+
+class LargeClass
+       var attribute = 0
+       var attribute1 = 0
+       var attribute2 = 0
+       var attribute3 = 0
+       var attribute4 = 0
+       var attribute5 = 0
+       var attribut6 = 0
+       var attribute7 = 0
+       var attribute8 = 0
+       var attribute9 = 0
+       var attribute10 = 0
+       var attribute11 = 0
+       var attribute12 = 0
+       var attribute13 = 0
+       var attribute14 = 0
+       var attribute15 = 0
+       var attribute16 = 0
+       var attribute17 = 0
+
+       fun start do
+               self.attribute16 = 10
+       end
+
+       fun ended do end
+       fun replay do end
+       fun restart do end
+       fun start1 do end
+       fun ended1 do end
+       fun replay1 do end
+       fun restart1 do end
+       fun start2 do end
+       fun ended2 do end
+       fun replay2 do end
+       fun restart2 do end
+end
+
+class NoLargeclass
+       var testVariable = 0
+
+       fun test do end
+end
\ No newline at end of file
diff --git a/tests/TestNitsmells/LongMethod/longmethod.nit b/tests/TestNitsmells/LongMethod/longmethod.nit
new file mode 100644 (file)
index 0000000..584ef44
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A test program with a fake model to check model tools.
+module longmethod
+
+import platform
+
+class Starter
+       var attribute = 0
+       var attribute1 = 0
+       var attribute2 = 0
+       fun start do
+               self.attribute1 = 10
+       end
+
+       fun ended do end
+end
+
+class LongMethodClass
+       var test_variable = 0
+
+       fun long_method do
+               var starter = new Starter
+               test_variable = 3
+       end
+end
\ No newline at end of file
diff --git a/tests/TestNitsmells/LongParameterList/longparameterlist.nit b/tests/TestNitsmells/LongParameterList/longparameterlist.nit
new file mode 100644 (file)
index 0000000..6988536
--- /dev/null
@@ -0,0 +1,34 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A test program with a fake model to check model tools.
+module longparameterlist
+
+import platform
+
+class Starter
+       fun no_para do end
+
+       fun no_para2() do end
+end
+
+class Test
+       fun short_list_parameter(numbers : Int, para1 : Bool, para2 : Float, para3 : Int) do
+               var starter = new Starter
+       end
+
+       fun long_list_parameter(numbers : Int, para1 : Bool, para2 : Float, para3 : Int, para4 : Starter) do
+               var starter = new Starter
+       end
+end
diff --git a/tests/TestNitsmells/platform/platform.nit b/tests/TestNitsmells/platform/platform.nit
new file mode 100644 (file)
index 0000000..175e6fc
--- /dev/null
@@ -0,0 +1,59 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Declares base types allowed on the platform.
+module platform
+
+import end
+
+# Root of everything.
+interface Object
+       # Used for comparisons.
+       type OTHER: nullable Object
+
+       # Is `other` equqls to `self`?
+       fun ==(other: OTHER): Bool is intern
+
+       # Is `other` different from `self`?
+       fun !=(other: OTHER): Bool do return not self == other
+end
+
+# Some services about Integers.
+class Int
+       fun -: Int is intern
+       fun +(i: Int): Int is intern
+       fun -(i: Int): Int is intern
+       fun *(i: Int): Int is intern
+       fun /(i: Int): Int is intern
+       fun >(i: Int): Bool is intern
+       fun to_f: Float is intern
+end
+
+# Some services about Floats.
+class Float
+       fun +(f: Float): Float is intern
+       fun -(f: Float): Float is intern
+       fun *(f: Float): Float is intern
+       fun /(f: Float): Float is intern
+       fun >(f: Float): Bool is intern
+end
+
+# Booleans, `true` or `false`.
+class Bool end
+
+# Strings (there is no chars...).
+class String end
+
+# List of things.
+class List[E] end
diff --git a/tests/nitsmells.args b/tests/nitsmells.args
new file mode 100644 (file)
index 0000000..0032cd9
--- /dev/null
@@ -0,0 +1,5 @@
+--no-colors test_prog/
+--no-colors TestNitsmells/FeatureEnvy/
+--no-colors TestNitsmells/LargeClass/
+--no-colors TestNitsmells/LongMethod/
+--no-colors TestNitsmells/LongParameterList/
\ No newline at end of file
diff --git a/tests/sav/nitsmells.res b/tests/sav/nitsmells.res
new file mode 100644 (file)
index 0000000..9b27b1c
--- /dev/null
@@ -0,0 +1,3 @@
+Usage: nitsmells [OPTION]... <file.nit>...
+ Computes code smells on Nit programs.
+Use --help for help
diff --git a/tests/sav/nitsmells_args1.res b/tests/sav/nitsmells_args1.res
new file mode 100644 (file)
index 0000000..68b3680
--- /dev/null
@@ -0,0 +1,20 @@
+*** CODE SMELLS METRICS ***
+--- Code Smells Metrics ---
+-----------
+test_prog$Character
+Large class: 6 attributes and 18 methods (5.414A 7.161M Average)
+Feature envy:
+       Affected method(s):
+               -total_strengh 4/9
+               -total_endurance 4/9
+               -total_intelligence 4/9
+Long method:  Average 1 lines
+       Affected method(s):
+               -total_strengh has 2 lines
+               -total_endurance has 2 lines
+               -total_intelligence has 2 lines
+-----------
+test_prog::combat$Dwarf
+Feature envy:
+       Affected method(s):
+               -dps 1/3
diff --git a/tests/sav/nitsmells_args2.res b/tests/sav/nitsmells_args2.res
new file mode 100644 (file)
index 0000000..26092c7
--- /dev/null
@@ -0,0 +1,7 @@
+*** CODE SMELLS METRICS ***
+--- Code Smells Metrics ---
+-----------
+TestNitsmells$FeatureEnvy
+Feature envy:
+       Affected method(s):
+               -feature_envy_method 2/6
diff --git a/tests/sav/nitsmells_args3.res b/tests/sav/nitsmells_args3.res
new file mode 100644 (file)
index 0000000..fd60962
--- /dev/null
@@ -0,0 +1,5 @@
+*** CODE SMELLS METRICS ***
+--- Code Smells Metrics ---
+-----------
+TestNitsmells$LargeClass
+Large class: 18 attributes and 48 methods (17.515A 30.464M Average)
diff --git a/tests/sav/nitsmells_args4.res b/tests/sav/nitsmells_args4.res
new file mode 100644 (file)
index 0000000..1fe9b09
--- /dev/null
@@ -0,0 +1,7 @@
+*** CODE SMELLS METRICS ***
+--- Code Smells Metrics ---
+-----------
+TestNitsmells$LongMethodClass
+Long method:  Average 1 lines
+       Affected method(s):
+               -long_method has 2 lines