3 # This file is part of the aMule project.
5 # Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either
10 # version 2 of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 # This function returns true if a file is filtered
24 # and shound't be included in the sainity testing.
25 def IsFiltered(filename
)
26 (["./config.h", "./configWIN32.h"].index(filename
) != nil) or
27 (filename
=~
/^.\/intl\
//) or
28 (filename
=~
/CryptoPP/)
33 # This class represents lines of code, with line-number and text
34 # It is used to store the source-files once they have been read
35 # and afterwards to store the lines returned by the filters.
37 def initialize( number
, text
)
49 def initialize( type
, file
, line
= nil )
56 @file.slice( /[^\/]+$/ )
60 @file.slice( /^.*\// )
70 # Base class for Sainity Checkers
72 # This class represents the basic sainity-check, which returns all
73 # files as positive results, regardless of the contents.
100 # This function will be called for each file, with the argument "file" as the
101 # name of the file and the argument "lines" being an array of Line objects for
102 # each line of the file.
104 def parse_file(file
, lines
)
105 raise "Missing parse_file() implementation for Filter: #{@name}"
111 def add_results( file
, lines
= [nil] )
113 @results << Result
.new( self, file
, line
)
122 class CompareAgainstEmptyString
< SainityCheck
126 @name = "CmpEmptyString"
127 @title = "Comparing With Empty String"
128 @type = "Good Practice"
129 @desc = "Comparisons with empty strings, such as wxT(\"\"), wxEmptyString and "
130 @desc += "_(\"\") should be avoided since they force the creation of a temporary "
131 @desc += "string object. The proper method is to use the IsEmpty() member-function "
132 @desc += "of wxString."
135 def parse_file(file
, lines
)
136 results
= lines
.select
do |line
|
137 line
.text
=~
/[!=]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/ or
138 line
.text
=~
/(wxEmptyString|wxT\(""\)|_\(""\))\s*[!=]=/
141 add_results( file
, results
)
147 class AssignmentToEmptyString
< SainityCheck
151 @name = "EmptyStringAssignment"
152 @title = "Assignment To Empty String"
153 @type = "Good Practice"
154 @desc = "Assigning an empty string such as wxT(\"\"), wxEmptyString and _(\"\") "
155 @desc += "to a wxString should be avoided, since it forces the creation of a "
156 @desc += "temporary object which is assigned to the string. The proper way to "
157 @desc += "clear a string is to use the Clear() member-function of wxString."
160 def parse_file(file
, lines
)
162 results
= lines
.select
do |line
|
163 line
.text
=~
/[^=!]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/
166 add_results( file
, results
)
173 class NoIfNDef
< SainityCheck
178 @title = "No #ifndef in headerfile"
179 @type = "Good Practice"
180 @desc = "All header files should contain a #ifndef __<FILENAME>__. The purpuse is to ensure "
181 @desc += "that the header can't be included twice, which would introduce a number of problems."
184 def parse_file(file
, lines
)
185 if file
=~
/\.h$/ then
186 if not lines
.find
{ |x
| x
.text
=~
/^#ifndef.*_H/ } then
195 class ThisDeference
< SainityCheck
199 @name = "ThisDeference"
200 @title = "Dereference of \"this\""
201 @type = "Good Practice"
202 @desc = "In all but the case of templates, using \"this->\" is unnescesarry and "
203 @desc += "only decreases the readability of the code."
206 def parse_file(file
, lines
)
207 results
= lines
.select
do |line
|
208 line
.text
=~
/\bthis->/
211 add_results( file
, results
)
217 class Assert
< SainityCheck
222 @type = "Consistancy"
223 @desc = "wxASSERT()s should be used rather than normal assert()s "
224 @desc += "for the sake of consistancy."
227 def parse_file(file
, lines
)
228 results
= lines
.select
do |line
|
229 line
.text
=~
/assert\s*\(/
232 add_results( file
, results
)
238 class PassByValue
< SainityCheck
242 @name = "PassByValue"
243 @title = "Pass By Value"
244 @type = "Good Practice"
245 @desc = "Passing objects by value means an extra overhead for large datatypes. "
246 @desc += "Therefore should these always be passed by const reference when possible."
247 @desc += "Non-const references should only be used for functions where the function is "
248 @desc += "supposed to change the actual value of the argument and return another or no value."
251 def parse_file(file
, lines
)
254 # Only handle header files
256 # Items that should be passed by const-ref
257 items
= [ "wxString", "wxRect", "wxPoint", "CMD4Hash" ]
260 # Try to identify function definitions
261 if line
.text
=~
/^\s*(virtual|static|inline|)\s*\w+\s+\w+\s*\(.*\)/
263 line
.text
.match(/\(.*\)/)[0].split(",").each
do |str
|
265 if str
=~
/#{item}\s*[^\s\*]/ and not str
=~
/const/
274 add_results( file
, results
)
280 class CStr
< SainityCheck
285 @title = "C_Str or GetData"
287 @desc = "Checks for usage of c_str() or GetData(). Using c_str will often result in "
288 @desc += "problems on Unicoded builds and should therefore be avoided. "
289 @desc += "Please note that the GetData check isn't that precise, because many other "
290 @desc += "classes have GetData members, so it does some crude filtering."
293 def parse_file(file
, lines
)
294 results
= lines
.select
do |line
|
295 if line
.text
=~
/c_str\(\)/
298 line
.text
=~
/GetData\(\)/ and line
.text
=~
/(wxT\(|wxString|_\()/
302 add_results( file
, results
)
308 class IfNotDefined
< SainityCheck
313 @title = "#if (!)defined"
314 @type = "Consistancy"
315 @desc = "Use #ifndef or #ifdef instead for reasons of simplicity."
318 def parse_file(file
, lines
)
319 results
= lines
.select
do |line
|
320 if line
.text
=~
/^#if.*[\!]?defined\(/
321 not line
.text
=~
/(\&\&|\|\|)/
325 add_results( file
, results
)
331 class GPLLicense
< SainityCheck
336 @title = "Missing GPL License"
338 @desc = "All header files should contain the proper GPL blorb."
341 def parse_file(file
, lines
)
343 if lines
.find
{ |x
| x
.text
=~
/This (program|library) is free software;/ } == nil
352 class Copyright
< SainityCheck
356 @name = "MissingCopyright"
357 @title = "Missing Copyright Notice"
359 @desc = "All files should contain the proper Copyright notice."
362 def parse_file(file
, lines
)
364 found
= lines
.select
do |line
|
365 line
.text
=~
/Copyright\s*\([cC]\)\s*[-\d,]+ aMule (Project|Team)/
377 class PartOfAmule
< SainityCheck
381 @name = "aMuleNotice"
382 @title = "Missing aMule notice"
384 @desc = "All files should contain a notice that they are part of the aMule project."
387 def parse_file(file
, lines
)
389 found
= lines
.select
do |line
|
390 line
.text
=~
/This file is part of the aMule Project/ or
391 line
.text
=~
/This file is part of aMule/
403 class MissingBody
< SainityCheck
407 @name = "MissingBody"
408 @title = "Missing Body in Loop"
410 @desc = "This checks looks for loops without any body. For example \"while(true);\" "
411 @desc += "In most cases this is a sign of either useless code or bugs. Only in a few "
412 @desc += "cases is it valid code, and in those it can often be represented clearer "
413 @desc += "in other ways."
416 def parse_file(file
, lines
)
417 results
= lines
.select
do |line
|
418 if line
.text
=~
/^[^}]*while\s*\(.*\)\s*;/ or
419 line
.text
=~
/^\s*for\s*\(.*\)\s*;[^\)]*$/
420 # Avoid returning "for" spanning multiple lines
421 # TODO A better way to count instances
422 line
.text
.split("(").size
== line
.text
.split(")").size
428 add_results( file
, results
)
434 class Translation
< SainityCheck
438 @name = "Translation"
439 @type = "Consistancy"
440 @desc = "Calls to AddLogLineM should translate the message, whereas "
441 @desc += "calls to AddDebugLogLine shouldn't. This is because the user "
442 @desc += "is meant to see normal log lines, whereas the the debug-lines "
443 @desc += "are only meant for the developers and I dont know about you, but "
444 @desc += "I dont plan on learning every language we choose to translate "
445 @desc += "aMule to. :P"
448 def parse_file(file
, lines
)
449 results
= lines
.select
do |line
|
451 line
.text
=~
/AddLogLine(M|)\(.*wxT\(/ or
452 line
.text
=~
/AddDebugLogLine(M|)\(.*_\(/
458 add_results( file
, results
)
464 class IfZero
< SainityCheck
468 @name = "PreIfConstant"
471 @desc = "Disabled code should be removed as soon as possible. If you wish to disable code "
472 @desc += "for only a short period, then please add a comment before the #if. Code with #if [1-9] "
473 @desc += "should be left, but the #ifs removed unless there is a pressing need for them."
476 def parse_file(file
, lines
)
477 results
= lines
.select
do |line
|
478 line
.text
=~
/#if\s+[0-9]/
481 add_results( file
, results
)
487 class InlinedIfTrue
< SainityCheck
492 @name = "Inlined If true/false"
494 @desc = "Using variations of (x ? true : false) or (x ? false : true) is just plain stupid."
497 def parse_file(file
, lines
)
498 results
= lines
.select
do |line
|
499 line
.text
=~
/\?\s*(true|false)\s*:\s*(true|false)/i
502 add_results( file
, results
)
508 class LoopOnConstant
< SainityCheck
512 @name = "LoopingOnConstant"
513 @title = "Looping On Constant"
515 @desc = "This checks detects loops that evaluate constant values "
516 @desc += "(true,false,0..) or \"for\" loops with no conditionals. all "
517 @desc += "are often a sign of poor code and in most cases can be avoided "
518 @desc += "or replaced with more elegant code."
521 def parse_file(file
, lines
)
522 results
= lines
.select
do |line
|
523 line
.text
=~
/while\s*\(\s*([0-9]+|true|false)\s*\)/i
or
524 line
.text
=~
/for\s*\([^;]*;\s*;[^;]*\)/
527 add_results( file
, results
)
533 class MissingImplementation
< SainityCheck
537 @name = "MissingImplementation"
538 @title = "Missing Function Implementation"
540 @desc = "Forgetting to remove a function-definition after having removed it "
541 @desc += "from the .cpp file only leads to cluttered header files. The only "
542 @desc += "case where non-implemented functions should be used is when it is "
543 @desc += "necesarry to prevent usage of for instance assignment between "
544 @desc += "instances of a class."
546 @declarations = Array
.new
547 @definitions = Array
.new
551 @definitions.each
do |definition
|
552 @declarations.delete_if
do |pair
|
553 pair
.first
== definition
558 return @declarations.map
do |pair
|
559 Result
.new( self, pair
.last
.first
, pair
.last
.last
)
563 def parse_file(file
, lines
)
564 if file
=~
/\.h$/ then
569 level
+= line
.text
.count( "{" ) - line
.text
.count( "}" )
571 tree
.delete_if
do |struct
|
576 if line
.text
!~
/^\s*\/\
// and line
.text
!~
/^\s*#/ and line
.text
!~
/typedef/ then
577 if line
.text
=~
/^\s*(class|struct)\s+/ and line
.text
.count( ";" ) == 0 then
580 if line
.text
.count( "{" ) == 0 then
584 name
= line
.text
.scan( /^\s*(class|struct)\s+([^\:{;]+)/ )
586 name
= name
.first
.last
.strip
588 name
= "Unknown at line " + line
.number
.to_s
+ " in " + file
591 tree
<< [ cur_level
, name
]
592 elsif line
.text
=~
/;\s*$/ and line
.text
.count( "{" ) == 0 then
593 # No pure virtual functions and no return(blah) calls (which otherwise can fit the requirements)
594 if line
.text
!~
/=\s*0\s*;\s*$/ and line
.text
!~
/return/ then
595 re
= /^\s*(virtual\s+|static\s+|inline\s+|)\w+(\s+[\*\&]?|[\*\&]\s+)(\w+)\(/.match( line
.text
)
597 if re
and level
> 0 and tree
.last
then
598 @declarations << [ tree
.last
.last
+ "::" + re
[ re
.length
- 1 ], [ file
, line
] ]
606 if line
.text
=~
/\b\w+::\w+\s*\([^;]+$/
607 @definitions << line
.text
.scan( /\b(\w+::\w+)\s*\([^;]+$/ ).first
.first
620 # List of enabled filters
621 filterList
= Array
.new
622 filterList
.push CompareAgainstEmptyString
.new
623 filterList
.push AssignmentToEmptyString
.new
624 filterList
.push NoIfNDef
.new
625 filterList
.push ThisDeference
.new
626 filterList
.push Assert
.new
627 filterList
.push PassByValue
.new
628 filterList
.push CStr
.new
629 filterList
.push IfNotDefined
.new
630 filterList
.push GPLLicense
.new
631 filterList
.push Copyright
.new
632 filterList
.push PartOfAmule
.new
633 filterList
.push MissingBody
.new
634 filterList
.push Translation
.new
635 filterList
.push IfZero
.new
636 filterList
.push InlinedIfTrue
.new
637 filterList
.push LoopOnConstant
.new
638 filterList
.push MissingImplementation
.new
641 # Sort enabled filters by type and name. The reason why this is done here is
642 # because it's much easier than manually resorting every time I add a filter
643 # or change the name or type of an existing filter.
644 filterList
.sort
! do |x
,y
|
645 cmp
= x
.type
<=> y
.type
656 def parse_files( path
, filters
)
657 filters
= filters
.dup
661 Find
.find( path
) do |filename
|
662 if filename
=~
/\.(cpp|h)$/ and not IsFiltered(filename
) then
663 File
.open(filename
, "r") do |aFile
|
664 # Read lines and add line-numbers
666 aFile
.each_line
do |line
|
667 lines
.push( Line
.new( aFile
.lineno
, line
) )
672 # Check the file against each filter
673 filters
.each
do |filter
|
674 # Process the file with this filter
675 filter
.parse_file( filename
, lines
)
682 filters
.each
do |filter
|
683 results
+= filter
.results
693 def get_val( key
, list
)
694 if not list
.last
or list
.last
.first
!= key
then
695 list
<< [ key
, Array
.new
]
703 def create_result_tree( path
, filters
)
705 results
= parse_files( path
, filters
)
707 # Sort the results by the following sequence of variables: Path -> File -> Filter -> Line
708 results
.sort
! do |a
, b
|
709 if (a
.file_path
<=> b
.file_path
) == 0 then
710 if (a
.file_name
<=> b
.file_name
) == 0 then
711 if (a
.type
.title
<=> b
.type
.title
) == 0 then
712 a
.line
.number
<=> b
.line
.number
714 a
.type
.title
<=> b
.type
.title
717 a
.file_name
<=> b
.file_name
720 a
.file_path
<=> b
.file_path
725 # Create a tree of results: [ Path, [ File, [ Filter, [ Line ] ] ] ]
726 result_tree
= Array
.new
727 results
.each
do |result
|
728 get_val( result
.type
, get_val( result
.file_name
, get_val( result
.file_path
, result_tree
) ) ) << result
737 def create_filter_tree( filters
)
738 # Change the filterList to a tree: [ Type, [ Filter ] ]
739 filter_tree
= Array
.new
741 filters
.each
do |filter
|
742 get_val( filter
.type
, filter_tree
) << filter
750 # Converts a number to a string and pads with zeros so that length becomes at least 5
755 ( "0" * ( 5 - num
.size
) ) + num
763 # Helper-function that escapes some chars to HTML codes
765 str
.gsub
!( /\&/, "&" )
766 str
.gsub
!( /\"/, """ )
767 str
.gsub
!( /</, "<" )
768 str
.gsub
!( />/, ">" )
769 str
.gsub
!( /\n/, "<br>" )
770 str
.gsub( /\t/, " " )
775 # Fugly output code goes here
776 # ... Abandon hope, yee who read past here
777 # TODO Enable use of templates.
779 def OutputHTML( filters
, results
)
783 <STYLE TYPE=\"text/css\">
786 background-color: \#A0A0A0;
791 background-color: \#838383;
796 background-color: #757575;
804 <body bgcolor=\"\#BDBDBD\">
810 filters
.each
do |filterType
|
812 " <dt><b>#{filterType.first}</b></dt>
816 filterType
.last
.each
do |filter
|
818 " <dt id=\"#{filter.name}\"><i>#{filter.title}</i></dt>
820 #{HTMLEsc(filter.desc)}<p>
840 # List the directories
841 results
.each
do |dir
|
844 <a href=\"\##{dir.first}\">#{dir.first}</a>
857 results
.each
do |dir
|
859 " <div class=\"dir\">
860 <h2 id=\"#{dir.first}\">#{dir.first}</h2>
863 dir
.last
.each
do |file
|
865 " <div class=\"file\">
866 <h3>#{file.first}</h3>
871 file
.last
.each
do |filter
|
874 <div class=\"filter\">
875 <b><a href=\"\##{filter.first.name}\">#{filter.first.title}</a></b>
880 filter
.last
.each
do |result
|
883 " <li><b>#{PadNum(result.line.number)}:</b> #{HTMLEsc(result.line.text.strip)}</li>
916 # Columnizing, using the http://www.rubygarden.org/ruby?UsingTestUnit example because I'm lazy
917 # TODO Rewrite it to better support newlines and stuff
918 def Columnize( text
, width
, indent
)
919 return indent
+ text
.scan(/(.{1,#{width}})(?: |$)/).join("\n#{indent}")
924 # Fugly output code also goes here, this is a bit more sparse than the HTML stuff
925 def OutputTEXT( filters
, results
)
929 filters
.each
do |filterType
|
930 text
+= "\t* #{filterType.first}\n"
932 filterType
.last
.each
do |filter
|
933 text
+= "\t\t- #{filter.title}\n"
935 text
+= Columnize( filter
.desc
, 80, "\t\t\t" ) + "\n\n"
939 # List the directories
940 text
+= "\n\nDirectories\n"
941 results
.each
do |dir
|
942 text
+= "\t#{dir.first}\n"
945 text
+= "\n\nResults\n"
947 # To avoid bad readability, I only use fullpaths here instead of sections per dir
948 results
.each
do |dir
|
949 dir
.last
.each
do |file
|
950 text
+= "\t#{dir.first}#{file.first}\n"
952 file
.last
.each
do |filter
|
953 text
+= "\t\t* #{filter.first.title}\n"
955 filter
.last
.each
do |result
|
957 text
+= "\t\t\t#{PadNum(result.line.number)}: #{result.line.text.strip}\n"
971 #TODO Improved parameter-handling, add =<file> for the outputing to a file
975 puts
OutputTEXT( create_filter_tree( filterList
), create_result_tree( ".", filterList
) )
977 puts
OutputHTML( create_filter_tree( filterList
), create_result_tree( ".", filterList
) )