Removed HAVE_CONFIG_H
[amule.git] / src / utils / scripts / sanity
blobf7552f139160ef1df61e38991935262144aeea14
1 #!/usr/bin/ruby
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/)
29 end
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.
36 class Line
37 def initialize( number, text )
38 @number = number
39 @text = text
40 end
42 attr_reader :number
43 attr_reader :text
44 end
48 class Result
49 def initialize( type, file, line = nil )
50 @type = type
51 @file = file
52 @line = line
53 end
55 def file_name
56 @file.slice( /[^\/]+$/ )
57 end
59 def file_path
60 @file.slice( /^.*\// )
61 end
63 attr_reader :type
64 attr_reader :file
65 attr_reader :line
66 end
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.
74 class SanityCheck
75 def initialize
76 @name = "None"
77 @title = nil
78 @type = "None"
79 @desc = "None"
80 @results = Array.new
81 end
83 attr_reader :name
84 attr_reader :type
85 attr_reader :desc
87 def title
88 if @title then
89 @title
90 else
91 @name
92 end
93 end
96 def results
97 @results
98 end
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}"
109 private
111 def add_results( file, lines = [nil] )
112 lines.each do |line|
113 @results << Result.new( self, file, line )
122 class CompareAgainstEmptyString < SanityCheck
123 def initialize
124 super
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
148 def initialize
149 super
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)
161 if file =~ /\.cpp$/
162 results = lines.select do |line|
163 line.text =~ /[^=!]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/
166 add_results( file, results )
173 class NoIfNDef < SanityCheck
174 def initialize
175 super
177 @name = "NoIfNDef"
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
187 add_results( file )
195 class ThisDeference < SanityCheck
196 def initialize
197 super
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
218 def initialize
219 super
221 @name = "Assert"
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
239 def initialize
240 super
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
261 def initialize
262 super
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
286 def initialize
287 super
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)
299 results = Array.new
301 # Only handle header files
302 if file =~ /\.h$/
303 # Items that should not be passed by value
304 items = [ "wxString", "wxRect", "wxPoint", "CMD4Hash", "CPath" ]
306 lines.each do |line|
307 # Try to identify function definitions
308 if line.text =~ /^\s*(virtual|static|inline|)\s*\w+\s+\w+\s*\(.*\)/
309 # Split by arguments
310 if line.text =~ /\(.*\)\s*\{/
311 args = line.text.match(/\(.*\)\s*\{/)[0]
312 else
313 args = line.text.match(/\(.*\)/)[0]
315 args.split(",").each do |str|
316 items.each do |item|
317 if str =~ /#{item}\s*[^\s\&\*\(]/
318 results.push( line )
326 add_results( file, results )
332 class CStr < SanityCheck
333 def initialize
334 super
336 @name = "CStr"
337 @title = "C_Str or GetData"
338 @type = "Unicoding"
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\(/
348 true
349 else
350 line.text =~ /GetData\(\)/ and line.text =~ /(wxT\(|wxString|_\()/
354 add_results( file, results )
360 class IfNotDefined < SanityCheck
361 def initialize
362 super
364 @name = "IfDefined"
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
384 def initialize
385 super
387 @name = "MissingGPL"
388 @title = "Missing GPL License"
389 @type = "License"
390 @desc = "All header files should contain the proper GPL blorb."
393 def parse_file(file, lines)
394 if file =~ /\.h$/
395 if lines.find { |x| x.text =~ /This (program|library) is free software;/ } == nil
396 add_results( file )
404 class Copyright < SanityCheck
405 def initialize
406 super
408 @name = "MissingCopyright"
409 @title = "Missing Copyright Notice"
410 @type = "License"
411 @desc = "All files should contain the proper Copyright notice."
414 def parse_file(file, lines)
415 if file =~ /\.h$/
416 found = lines.select do |line|
417 line.text =~ /Copyright\s*\([cC]\)\s*[-\d,]+ aMule (Project|Team)/
420 if found.empty? then
421 add_results( file )
429 class PartOfAmule < SanityCheck
430 def initialize
431 super
433 @name = "aMuleNotice"
434 @title = "Missing aMule notice"
435 @type = "License"
436 @desc = "All files should contain a notice that they are part of the aMule project."
439 def parse_file(file, lines)
440 if file =~ /\.h$/
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/
446 if found.empty? then
447 add_results( file )
455 class MissingBody < SanityCheck
456 def initialize
457 super
459 @name = "MissingBody"
460 @title = "Missing Body in Loop"
461 @type = "Garbage"
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
475 else
476 false
480 add_results( file, results )
486 class Translation < SanityCheck
487 def initialize
488 super
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|
502 if line.text =~ /\"/
503 line.text =~ /AddLogLine.?.?\(.*wxT\(/ or
504 line.text =~ /AddDebugLogLine.?\(.*_\(/
505 else
506 false
510 add_results( file, results )
516 class IfZero < SanityCheck
517 def initialize
518 super
520 @name = "PreIfConstant"
521 @title = "#if 0-9"
522 @type = "Garbage"
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
540 def initialize
541 super
543 @name = "InlinedIf"
544 @title = "Inlined If true/false"
545 @type = "Garbage"
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
561 def initialize
562 super
564 @name = "LoopingOnConstant"
565 @title = "Looping On Constant"
566 @type = "Garbage"
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
586 def initialize
587 super
589 @name = "MissingImplementation"
590 @title = "Missing Function Implementation"
591 @type = "Garbage"
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
602 def results
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
617 level = 0
618 tree = Array.new
620 lines.each do |line|
621 level += line.text.count( "{" ) - line.text.count( "}" )
623 tree.delete_if do |struct|
624 struct.first > level
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
630 cur_level = level;
632 if line.text.count( "{" ) == 0 then
633 cur_level += 1
636 name = line.text.scan( /^\s*(class|struct)\s+([^\:{;]+)/ )
637 if name != [] then
638 name = name.first.last.strip
639 else
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 ] ]
656 else
657 lines.each do |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
701 if cmp == 0 then
702 x.title <=> y.title
703 else
710 def parse_files( path, filters )
711 filters = filters.dup
713 require "find"
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
719 lines = Array.new
720 aFile.each_line do |line|
721 lines.push( Line.new( aFile.lineno, line ) )
724 lines.freeze
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 )
735 results = Array.new
736 filters.each do |filter|
737 results += filter.results
740 results
746 # Helper-function
747 def get_val( key, list )
748 if not list.last or list.last.first != key then
749 list << [ key, Array.new ]
752 list.last.last
757 def create_result_tree( path, filters )
758 # Gather the results
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
767 else
768 a.type.title <=> b.type.title
770 else
771 a.file_name <=> b.file_name
773 else
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
786 result_tree
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
799 filter_tree
804 # Converts a number to a string and pads with zeros so that length becomes at least 5
805 def PadNum( number )
806 num = number.to_s
808 if ( num.size < 5 )
809 ( "0" * ( 5 - num.size ) ) + num
810 else
817 # Helper-function that escapes some chars to HTML codes
818 def HTMLEsc( str )
819 str.gsub!( /\&/, "&amp;" )
820 str.gsub!( /\"/, "&quot;" )
821 str.gsub!( /</, "&lt;" )
822 str.gsub!( />/, "&gt;" )
823 str.gsub!( /\n/, "<br>" )
824 str.gsub( /\t/, "&nbsp;" )
829 # Fugly output code goes here
830 # ... Abandon hope, yee who read past here
831 # TODO Enable use of templates.
832 # TODO Abandon hope.
833 def OutputHTML( filters, results )
834 text =
835 "<html>
836 <head>
837 <STYLE TYPE=\"text/css\">
838 <!--
839 .dir {
840 background-color: \#A0A0A0;
841 padding-left: 10pt;
842 padding-right: 10pt;
844 .file {
845 background-color: \#838383;
846 padding-left: 10pt;
847 padding-right: 10pt;
849 .filter {
850 background-color: #757575;
851 padding-left: 10pt;
852 padding-right: 10pt;
853 padding-top: 5pt;
856 </STYLE>
857 </head>
858 <body bgcolor=\"\#BDBDBD\">
860 <h1>Filters</h1>
861 <dl>
863 # List the filters
864 filters.each do |filterType|
865 text +=
866 " <dt><b>#{filterType.first}</b></dt>
867 <dd>
868 <dl>
870 filterType.last.each do |filter|
871 text +=
872 " <dt id=\"#{filter.name}\"><i>#{filter.title}</i></dt>
873 <dd>
874 #{HTMLEsc(filter.desc)}<p>
875 </dd>
879 text +=
880 " </dl>
881 </dd>
885 text +=
886 " </dl>
890 <h1>Directories</h1>
891 <ul>
894 # List the directories
895 results.each do |dir|
896 text +=
897 " <li>
898 <a href=\"\##{dir.first}\">#{dir.first}</a>
899 </li>
903 text +=
904 " </ul>
908 <h1>Results</h1>
911 results.each do |dir|
912 text +=
913 " <div class=\"dir\">
914 <h2 id=\"#{dir.first}\">#{dir.first}</h2>
917 dir.last.each do |file|
918 text +=
919 " <div class=\"file\">
920 <h3>#{file.first}</h3>
922 <ul>
925 file.last.each do |filter|
926 text +=
927 " <li>
928 <div class=\"filter\">
929 <b><a href=\"\##{filter.first.name}\">#{filter.first.title}</a></b>
931 <ul>
934 filter.last.each do |result|
935 if result.line then
936 text +=
937 " <li><b>#{PadNum(result.line.number)}:</b> #{HTMLEsc(result.line.text.strip)}</li>
942 text +=
943 " </ul>
944 </div>
945 </li>
948 text +=
949 " </ul>
950 </div>
954 text +=
955 " </div>
961 text +=
962 " </body>
963 </html>"
965 return text;
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 )
981 # List the filters
982 text = "Filters\n"
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|
1010 if result.line then
1011 text += "\t\t\t#{PadNum(result.line.number)}: #{result.line.text.strip}\n"
1016 text += "\n"
1020 return text;
1025 #TODO Improved parameter-handling, add =<file> for the outputing to a file
1026 ARGV.each do |param|
1027 case param
1028 when "--text" then
1029 puts OutputTEXT( create_filter_tree( filterList ), create_result_tree( ".", filterList ) )
1030 when "--html" then
1031 puts OutputHTML( create_filter_tree( filterList ), create_result_tree( ".", filterList ) )