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"],
110 # missing check: ChangeLog prefixes
111 # missing: sc_always_defined_macros from coreutils
112 # missing: sc_tight_scope
115 def Warning(filename
, desc
):
116 print >> sys
.stderr
, "warning: %s: %s" % (filename
, desc
)
119 def BuildIncludeList(text
):
120 """Build a list of included files, with line numbers.
122 text: the full text of the source file
124 [ ('config.h',32), ('assert.h',33), ... ]
126 include_re
= re
.compile(r
'# *include +[<"](.*)[>"]')
130 for m
in include_re
.finditer(text
):
132 # Count only the number of lines between the last include and
133 # this one. Counting them from the beginning would be quadratic.
134 line
+= m
.string
.count('\n', last_include_pos
, m
.start(0))
135 last_include_pos
= m
.end()
136 includes
.append( (header
,line
) )
140 def CheckStatHeader(filename
, lines
, fulltext
):
141 stat_hdr_re
= re
.compile(r
'# *include .*<sys/stat.h>')
142 # It's OK to have a pointer though.
143 stat_use_re
= re
.compile(r
'struct stat\W *[^*]')
145 m
= stat_use_re
.search(line
[1])
147 msg
= "If you use struct stat, you must #include <sys/stat.h> first"
148 Problem(filename
= filename
, line
= line
[0], message
= msg
)
151 m
= stat_hdr_re
.search(line
[1])
155 def CheckFirstInclude(filename
, lines
, fulltext
):
156 includes
= BuildIncludeList(fulltext
)
157 #print "Include map:"
158 #for name, line in includes:
159 # print "%s:%d: %s" % (filename, line, name)
161 actual_first_include
= includes
[0][0]
163 actual_first_include
= None
164 if actual_first_include
and actual_first_include
!= FIRST_INCLUDE
:
165 if FIRST_INCLUDE
in [inc
[0] for inc
in includes
]:
166 msg
= ("%(actual_first_include)s is the first included file, "
167 "but %(required_first_include)s should be included first")
168 Problem(filename
=filename
, line
=includes
[0][1], message
=msg
,
169 actual_first_include
=actual_first_include
,
170 required_first_include
= FIRST_INCLUDE
)
171 if FIRST_INCLUDE
not in [inc
[0] for inc
in includes
]:
173 "%s should be included by most files" % FIRST_INCLUDE
)
176 def SniffSourceFile(filename
, lines
, fulltext
):
177 if C_MODULE_FILENAME_RE
.search(filename
):
178 CheckFirstInclude(filename
, lines
, fulltext
)
179 CheckStatHeader (filename
, lines
, fulltext
)
180 for checker
in checkers
:
181 checker
.Check(filename
, lines
, fulltext
)
186 for srcfile
in args
[1:]:
190 for line
in f
.readlines():
191 lines
.append( (line_number
, line
) )
193 fulltext
= ''.join([line
[1] for line
in lines
])
194 SniffSourceFile(srcfile
, lines
, fulltext
)
202 if __name__
== "__main__":
203 sys
.exit(main(sys
.argv
))