7 C_ISH_FILENAME
= "\.(c|cc|h|cpp|cxx|hxx)$"
8 C_ISH_FILENAME_RE
= re
.compile(C_ISH_FILENAME
)
9 C_MODULE_FILENAME_RE
= re
.compile("\.(c|cc|cpp|cxx)$")
10 FIRST_INCLUDE
= 'config.h'
14 def Problem(**kwargs
):
17 msg
= kwargs
['message']
19 location
= "%(filename)s:%(line)d" % kwargs
21 location
= "%(filename)s" % kwargs
23 print >>sys
.stderr
, "error: %s: %s" % (location
, detail
)
26 class RegexSniffer(object):
27 def __init__(self
, source
, message
):
28 super(RegexSniffer
, self
).__init
__()
29 self
._regex
= re
.compile(source
)
31 def Sniff(self
, text
, filename
, line
):
32 m
= self
._regex
.search(text
)
35 line
= 1 + m
.string
.count('\n', 1, m
.start(0))
37 'filename' : filename
,
40 'matchtext': m
.group(0),
46 class RegexChecker(object):
47 def __init__(self
, regex
, line_smells
, file_smells
):
48 super(RegexChecker
, self
).__init
__(self
)
49 self
._regex
= re
.compile(regex
)
50 self
._line
_sniffers
= [RegexSniffer(s
[0],s
[1]) for s
in line_smells
]
51 self
._file
_sniffers
= [RegexSniffer(s
[0],s
[1]) for s
in file_smells
]
52 def Check(self
, filename
, lines
, fulltext
):
53 if self
._regex
.search(filename
):
54 # We recognise this type of file.
55 for line_number
, line_text
in lines
:
56 for sniffer
in self
._line
_sniffers
:
57 sniffer
.Sniff(line_text
, filename
, line_number
)
58 for sniffer
in self
._file
_sniffers
:
59 sniffer
.Sniff(fulltext
, filename
, None)
61 # We don't know how to check this file. Skip it.
66 # Check C-like languages for C code smells.
67 RegexChecker(C_ISH_FILENAME_RE
,
70 [r
'(?<!\w)free \(\(', "don't cast the argument to free()"],
71 [r
'\*\) *x(m|c|re)alloc(?!\w)',"don't cast the result of x*alloc"],
72 [r
'\*\) *alloca(?!\w)',"don't cast the result of alloca"],
73 [r
'[ ] ',"found SPACE-TAB; remove the space"],
74 [r
'(?<!\w)([fs]?scanf|ato([filq]|ll))(?!\w)',
75 'do not use *scan''f, ato''f, ato''i, ato''l, ato''ll, ato''q, or ss''canf'],
76 [r
'error \(EXIT_SUCCESS',"passing EXIT_SUCCESS to error is confusing"],
77 [r
'file[s]ystem', "prefer writing 'file system' to 'filesystem'"],
78 [r
'HAVE''_CONFIG_H', "Avoid checking HAVE_CONFIG_H"],
79 # [r'HAVE_FCNTL_H', "Avoid checking HAVE_FCNTL_H"],
80 [r
'O_NDELAY', "Avoid using O_NDELAY"],
81 [r
'the *the', "'the the' is probably not deliberate"],
82 [r
'(?<!\w)error \([^_"]*[^_]"[^"]*[a-z]{3}', "untranslated error message"],
83 [r
'^# *if\s+defined *\(', "useless parentheses in '#if defined'"],
87 [r
'# *include <assert.h>(?!.*assert \()',
88 "If you include <assert.h>, use assert()."],
89 [r
'# *include "quotearg.h"(?!.*(?<!\w)quotearg(_[^ ]+)? \()',
90 "If you include \"quotearg.h\", use one of its functions."],
91 [r
'# *include "quote.h"(?!.*(?<!\w)quote(_[^ ]+)? \()',
92 "If you include \"quote.h\", use one of its functions."],
94 # Check Makefiles for Makefile code smells.
95 RegexChecker('(^|/)[Mm]akefile(.am|.in)?',
96 [ [r
'^ ', "Spaces at start of line"], ],
98 # Check everything for whitespace problems.
99 # RegexChecker('', [], [[r'\s$', "trailing whitespace"],]),
100 # Check everything for out of date addresses.
101 RegexChecker('', [], [
102 [r
'675\s*Mass\s*Ave,\s*02139[^a-zA-Z]*USA',
103 "out of date FSF address"],
104 [r
'59 Temple Place.*02111-?1307\s*USA',
105 "out of date FSF address"],
107 # Bourne shell code smells
108 RegexChecker('\.sh$',
110 ['for\s*\w+\s*in.*;\s*do',
111 # Solaris 10 /bin/sh rejects this, see Autoconf manual
112 "for loops should not contain a 'do' on the same line."],
117 # missing check: ChangeLog prefixes
118 # missing: sc_always_defined_macros from coreutils
119 # missing: sc_tight_scope
122 def Warning(filename
, desc
):
123 print >> sys
.stderr
, "warning: %s: %s" % (filename
, desc
)
126 def BuildIncludeList(text
):
127 """Build a list of included files, with line numbers.
129 text: the full text of the source file
131 [ ('config.h',32), ('assert.h',33), ... ]
133 include_re
= re
.compile(r
'# *include +[<"](.*)[>"]')
137 for m
in include_re
.finditer(text
):
139 # Count only the number of lines between the last include and
140 # this one. Counting them from the beginning would be quadratic.
141 line
+= m
.string
.count('\n', last_include_pos
, m
.start(0))
142 last_include_pos
= m
.end()
143 includes
.append( (header
,line
) )
147 def CheckStatHeader(filename
, lines
, fulltext
):
148 stat_hdr_re
= re
.compile(r
'# *include .*<sys/stat.h>')
149 # It's OK to have a pointer though.
150 stat_use_re
= re
.compile(r
'struct stat\W *[^*]')
152 m
= stat_use_re
.search(line
[1])
154 msg
= "If you use struct stat, you must #include <sys/stat.h> first"
155 Problem(filename
= filename
, line
= line
[0], message
= msg
)
158 m
= stat_hdr_re
.search(line
[1])
162 def CheckFirstInclude(filename
, lines
, fulltext
):
163 includes
= BuildIncludeList(fulltext
)
164 #print "Include map:"
165 #for name, line in includes:
166 # print "%s:%d: %s" % (filename, line, name)
168 actual_first_include
= includes
[0][0]
170 actual_first_include
= None
171 if actual_first_include
and actual_first_include
!= FIRST_INCLUDE
:
172 if FIRST_INCLUDE
in [inc
[0] for inc
in includes
]:
173 msg
= ("%(actual_first_include)s is the first included file, "
174 "but %(required_first_include)s should be included first")
175 Problem(filename
=filename
, line
=includes
[0][1], message
=msg
,
176 actual_first_include
=actual_first_include
,
177 required_first_include
= FIRST_INCLUDE
)
178 if FIRST_INCLUDE
not in [inc
[0] for inc
in includes
]:
180 "%s should be included by most files" % FIRST_INCLUDE
)
183 def SniffSourceFile(filename
, lines
, fulltext
):
184 if C_MODULE_FILENAME_RE
.search(filename
):
185 CheckFirstInclude(filename
, lines
, fulltext
)
186 CheckStatHeader (filename
, lines
, fulltext
)
187 for checker
in checkers
:
188 checker
.Check(filename
, lines
, fulltext
)
193 for srcfile
in args
[1:]:
197 for line
in f
.readlines():
198 lines
.append( (line_number
, line
) )
200 fulltext
= ''.join([line
[1] for line
in lines
])
201 SniffSourceFile(srcfile
, lines
, fulltext
)
209 if __name__
== "__main__":
210 sys
.exit(main(sys
.argv
))