3 # This file is part of the aMule project.
5 # Copyright (c) 2003-2011 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 sanity testing.
25 def IsFiltered(filename
)
26 (["./amule-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 Sanity Checkers
72 # This class represents the basic sanity-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
< SanityCheck
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
< SanityCheck
151 @name = "EmptyStringAssignment"
152 @title = "Assigning The 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
< SanityCheck
178 @title = "No #ifndef in headerfile"
179 @type = "Good Practice"
180 @desc = "All header files should contain a #ifndef __<FILENAME>__. The purpose 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
< SanityCheck
199 @name = "ThisDeference"
200 @title = "Dereference of \"this\""
201 @type = "Good Practice"
202 @desc = "In all but the case of templates, using \"this->\" is unnecessary 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
< SanityCheck
222 @type = "Consistency"
223 @desc = "wxASSERT()s should be used rather than normal assert()s "
224 @desc += "for the sake of consistency."
227 def parse_file(file
, lines
)
228 results
= lines
.select
do |line
|
229 line
.text
=~
/assert\s*\(/
232 add_results( file
, results
)
238 class WxFailUsage
< SanityCheck
242 @name = "WxFailUsage"
243 @title = "Always failing wxASSERT()"
244 @type = "Good Practice"
245 @desc = "Use wxFAIL_MSG() instead of an always failing wxASSERT()."
248 def parse_file(file
, lines
)
249 results
= lines
.select
do |line
|
250 line
.text
=~
/\bwxASSERT\s*\(\s*(0|false)\s*\)/ or
251 line
.text
=~
/\bwxASSERT_MSG\s*\(\s*(0|false)\s*,/
254 add_results( file
, results
)
260 class NoMoreWxFail
< SanityCheck
264 @name = "NoMoreWxFail"
265 @title = "wxFAIL needs an explanation"
266 @type = "Good Practice"
267 @desc = "Use wxFAIL_MSG() instead of wxFAIL to provide a description of what happened. "
268 @desc += "Or even better, use one of the wxCHECK*() or wxASSERT*() macros if applicable. "
269 @desc += "An assertion of type 'wxAssertFailure' without description does not say much, "
270 @desc += "please provide a short explanation of what went wrong."
273 def parse_file(file
, lines
)
274 results
= lines
.select
do |line
|
275 line
.text
=~
/\bwxFAIL\b/ and not
276 line
.text
=~
/\/\
/\s*wxFAIL\b/
279 add_results( file
, results
)
285 class PassByValue
< SanityCheck
289 @name = "PassByValue"
290 @title = "Pass By Value"
291 @type = "Good Practice"
292 @desc = "Passing objects by value means an extra overhead for large datatypes. "
293 @desc += "Therefore these should always be passed by const reference when possible. "
294 @desc += "Non-const references should only be used for functions where the function is "
295 @desc += "supposed to change the actual value of the argument and return another or no value."
298 def parse_file(file
, lines
)
301 # Only handle header files
303 # Items that should not be passed by value
304 items
= [ "wxString", "wxRect", "wxPoint", "CMD4Hash", "CPath" ]
307 # Try to identify function definitions
308 if line
.text
=~
/^\s*(virtual|static|inline|)\s*\w+\s+\w+\s*\(.*\)/
310 if line
.text
=~
/\(.*\)\s*\{/
311 args
= line
.text
.match(/\(.*\)\s*\{/)[0]
313 args
= line
.text
.match(/\(.*\)/)[0]
315 args
.split(",").each
do |str
|
317 if str
=~
/#{item}\s*[^\s\&\*\(]/
326 add_results( file
, results
)
332 class CStr
< SanityCheck
337 @title = "C_Str or GetData"
339 @desc = "Checks for usage of c_str() or GetData(). Using c_str will often result in "
340 @desc += "problems on Unicoded builds and should therefore be avoided. "
341 @desc += "Please note that the GetData check isn't that precise, because many other "
342 @desc += "classes have GetData members, so it does some crude filtering."
345 def parse_file(file
, lines
)
346 results
= lines
.select
do |line
|
347 if line
.text
=~
/c_str\(\)/ and line
.text
!~
/char2unicode\(/
350 line
.text
=~
/GetData\(\)/ and line
.text
=~
/(wxT\(|wxString|_\()/
354 add_results( file
, results
)
360 class IfNotDefined
< SanityCheck
365 @title = "#if (!)defined"
366 @type = "Consistency"
367 @desc = "Use #ifndef or #ifdef instead for reasons of simplicity."
370 def parse_file(file
, lines
)
371 results
= lines
.select
do |line
|
372 if line
.text
=~
/^#if.*[\!]?defined\(/
373 not line
.text
=~
/(\&\&|\|\|)/
377 add_results( file
, results
)
383 class GPLLicense
< SanityCheck
388 @title = "Missing GPL License"
390 @desc = "All header files should contain the proper GPL blorb."
393 def parse_file(file
, lines
)
395 if lines
.find
{ |x
| x
.text
=~
/This (program|library) is free software;/ } == nil
404 class Copyright
< SanityCheck
408 @name = "MissingCopyright"
409 @title = "Missing Copyright Notice"
411 @desc = "All files should contain the proper Copyright notice."
414 def parse_file(file
, lines
)
416 found
= lines
.select
do |line
|
417 line
.text
=~
/Copyright\s*\([cC]\)\s*[-\d,]+ aMule (Project|Team)/
429 class PartOfAmule
< SanityCheck
433 @name = "aMuleNotice"
434 @title = "Missing aMule notice"
436 @desc = "All files should contain a notice that they are part of the aMule project."
439 def parse_file(file
, lines
)
441 found
= lines
.select
do |line
|
442 line
.text
=~
/This file is part of the aMule Project/ or
443 line
.text
=~
/This file is part of aMule/
455 class MissingBody
< SanityCheck
459 @name = "MissingBody"
460 @title = "Missing Body in Loop"
462 @desc = "This check looks for loops without any body. For example \"while(true);\" "
463 @desc += "In most cases this is a sign of either useless code or bugs. Only in a few "
464 @desc += "cases is it valid code, and in those it can often be represented clearer "
465 @desc += "in other ways."
468 def parse_file(file
, lines
)
469 results
= lines
.select
do |line
|
470 if line
.text
=~
/^[^}]*while\s*\(.*\)\s*;/ or
471 line
.text
=~
/^\s*for\s*\(.*\)\s*;[^\)]*$/
472 # Avoid returning "for" spanning multiple lines
473 # TODO A better way to count instances
474 line
.text
.split("(").size
== line
.text
.split(")").size
480 add_results( file
, results
)
486 class Translation
< SanityCheck
490 @name = "Translation"
491 @type = "Consistency"
492 @desc = "Calls to AddLogLine should translate the message, whereas "
493 @desc += "calls to AddDebugLogLine shouldn't. This is because the user "
494 @desc += "is meant to see normal log lines, whereas the the debug-lines "
495 @desc += "are only meant for the developers and I don't know about you, but "
496 @desc += "I don't plan on learning every language we choose to translate "
497 @desc += "aMule to. :P"
500 def parse_file(file
, lines
)
501 results
= lines
.select
do |line
|
503 line
.text
=~
/AddLogLine.?.?\(.*wxT\(/ or
504 line
.text
=~
/AddDebugLogLine.?\(.*_\(/
510 add_results( file
, results
)
516 class IfZero
< SanityCheck
520 @name = "PreIfConstant"
523 @desc = "Disabled code should be removed as soon as possible. If you wish to disable code "
524 @desc += "for only a short period, then please add a comment before the #if. Code with #if [1-9] "
525 @desc += "should be left, but the #ifs removed unless there is a pressing need for them."
528 def parse_file(file
, lines
)
529 results
= lines
.select
do |line
|
530 line
.text
=~
/#if\s+[0-9]/
533 add_results( file
, results
)
539 class InlinedIfTrue
< SanityCheck
544 @title = "Inlined If true/false"
546 @desc = "Using variations of (x ? true : false) or (x ? false : true) is just plain stupid."
549 def parse_file(file
, lines
)
550 results
= lines
.select
do |line
|
551 line
.text
=~
/\?\s*(true|false)\s*:\s*(true|false)/i
554 add_results( file
, results
)
560 class LoopOnConstant
< SanityCheck
564 @name = "LoopingOnConstant"
565 @title = "Looping On Constant"
567 @desc = "This checks detects loops that evaluate constant values "
568 @desc += "(true,false,0..) or \"for\" loops with no conditionals. all "
569 @desc += "are often a sign of poor code and in most cases can be avoided "
570 @desc += "or replaced with more elegant code."
573 def parse_file(file
, lines
)
574 results
= lines
.select
do |line
|
575 line
.text
=~
/while\s*\(\s*([0-9]+|true|false)\s*\)/i
or
576 line
.text
=~
/for\s*\([^;]*;\s*;[^;]*\)/
579 add_results( file
, results
)
585 class MissingImplementation
< SanityCheck
589 @name = "MissingImplementation"
590 @title = "Missing Function Implementation"
592 @desc = "Forgetting to remove a function-definition after having removed it "
593 @desc += "from the .cpp file only leads to cluttered header files. The only "
594 @desc += "case where non-implemented functions should be used is when it is "
595 @desc += "necessary to prevent usage of for instance assignment between "
596 @desc += "instances of a class."
598 @declarations = Array
.new
599 @definitions = Array
.new
603 @definitions.each
do |definition
|
604 @declarations.delete_if
do |pair
|
605 pair
.first
== definition
610 return @declarations.map
do |pair
|
611 Result
.new( self, pair
.last
.first
, pair
.last
.last
)
615 def parse_file(file
, lines
)
616 if file
=~
/\.h$/ then
621 level
+= line
.text
.count( "{" ) - line
.text
.count( "}" )
623 tree
.delete_if
do |struct
|
628 if line
.text
!~
/^\s*\/\
// and line
.text
!~
/^\s*#/ and line
.text
!~
/typedef/ then
629 if line
.text
=~
/^\s*(class|struct)\s+/ and line
.text
.count( ";" ) == 0 then
632 if line
.text
.count( "{" ) == 0 then
636 name
= line
.text
.scan( /^\s*(class|struct)\s+([^\:{;]+)/ )
638 name
= name
.first
.last
.strip
640 name
= "Unknown at line " + line
.number
.to_s
+ " in " + file
643 tree
<< [ cur_level
, name
]
644 elsif line
.text
=~
/;\s*$/ and line
.text
.count( "{" ) == 0 then
645 # No pure virtual functions and no return(blah) calls (which otherwise can fit the requirements)
646 if line
.text
!~
/=\s*0\s*;\s*$/ and line
.text
!~
/return/ then
647 re
= /^\s*(virtual\s+|static\s+|inline\s+|)\w+(\s+[\*\&]?|[\*\&]\s+)(\w+)\(/.match( line
.text
)
649 if re
and level
> 0 and tree
.last
then
650 @declarations << [ tree
.last
.last
+ "::" + re
[ re
.length
- 1 ], [ file
, line
] ]
658 if line
.text
=~
/\b\w+::\w+\s*\([^;]+$/
659 @definitions << line
.text
.scan( /\b(\w+::\w+)\s*\([^;]+$/ ).first
.first
672 # List of enabled filters
673 filterList
= Array
.new
674 filterList
.push CompareAgainstEmptyString
.new
675 filterList
.push AssignmentToEmptyString
.new
676 filterList
.push NoIfNDef
.new
677 filterList
.push ThisDeference
.new
678 filterList
.push Assert
.new
679 filterList
.push PassByValue
.new
680 filterList
.push CStr
.new
681 filterList
.push IfNotDefined
.new
682 filterList
.push GPLLicense
.new
683 filterList
.push Copyright
.new
684 filterList
.push PartOfAmule
.new
685 filterList
.push MissingBody
.new
686 filterList
.push Translation
.new
687 filterList
.push IfZero
.new
688 filterList
.push InlinedIfTrue
.new
689 filterList
.push LoopOnConstant
.new
690 filterList
.push MissingImplementation
.new
691 filterList
.push WxFailUsage
.new
692 filterList
.push NoMoreWxFail
.new
695 # Sort enabled filters by type and name. The reason why this is done here is
696 # because it's much easier than manually resorting every time I add a filter
697 # or change the name or type of an existing filter.
698 filterList
.sort
! do |x
,y
|
699 cmp
= x
.type
<=> y
.type
710 def parse_files( path
, filters
)
711 filters
= filters
.dup
715 Find
.find( path
) do |filename
|
716 if filename
=~
/\.(cpp|h)$/ and not IsFiltered(filename
) then
717 File
.open(filename
, "r") do |aFile
|
718 # Read lines and add line-numbers
720 aFile
.each_line
do |line
|
721 lines
.push( Line
.new( aFile
.lineno
, line
) )
726 # Check the file against each filter
727 filters
.each
do |filter
|
728 # Process the file with this filter
729 filter
.parse_file( filename
, lines
)
736 filters
.each
do |filter
|
737 results
+= filter
.results
747 def get_val( key
, list
)
748 if not list
.last
or list
.last
.first
!= key
then
749 list
<< [ key
, Array
.new
]
757 def create_result_tree( path
, filters
)
759 results
= parse_files( path
, filters
)
761 # Sort the results by the following sequence of variables: Path -> File -> Filter -> Line
762 results
.sort
! do |a
, b
|
763 if (a
.file_path
<=> b
.file_path
) == 0 then
764 if (a
.file_name
<=> b
.file_name
) == 0 then
765 if (a
.type
.title
<=> b
.type
.title
) == 0 then
766 a
.line
.number
<=> b
.line
.number
768 a
.type
.title
<=> b
.type
.title
771 a
.file_name
<=> b
.file_name
774 a
.file_path
<=> b
.file_path
779 # Create a tree of results: [ Path, [ File, [ Filter, [ Line ] ] ] ]
780 result_tree
= Array
.new
781 results
.each
do |result
|
782 get_val( result
.type
, get_val( result
.file_name
, get_val( result
.file_path
, result_tree
) ) ) << result
791 def create_filter_tree( filters
)
792 # Change the filterList to a tree: [ Type, [ Filter ] ]
793 filter_tree
= Array
.new
795 filters
.each
do |filter
|
796 get_val( filter
.type
, filter_tree
) << filter
804 # Converts a number to a string and pads with zeros so that length becomes at least 5
809 ( "0" * ( 5 - num
.size
) ) + num
817 # Helper-function that escapes some chars to HTML codes
819 str
.gsub
!( /\&/, "&" )
820 str
.gsub
!( /\"/, """ )
821 str
.gsub
!( /</, "<" )
822 str
.gsub
!( />/, ">" )
823 str
.gsub
!( /\n/, "<br>" )
824 str
.gsub( /\t/, " " )
829 # Fugly output code goes here
830 # ... Abandon hope, yee who read past here
831 # TODO Enable use of templates.
833 def OutputHTML( filters
, results
)
837 <STYLE TYPE=\"text/css\">
840 background-color: \#A0A0A0;
845 background-color: \#838383;
850 background-color: #757575;
858 <body bgcolor=\"\#BDBDBD\">
864 filters
.each
do |filterType
|
866 " <dt><b>#{filterType.first}</b></dt>
870 filterType
.last
.each
do |filter
|
872 " <dt id=\"#{filter.name}\"><i>#{filter.title}</i></dt>
874 #{HTMLEsc(filter.desc)}<p>
894 # List the directories
895 results
.each
do |dir
|
898 <a href=\"\##{dir.first}\">#{dir.first}</a>
911 results
.each
do |dir
|
913 " <div class=\"dir\">
914 <h2 id=\"#{dir.first}\">#{dir.first}</h2>
917 dir
.last
.each
do |file
|
919 " <div class=\"file\">
920 <h3>#{file.first}</h3>
925 file
.last
.each
do |filter
|
928 <div class=\"filter\">
929 <b><a href=\"\##{filter.first.name}\">#{filter.first.title}</a></b>
934 filter
.last
.each
do |result
|
937 " <li><b>#{PadNum(result.line.number)}:</b> #{HTMLEsc(result.line.text.strip)}</li>
970 # Columnizing, using the http://www.rubygarden.org/ruby?UsingTestUnit example because I'm lazy
971 # TODO Rewrite it to better support newlines and stuff
972 def Columnize( text
, width
, indent
)
973 return indent
+ text
.scan(/(.{1,#{width}})(?: |$)/).join("\n#{indent}")
978 # Fugly output code also goes here, this is a bit more sparse than the HTML stuff
979 def OutputTEXT( filters
, results
)
983 filters
.each
do |filterType
|
984 text
+= "\t* #{filterType.first}\n"
986 filterType
.last
.each
do |filter
|
987 text
+= "\t\t- #{filter.title}\n"
989 text
+= Columnize( filter
.desc
, 80, "\t\t\t" ) + "\n\n"
993 # List the directories
994 text
+= "\n\nDirectories\n"
995 results
.each
do |dir
|
996 text
+= "\t#{dir.first}\n"
999 text
+= "\n\nResults\n"
1001 # To avoid bad readability, I only use fullpaths here instead of sections per dir
1002 results
.each
do |dir
|
1003 dir
.last
.each
do |file
|
1004 text
+= "\t#{dir.first}#{file.first}\n"
1006 file
.last
.each
do |filter
|
1007 text
+= "\t\t* #{filter.first.title}\n"
1009 filter
.last
.each
do |result
|
1011 text
+= "\t\t\t#{PadNum(result.line.number)}: #{result.line.text.strip}\n"
1025 #TODO Improved parameter-handling, add =<file> for the outputing to a file
1026 ARGV.each
do |param
|
1029 puts
OutputTEXT( create_filter_tree( filterList
), create_result_tree( ".", filterList
) )
1031 puts
OutputHTML( create_filter_tree( filterList
), create_result_tree( ".", filterList
) )